diskann_providers/model/graph/provider/async_/
simple_neighbor_provider.rs1use std::sync::RwLock;
7
8use crate::storage::{StorageReadProvider, StorageWriteProvider};
9use diskann::{ANNError, ANNResult, graph::AdjacencyList, provider::HasId};
10use diskann_vector::contains::ContainsSimd;
11use tracing::trace;
12
13use super::common::{AlignedMemoryVectorStore, TestCallCount};
14use crate::storage::{
15 self, AsyncIndexMetadata, AsyncQuantLoadContext, DiskGraphOnly, LoadWith, SaveWith,
16};
17
18pub struct SimpleNeighborProviderAsync {
19 graph: AlignedMemoryVectorStore<u32>,
22 locks: Vec<RwLock<()>>,
23 num_start_points: usize,
24
25 pub num_get_calls: TestCallCount,
26}
27
28impl SimpleNeighborProviderAsync {
29 pub fn new(
30 max_points: usize,
31 num_start_points: usize,
32 max_degree: u32,
33 graph_slack_factor: f32,
34 ) -> Self {
35 let size = max_points + num_start_points;
36 let graph = AlignedMemoryVectorStore::with_capacity(
37 size,
38 (max_degree as f32 * graph_slack_factor) as usize + 1,
39 );
40 let locks = (0..size).map(|_| RwLock::new(())).collect::<Vec<_>>();
41
42 Self {
43 graph,
44 locks,
45 num_start_points,
46 num_get_calls: TestCallCount::default(),
47 }
48 }
49
50 unsafe fn get_slice(&self, index: usize) -> &[u32] {
57 let s = unsafe { self.graph.get_slice(index) };
60
61 let len = s[self.graph.dim() - 1] as usize;
62 &s[0..len]
63 }
64
65 pub fn set_neighbors_sync(&self, id: usize, neighbors: &[u32]) -> ANNResult<()> {
66 assert!(
67 neighbors.len() < self.graph.dim(),
68 "neighbors ({}) exceeded max adjacency list size ({})",
69 neighbors.len(),
70 self.graph.dim() - 1,
71 );
72
73 #[allow(clippy::unwrap_used)]
75 let _guard = self.locks[id].write().unwrap();
76
77 let list = unsafe { self.graph.get_mut_slice(id) };
79 list[0..neighbors.len()].copy_from_slice(neighbors);
80
81 list[self.graph.dim() - 1] = neighbors.len() as u32;
84 Ok(())
85 }
86
87 pub fn get_neighbors_sync(
88 &self,
89 id: usize,
90 neighbors: &mut AdjacencyList<u32>,
91 ) -> ANNResult<()> {
92 #[cfg(test)]
93 self.num_get_calls.increment();
94
95 #[allow(clippy::unwrap_used)]
97 let _guard = self.locks[id].read().unwrap();
98
99 let list = unsafe { self.get_slice(id) };
101 neighbors.overwrite_trusted(list);
102 Ok(())
103 }
104
105 pub fn append_vector_sync(&self, id: usize, new_neighbor_ids: &[u32]) -> ANNResult<()> {
106 #[allow(clippy::unwrap_used)]
108 let _guard = self.locks[id].write().unwrap();
109
110 let list_raw = unsafe { self.graph.get_mut_slice(id) };
112 let len = list_raw[self.graph.dim() - 1] as usize;
113 let mut new_len = len;
114 let mut list = &mut list_raw[0..len];
115
116 for new_neighbor_id in new_neighbor_ids {
117 if u32::contains_simd(list, *new_neighbor_id) {
118 trace!("append_vector: new neighbor already exists");
119 continue;
120 }
121
122 if new_len < self.graph.dim() - 1 {
123 list_raw[new_len] = *new_neighbor_id;
124 new_len += 1;
125 list = &mut list_raw[0..new_len];
126 } else {
127 trace!("append_vector: some new neighbors discarded; adjacency list full");
128 break;
129 }
130 }
131
132 list_raw[self.graph.dim() - 1] = new_len as u32;
134 Ok(())
135 }
136}
137
138impl HasId for SimpleNeighborProviderAsync {
139 type Id = u32;
140}
141
142impl SimpleNeighborProviderAsync {
143 pub fn load_direct<P>(provider: &P, path: &str) -> ANNResult<Self>
147 where
148 P: StorageReadProvider,
149 {
150 storage::bin::load_graph(
151 provider,
152 path,
153 |num_points, max_degree, num_start_points| {
154 let max_points = num_points.checked_sub(num_start_points).ok_or_else(|| {
159 ANNError::log_index_error(format_args!(
160 "expected {} start points but the on-disk dataset only has {} total points",
161 num_start_points, num_points,
162 ))
163 })?;
164
165 Ok(Self::new(
168 max_points,
169 num_start_points,
170 max_degree as u32,
171 1.0,
172 ))
173 },
174 )
175 }
176
177 pub fn save_direct<P>(&self, provider: &P, start_point: u32, path: &str) -> ANNResult<usize>
181 where
182 P: StorageWriteProvider,
183 {
184 storage::bin::save_graph(self, provider, start_point, path)
185 }
186}
187
188impl SaveWith<(u32, AsyncIndexMetadata)> for SimpleNeighborProviderAsync {
193 type Ok = usize;
194 type Error = ANNError;
195
196 async fn save_with<P>(
197 &self,
198 provider: &P,
199 (start_point, metadata): &(u32, AsyncIndexMetadata),
200 ) -> ANNResult<usize>
201 where
202 P: StorageWriteProvider,
203 {
204 self.save_direct(provider, *start_point, metadata.prefix())
205 }
206}
207
208impl SaveWith<(u32, u32, DiskGraphOnly)> for SimpleNeighborProviderAsync {
217 type Ok = usize;
218 type Error = ANNError;
219
220 async fn save_with<P>(
221 &self,
222 provider: &P,
223 (imem_start_point, actual_start_point, metadata): &(u32, u32, DiskGraphOnly),
224 ) -> Result<Self::Ok, Self::Error>
225 where
226 P: StorageWriteProvider,
227 {
228 let graph = DiskAdaptor {
229 provider: self,
230 inmem_start_point: *imem_start_point,
231 actual_start_point: *actual_start_point,
232 };
233
234 storage::bin::save_graph(&graph, provider, *actual_start_point, metadata.prefix())
235 }
236}
237
238impl LoadWith<AsyncIndexMetadata> for SimpleNeighborProviderAsync {
240 type Error = ANNError;
241
242 async fn load_with<P>(provider: &P, metadata: &AsyncIndexMetadata) -> ANNResult<Self>
243 where
244 P: StorageReadProvider,
245 {
246 Self::load_direct(provider, metadata.prefix())
247 }
248}
249
250impl LoadWith<AsyncQuantLoadContext> for SimpleNeighborProviderAsync {
252 type Error = ANNError;
253
254 async fn load_with<P>(provider: &P, ctx: &AsyncQuantLoadContext) -> ANNResult<Self>
255 where
256 P: StorageReadProvider,
257 {
258 Self::load_with(provider, &ctx.metadata).await
259 }
260}
261
262impl storage::bin::SetAdjacencyList for SimpleNeighborProviderAsync {
268 type Item = u32;
269 fn set_adjacency_list(&mut self, i: usize, element: &[u32]) -> ANNResult<()> {
270 self.set_neighbors_sync(i, element)?;
271 Ok(())
272 }
273}
274
275impl storage::bin::GetAdjacencyList for SimpleNeighborProviderAsync {
277 type Element = u32;
278 type Item<'a> = AdjacencyList<u32>;
279
280 fn get_adjacency_list(&self, i: usize) -> ANNResult<Self::Item<'_>> {
281 let mut list = AdjacencyList::new();
282 self.get_neighbors_sync(i, &mut list)?;
283 Ok(list)
284 }
285
286 fn total(&self) -> usize {
287 self.locks.len()
288 }
289
290 fn additional_points(&self) -> u64 {
291 self.num_start_points as u64
292 }
293
294 fn max_degree(&self) -> Option<u32> {
295 Some((self.graph.dim() - 1) as u32)
296 }
297}
298
299struct DiskAdaptor<'a> {
315 provider: &'a SimpleNeighborProviderAsync,
316 inmem_start_point: u32,
317 actual_start_point: u32,
318}
319
320impl storage::bin::GetAdjacencyList for DiskAdaptor<'_> {
321 type Element = u32;
322 type Item<'item>
323 = Vec<u32>
324 where
325 Self: 'item;
326
327 fn get_adjacency_list(&self, i: usize) -> ANNResult<Self::Item<'_>> {
328 let mut list = AdjacencyList::new();
329 self.provider.get_neighbors_sync(i, &mut list)?;
330
331 let mut list: Vec<_> = list.into();
335 for i in list.iter_mut() {
336 if *i == self.inmem_start_point {
337 *i = self.actual_start_point;
338 }
339 }
340
341 Ok(list)
342 }
343
344 fn total(&self) -> usize {
345 self.provider.locks.len() - self.provider.num_start_points
347 }
348
349 fn additional_points(&self) -> u64 {
351 0
352 }
353
354 fn max_degree(&self) -> Option<u32> {
355 None
356 }
357}
358
359#[cfg(test)]
364mod tests {
365 use crate::storage::VirtualStorageProvider;
366
367 use super::*;
368
369 #[test]
370 fn test_neighbor_provider() {
371 let neighbor_provider = SimpleNeighborProviderAsync::new(10, 1, 5, 1.0);
372
373 let adj_list = vec![1, 2, 3];
374 neighbor_provider.set_neighbors_sync(1, &adj_list).unwrap();
375
376 let mut result = AdjacencyList::new();
377 neighbor_provider
378 .get_neighbors_sync(1, &mut result)
379 .unwrap();
380
381 assert_eq!(&adj_list, &*result);
382
383 let new_adj_list = AdjacencyList::from_iter_untrusted([4, 5, 6]);
384 neighbor_provider
385 .set_neighbors_sync(1, &new_adj_list)
386 .unwrap();
387
388 neighbor_provider
389 .get_neighbors_sync(1, &mut result)
390 .unwrap();
391
392 assert_eq!(new_adj_list, result);
393 }
394
395 #[tokio::test]
396 async fn test_save_load() {
397 let max_degree = 5;
398 let max_points = 8;
399 let additional_points = 2;
400
401 let provider =
402 SimpleNeighborProviderAsync::new(max_points, additional_points, max_degree, 1.0);
403
404 let storage = VirtualStorageProvider::new_memory();
406
407 for i in 0..max_points + additional_points {
409 let neighbors: Vec<u32> = (1..4).map(|j| i as u32 + j).collect();
410 provider.set_neighbors_sync(i, &neighbors).unwrap();
411 }
412
413 let prefix = AsyncIndexMetadata::new("/resumable_test");
414 let start_point = 0;
416 let result = provider
417 .save_with(&storage, &(start_point, prefix.clone()))
418 .await;
419 assert!(result.is_ok(), "Failed to save with resumable context");
420
421 let expected_path = prefix.prefix();
423 assert!(
424 storage.exists(expected_path),
425 "Resumable graph file was not created"
426 );
427
428 let receiver = SimpleNeighborProviderAsync::load_direct(&storage, prefix.prefix()).unwrap();
429
430 for i in 0..max_points + additional_points {
431 let mut result = AdjacencyList::new();
432 let mut loaded_result = AdjacencyList::new();
433 provider.get_neighbors_sync(i, &mut result).unwrap();
434 receiver.get_neighbors_sync(i, &mut loaded_result).unwrap();
435 assert_eq!(
436 result, loaded_result,
437 "Adjacency list for node {} doesn't match after loading",
438 i
439 );
440 }
441 }
442}