use std::fmt::Debug;
use diskann_utils::future::SendFuture;
use diskann_vector::PreprocessedDistanceFunction;
use crate::{
error::{StandardError, ToRanked},
provider::DataProvider,
};
pub trait DistancesUnordered<C>: Send + Sync
where
C: for<'a> PreprocessedDistanceFunction<Self::ElementRef<'a>, f32>,
{
type ElementRef<'a>;
type Id;
type Error: ToRanked + Debug + Send + Sync + 'static;
fn distances_unordered<F>(
&mut self,
computer: &C,
f: F,
) -> impl SendFuture<Result<(), Self::Error>>
where
F: Send + FnMut(Self::Id, f32);
}
pub trait SearchStrategy<P, T>: Send + Sync
where
P: DataProvider,
{
type ElementRef<'a>;
type Id;
type QueryComputer: for<'a> PreprocessedDistanceFunction<Self::ElementRef<'a>, f32>
+ Send
+ Sync
+ 'static;
type QueryComputerError: StandardError;
type Visitor<'a>: for<'b> DistancesUnordered<
Self::QueryComputer,
ElementRef<'b> = Self::ElementRef<'b>,
Id = Self::Id,
>
where
Self: 'a,
P: 'a;
type Error: StandardError;
fn create_visitor<'a>(
&'a self,
provider: &'a P,
context: &'a P::Context,
) -> Result<Self::Visitor<'a>, Self::Error>;
fn build_query_computer(
&self,
query: T,
) -> Result<Self::QueryComputer, Self::QueryComputerError>;
}
#[cfg(test)]
mod tests {
use std::marker::PhantomData;
use diskann_utils::future::SendFuture;
use diskann_vector::{PreprocessedDistanceFunction, distance::Metric};
use super::*;
use crate::{ANNError, always_escalate, error::Infallible, utils::VectorRepr};
fn sample_items() -> Vec<(u32, Vec<f32>)> {
vec![
(10, vec![0.0, 0.0]),
(11, vec![1.0, 0.0]),
(12, vec![0.0, 2.0]),
]
}
struct Scanner {
items: Vec<(u32, Vec<f32>)>,
}
impl DistancesUnordered<<f32 as VectorRepr>::QueryDistance> for Scanner {
type ElementRef<'a> = &'a [f32];
type Id = u32;
type Error = Infallible;
fn distances_unordered<F>(
&mut self,
computer: &<f32 as VectorRepr>::QueryDistance,
mut f: F,
) -> impl SendFuture<Result<(), Self::Error>>
where
F: Send + FnMut(Self::Id, f32),
{
async move {
for (id, v) in &self.items {
let dist = computer.evaluate_similarity(v.as_slice());
f(*id, dist);
}
Ok(())
}
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn distances_unordered_scanner() {
let query = vec![0.5_f32, 0.9];
let computer = f32::query_distance(&query, Metric::L2);
let expected: Vec<(u32, f32)> = sample_items()
.into_iter()
.map(|(id, v)| (id, computer.evaluate_similarity(v.as_slice())))
.collect();
let mut scanner = Scanner {
items: sample_items(),
};
let mut seen: Vec<(u32, f32)> = Vec::new();
scanner
.distances_unordered(&computer, |id, d| seen.push((id, d)))
.await
.unwrap();
assert_eq!(seen, expected);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("synthetic scan failure at id {0}")]
struct Boom(u32);
always_escalate!(Boom);
impl From<Boom> for ANNError {
#[track_caller]
fn from(boom: Boom) -> ANNError {
ANNError::opaque(boom)
}
}
struct Failing {
items: Vec<(u32, Vec<f32>)>,
fail_after: usize,
}
impl DistancesUnordered<<f32 as VectorRepr>::QueryDistance> for Failing {
type ElementRef<'a> = &'a [f32];
type Id = u32;
type Error = Boom;
fn distances_unordered<F>(
&mut self,
computer: &<f32 as VectorRepr>::QueryDistance,
mut f: F,
) -> impl SendFuture<Result<(), Self::Error>>
where
F: Send + FnMut(Self::Id, f32),
{
async move {
for (i, (id, v)) in self.items.iter().enumerate() {
if i == self.fail_after {
return Err(Boom(*id));
}
let dist = computer.evaluate_similarity(v.as_slice());
f(*id, dist);
}
Ok(())
}
}
}
#[tokio::test]
async fn failures_midstream() {
let mut scanner = Failing {
items: sample_items(),
fail_after: 1, };
let query = vec![0.0_f32, 0.0];
let computer = f32::query_distance(&query, Metric::L2);
let mut seen: Vec<u32> = Vec::new();
let err = scanner
.distances_unordered(&computer, |id, _d| seen.push(id))
.await
.expect_err("Failing scanner must surface its error");
assert_eq!(err, Boom(11));
assert_eq!(
seen,
vec![10],
"the closure must only see items yielded before the failure",
);
}
struct View<'a> {
ptr: *const f32,
len: usize,
_phantom: PhantomData<&'a [f32]>,
}
unsafe impl Send for View<'_> {}
unsafe impl Sync for View<'_> {}
struct ViewComputer {
query: Vec<f32>,
}
impl<'a> PreprocessedDistanceFunction<View<'a>, f32> for ViewComputer {
fn evaluate_similarity(&self, v: View<'a>) -> f32 {
let s = unsafe { std::slice::from_raw_parts(v.ptr, v.len) };
s.iter().zip(&self.query).map(|(a, b)| a * b).sum()
}
}
struct ViewScanner {
rows: Vec<(u32, Vec<f32>)>,
}
impl ViewScanner {
fn iter<'a>(&self) -> impl Iterator<Item = (u32, View<'a>)> {
self.rows.iter().map(|(x, y)| {
(
*x,
View {
ptr: y.as_ptr(),
len: y.len(),
_phantom: PhantomData,
},
)
})
}
}
impl DistancesUnordered<ViewComputer> for ViewScanner {
type ElementRef<'a> = View<'a>;
type Id = u32;
type Error = Infallible;
fn distances_unordered<F>(
&mut self,
computer: &ViewComputer,
mut f: F,
) -> impl SendFuture<Result<(), Self::Error>>
where
F: Send + FnMut(Self::Id, f32),
{
async move {
for (id, v) in self.iter() {
f(id, computer.evaluate_similarity(v));
}
Ok(())
}
}
}
#[tokio::test]
async fn distances_unordered_lifetime_carrying_element_ref() {
let mut scanner = ViewScanner {
rows: vec![
(10, vec![1.0, 0.0]),
(11, vec![0.5, 0.5]),
(12, vec![0.0, 2.0]),
],
};
let computer = ViewComputer {
query: vec![1.0, 3.0],
};
let expected: Vec<(u32, f32)> = vec![
(10, 1.0 * 1.0 + 0.0 * 3.0),
(11, 0.5 * 1.0 + 0.5 * 3.0),
(12, 0.0 * 1.0 + 2.0 * 3.0),
];
let mut seen: Vec<(u32, f32)> = Vec::new();
scanner
.distances_unordered(&computer, |id, d| seen.push((id, d)))
.await
.unwrap();
assert_eq!(seen, expected);
}
}