use std::marker::PhantomData;
use bf_tree::{BfTree, Config};
use bytemuck::{cast_slice, cast_slice_mut};
use diskann::{
graph::AdjacencyList,
provider::{self, HasId},
utils::{IntoUsize, VectorId},
ANNError, ANNResult,
};
use super::ConfigError;
use crate::TestCallCount;
pub struct NeighborProvider<I: VectorId> {
adjacency_list_index: BfTree,
dim: usize, #[allow(dead_code)]
pub(crate) num_get_calls: TestCallCount,
_phantom: PhantomData<I>,
}
impl<I: VectorId> HasId for NeighborProvider<I> {
type Id = I;
}
impl<I: VectorId> NeighborProvider<I> {
pub fn new_with_config(max_degree: u32, config: Config) -> ANNResult<Self> {
let adj_list_index = BfTree::with_config(config, None).map_err(ConfigError)?;
Self::new(max_degree, adj_list_index)
}
fn new(max_degree: u32, adjacency_list_index: BfTree) -> ANNResult<Self> {
let dim = 1 + max_degree.into_usize();
Ok(Self {
adjacency_list_index,
dim,
num_get_calls: TestCallCount::default(),
_phantom: PhantomData,
})
}
pub(crate) fn config(&self) -> &Config {
self.adjacency_list_index.config()
}
pub(crate) fn bftree(&self) -> &BfTree {
&self.adjacency_list_index
}
pub fn max_degree(&self) -> u32 {
(self.dim - 1) as u32
}
pub(crate) fn new_from_bftree(
max_degree: u32,
adjacency_list_index: BfTree,
) -> ANNResult<Self> {
Self::new(max_degree, adjacency_list_index)
}
pub fn get_neighbors(&self, vector_id: I, neighbors: &mut AdjacencyList<I>) -> ANNResult<()> {
#[cfg(test)]
self.num_get_calls.increment();
let mut guard = neighbors.resize(self.dim);
let key = bytemuck::bytes_of(&vector_id);
let value = cast_slice_mut::<I, u8>(&mut guard);
match self.adjacency_list_index.read(key, value) {
bf_tree::LeafReadResult::Found(read_size) => {
if read_size > 0 {
if read_size as usize != self.dim * std::mem::size_of::<I>() {
return Err(ANNError::log_index_error(
"Retrieved neighbor list is not expected length = max degree + 1",
));
}
let count = cell_to_len(&guard[self.dim - 1]);
if count > self.max_degree() {
return Err(ANNError::log_index_error(
"Size of retrieved neighbor list is shorter than the stored neighbor count",
));
}
guard.finish(count as usize);
}
}
bf_tree::LeafReadResult::Deleted => {
return Err(ANNError::log_index_error(
"The bf-tree entry for the vector is marked as deleted",
));
}
bf_tree::LeafReadResult::InvalidKey => {
return Err(ANNError::log_index_error(
"The bf-tree entry for the vector key is marked as invalid",
));
}
bf_tree::LeafReadResult::NotFound => {
return Err(ANNError::log_index_error(
"The bf-tree entry for the vector key is marked as not found",
));
}
};
Ok(())
}
fn set_neighbors_internal(
&self,
vector_id: I,
neighbors: &[I],
buf: &mut [I],
) -> ANNResult<()> {
#[cfg(test)]
self.num_get_calls.increment();
if buf.len() < self.dim {
return Err(ANNError::log_index_error(
"The provided buffer is not long enough",
));
}
buf[..neighbors.len()].copy_from_slice(neighbors);
buf[self.dim - 1] = len_to_cell(neighbors.len() as u32);
let key = bytemuck::bytes_of(&vector_id);
let value = cast_slice::<I, u8>(&buf[..self.dim]);
self.adjacency_list_index.insert(key, value);
Ok(())
}
pub fn set_neighbors(&self, vector_id: I, neighbors: &[I], buf: &mut [I]) -> ANNResult<()> {
if neighbors.len() > self.dim - 1 {
return Err(ANNError::log_index_error(
"The provided neighbor list is longer than the max degree",
));
};
self.set_neighbors_internal(vector_id, neighbors, buf)
}
pub fn append_vector(
&self,
vector_id: I,
new_neighbor_ids: &[I],
buf: &mut [I],
) -> ANNResult<()> {
let mut neighbor_list = AdjacencyList::with_capacity(self.dim);
self.get_neighbors(vector_id, &mut neighbor_list)?;
let mut new_neighbor_added = false;
for new_neighbor_id in new_neighbor_ids {
if neighbor_list.len() == self.dim - 1 {
break;
}
new_neighbor_added |= neighbor_list.push(*new_neighbor_id);
}
if new_neighbor_added {
self.set_neighbors_internal(vector_id, &neighbor_list, buf)?;
}
Ok(())
}
pub fn delete_vector(&self, vector_id: I) -> ANNResult<()> {
let key = bytemuck::bytes_of(&vector_id);
self.adjacency_list_index.delete(key);
Ok(())
}
pub(crate) fn scratch(&self) -> NeighborAccessor<'_, I> {
NeighborAccessor {
provider: self,
buf: vec![I::zeroed(); self.dim],
}
}
}
pub struct NeighborAccessor<'a, I>
where
I: VectorId,
{
provider: &'a NeighborProvider<I>,
buf: Vec<I>,
}
impl<'a, I> NeighborAccessor<'a, I>
where
I: VectorId,
{
pub fn write_neighbors(&mut self, id: I, neighbors: &[I]) -> ANNResult<()> {
self.provider.set_neighbors(id, neighbors, &mut self.buf)
}
pub fn write_append(&mut self, id: I, neighbors: &[I]) -> ANNResult<()> {
self.provider.append_vector(id, neighbors, &mut self.buf)
}
}
impl<'a, I> HasId for NeighborAccessor<'a, I>
where
I: VectorId,
{
type Id = I;
}
impl<'a, I> provider::NeighborAccessor for NeighborAccessor<'a, I>
where
I: VectorId,
{
fn get_neighbors(
&mut self,
id: Self::Id,
neighbors: &mut AdjacencyList<Self::Id>,
) -> impl std::future::Future<Output = ANNResult<()>> + Send {
std::future::ready(self.provider.get_neighbors(id, neighbors))
}
}
impl<'a, I> provider::NeighborAccessorMut for NeighborAccessor<'a, I>
where
I: VectorId,
{
fn set_neighbors(
&mut self,
id: Self::Id,
neighbors: &[Self::Id],
) -> impl std::future::Future<Output = ANNResult<()>> + Send {
std::future::ready(self.provider.set_neighbors(id, neighbors, &mut self.buf))
}
fn append_vector(
&mut self,
id: Self::Id,
neighbors: &[Self::Id],
) -> impl std::future::Future<Output = ANNResult<()>> + Send {
std::future::ready(self.provider.append_vector(id, neighbors, &mut self.buf))
}
}
const FOUR: usize = std::mem::size_of::<u32>();
fn len_to_cell<I: bytemuck::Pod>(len: u32) -> I {
const { assert!(std::mem::size_of::<I>() >= FOUR) };
let mut cell = I::zeroed();
bytemuck::bytes_of_mut(&mut cell)[..FOUR].copy_from_slice(&len.to_le_bytes());
cell
}
fn cell_to_len<I: bytemuck::Pod>(cell: &I) -> u32 {
const { assert!(std::mem::size_of::<I>() >= FOUR) };
let mut low = [0u8; FOUR];
low.copy_from_slice(&bytemuck::bytes_of(cell)[..FOUR]);
u32::from_le_bytes(low)
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use tokio::task::JoinSet;
use super::*;
#[test]
fn len_cell_round_trip() {
for len in [0u32, 1, 42, u32::MAX] {
assert_eq!(cell_to_len::<u32>(&len_to_cell::<u32>(len)), len);
assert_eq!(cell_to_len::<u64>(&len_to_cell::<u64>(len)), len);
}
}
#[tokio::test]
async fn test_neighbor_accessors() {
let bf_tree_config = Config::default();
let neighbor_provider =
NeighborProvider::<u32>::new_with_config(6, bf_tree_config).unwrap();
let mut scratch = neighbor_provider.scratch();
let adj_list = vec![1, 2, 3];
scratch.write_neighbors(1, &adj_list).unwrap();
let mut result = AdjacencyList::with_capacity(10);
neighbor_provider.get_neighbors(1, &mut result).unwrap();
assert_eq!(&*adj_list, &*result);
let new_neighbors = vec![9, 2, 9];
scratch.write_append(1, &new_neighbors).unwrap();
neighbor_provider.get_neighbors(1, &mut result).unwrap();
let adj_list_new = vec![1, 2, 3, 9];
assert_eq!(&*adj_list_new, &*result);
let new_neighbors = vec![5, 6, 7];
scratch.write_append(1, &new_neighbors).unwrap();
neighbor_provider.get_neighbors(1, &mut result).unwrap();
let adj_list_new = vec![1, 2, 3, 9, 5, 6];
assert_eq!(&*adj_list_new, &*result);
scratch.write_neighbors(1, &[]).unwrap();
neighbor_provider.get_neighbors(1, &mut result).unwrap();
assert!(result.is_empty());
let new_neighbors = vec![3, 4, 5];
scratch.write_append(1, &new_neighbors).unwrap();
neighbor_provider.get_neighbors(1, &mut result).unwrap();
assert_eq!(&*new_neighbors, &*result);
neighbor_provider.delete_vector(1).unwrap();
assert!(neighbor_provider.get_neighbors(1, &mut result).is_err());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn test_parallel_tree_traversal() {
let bf_tree_config = Config::default();
let neighbor_provider =
Arc::new(NeighborProvider::<u32>::new_with_config(120, bf_tree_config).unwrap());
let mut set = JoinSet::new();
for i in 0..100 {
let neighbor_list = vec![i as u32, (i + 1) as u32, (i + 2) as u32];
let neighbor_provider_clone = Arc::clone(&neighbor_provider);
set.spawn(async move {
let mut scratch = neighbor_provider_clone.scratch();
scratch.write_neighbors(i as u32, &neighbor_list).unwrap();
});
}
while let Some(res) = set.join_next().await {
res.unwrap();
}
let mut result = AdjacencyList::with_capacity(121);
for i in 0..100 {
neighbor_provider
.get_neighbors(i as u32, &mut result)
.unwrap();
let neighbor_list = vec![i as u32, (i + 1) as u32, (i + 2) as u32];
assert_eq!(&*neighbor_list, &*result);
}
}
#[tokio::test]
async fn test_snapshot() {
let temp_dir = tempfile::tempdir().unwrap();
let snapshot_path = temp_dir.path().join("test_neighbor_snapshot.bftree");
let mut bf_tree_config = Config::new(&snapshot_path, 16384 * 16);
bf_tree_config.storage_backend(bf_tree::StorageBackend::Std);
let neighbor_provider =
NeighborProvider::<u32>::new_with_config(6, bf_tree_config).unwrap();
let mut scratch = neighbor_provider.scratch();
scratch.write_neighbors(1, &[2, 3, 4]).unwrap();
scratch.write_neighbors(2, &[1, 3, 5]).unwrap();
neighbor_provider.adjacency_list_index.snapshot();
let mut result = AdjacencyList::with_capacity(10);
neighbor_provider.get_neighbors(1, &mut result).unwrap();
assert_eq!(&[2, 3, 4], &*result);
neighbor_provider.get_neighbors(2, &mut result).unwrap();
assert_eq!(&[1, 3, 5], &*result);
}
#[tokio::test]
async fn test_max_degree() {
let bf_tree_config = Config::default();
let neighbor_provider =
NeighborProvider::<u32>::new_with_config(6, bf_tree_config.clone()).unwrap();
assert_eq!(neighbor_provider.max_degree(), 6);
let neighbor_provider =
NeighborProvider::<u32>::new_with_config(120, bf_tree_config.clone()).unwrap();
assert_eq!(neighbor_provider.max_degree(), 120);
let neighbor_provider =
NeighborProvider::<u32>::new_with_config(1, bf_tree_config).unwrap();
assert_eq!(neighbor_provider.max_degree(), 1);
}
#[tokio::test]
async fn test_new_from_bftree() {
let bftree = BfTree::with_config(Config::default(), None).expect("Failed to create BfTree");
let neighbor_provider = NeighborProvider::<u32>::new_from_bftree(10, bftree).unwrap();
assert_eq!(neighbor_provider.max_degree(), 10);
let mut scratch = neighbor_provider.scratch();
scratch.write_neighbors(1, &[2, 3]).unwrap();
let mut result = AdjacencyList::with_capacity(11);
neighbor_provider.get_neighbors(1, &mut result).unwrap();
assert_eq!(&[2, 3], &*result);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn test_parallel_neighbor_access() {
let bf_tree_config = Config::default();
let neighbor_provider =
Arc::new(NeighborProvider::<u32>::new_with_config(120, bf_tree_config).unwrap());
let mut set = JoinSet::new();
for _ in 0..5 {
let neighbor_provider_clone = Arc::clone(&neighbor_provider);
set.spawn(async move {
let mut scratch = neighbor_provider_clone.scratch();
for i in 0..5 {
scratch.write_neighbors(i as u32, &[1, 2, 3, 4, 5]).unwrap();
}
let mut result = AdjacencyList::with_capacity(121);
for i in 0..5 {
neighbor_provider_clone
.get_neighbors(i as u32, &mut result)
.unwrap();
assert_eq!(&[1, 2, 3, 4, 5], &*result);
}
});
}
while let Some(res) = set.join_next().await {
res.unwrap();
}
}
}