use std::{
collections::HashSet,
fmt::{self, Debug},
future::Future,
};
use diskann_utils::{
future::SendFuture,
views::rowmajor::{self, Matrix},
};
use diskann_vector::{PreprocessedDistanceFunction, distance::Metric};
use thiserror::Error;
use crate::{
always_escalate, convert_error,
error::{RankedError, ToRanked, TransientError},
flat::DistancesUnordered,
graph::test::synthetic::Grid,
internal::counter::{Counter, LocalCounter},
provider::{self, ExecutionContext, HasId, NoopGuard},
utils::VectorRepr,
};
#[derive(Debug, Error)]
pub enum ProviderError {
#[error("flat::test::Provider needs at least one item")]
Empty,
#[error("flat::test::Provider items must have non-zero dimension")]
ZeroDimension,
}
convert_error!(ProviderError);
#[derive(Debug)]
pub struct Provider {
items: rowmajor::Owned<f32>,
get_element: Counter,
}
impl Provider {
pub fn new(items: rowmajor::Owned<f32>) -> Result<Self, ProviderError> {
if items.nrows() == 0 {
return Err(ProviderError::Empty);
}
if items.ncols() == 0 {
return Err(ProviderError::ZeroDimension);
}
Ok(Self {
items,
get_element: Counter::new(),
})
}
pub fn grid(grid: Grid, size: usize) -> Result<Self, ProviderError> {
Self::new(grid.data(size))
}
pub fn len(&self) -> usize {
self.items.nrows()
}
pub fn dim(&self) -> usize {
self.items.ncols()
}
pub fn metrics(&self) -> ElementCounter {
ElementCounter {
count: self.get_element.value(),
}
}
pub fn items(&self) -> &rowmajor::Owned<f32> {
&self.items
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
pub struct ElementCounter {
pub count: usize,
}
#[cfg(test)]
crate::test::cmp::verbose_eq!(ElementCounter { count });
#[derive(Debug, Clone, Default)]
pub struct Context;
impl ExecutionContext for Context {
fn wrap_spawn<F, T>(&self, f: F) -> impl Future<Output = T> + Send + 'static
where
F: Future<Output = T> + Send + 'static,
{
f
}
}
#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
#[error("flat::test::Provider has no id {0}")]
pub struct InvalidId(pub u32);
always_escalate!(InvalidId);
convert_error!(InvalidId);
#[must_use]
#[derive(Debug)]
pub struct TransientGetError {
id: u32,
handled: bool,
}
impl TransientGetError {
fn new(id: u32) -> Self {
Self { id, handled: false }
}
}
impl fmt::Display for TransientGetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "transient failure retrieving id {}", self.id)
}
}
impl std::error::Error for TransientGetError {}
impl Drop for TransientGetError {
fn drop(&mut self) {
assert!(
self.handled,
"dropped an unhandled TransientGetError for id {}",
self.id,
);
}
}
impl TransientError<InvalidId> for TransientGetError {
fn acknowledge<D>(mut self, _why: D)
where
D: fmt::Display,
{
self.handled = true;
}
fn escalate<D>(mut self, _why: D) -> InvalidId
where
D: fmt::Display,
{
self.handled = true;
InvalidId(self.id)
}
}
#[derive(Debug)]
pub enum AccessError {
InvalidId(InvalidId),
Transient(TransientGetError),
}
impl fmt::Display for AccessError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidId(e) => fmt::Display::fmt(e, f),
Self::Transient(e) => fmt::Display::fmt(e, f),
}
}
}
impl std::error::Error for AccessError {}
impl ToRanked for AccessError {
type Transient = TransientGetError;
type Error = InvalidId;
fn to_ranked(self) -> RankedError<TransientGetError, InvalidId> {
match self {
Self::InvalidId(e) => RankedError::Error(e),
Self::Transient(e) => RankedError::Transient(e),
}
}
fn from_transient(transient: TransientGetError) -> Self {
Self::Transient(transient)
}
fn from_error(error: InvalidId) -> Self {
Self::InvalidId(error)
}
}
impl provider::DataProvider for Provider {
type Context = Context;
type InternalId = u32;
type ExternalId = u32;
type Error = InvalidId;
type Guard = NoopGuard<u32>;
fn to_internal_id(&self, _ctx: &Context, gid: &u32) -> Result<u32, InvalidId> {
if (*gid as usize) < self.items.nrows() {
Ok(*gid)
} else {
Err(InvalidId(*gid))
}
}
fn to_external_id(&self, _ctx: &Context, id: u32) -> Result<u32, InvalidId> {
if (id as usize) < self.items.nrows() {
Ok(id)
} else {
Err(InvalidId(id))
}
}
}
pub struct Visitor<'a> {
provider: &'a Provider,
transient_ids: Option<HashSet<u32>>,
get_element: LocalCounter<'a>,
computer: <f32 as VectorRepr>::QueryDistance,
}
impl<'a> Visitor<'a> {
pub fn new(provider: &'a Provider, query: &[f32]) -> Result<Self, VisitorError> {
let computer = Self::query_computer(provider, query)?;
Ok(Self {
provider,
transient_ids: None,
get_element: provider.get_element.local(),
computer,
})
}
pub fn flaky(
provider: &'a Provider,
query: &[f32],
transient_ids: impl IntoIterator<Item = u32>,
) -> Result<Self, VisitorError> {
let computer = Self::query_computer(provider, query)?;
Ok(Self {
provider,
transient_ids: Some(transient_ids.into_iter().collect()),
get_element: provider.get_element.local(),
computer,
})
}
fn query_computer(
provider: &Provider,
query: &[f32],
) -> Result<<f32 as VectorRepr>::QueryDistance, VisitorError> {
if query.len() != provider.dim() {
return Err(VisitorError {
expected: provider.dim(),
actual: query.len(),
});
}
Ok(f32::query_distance(query, Metric::L2))
}
}
impl Debug for Visitor<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Visitor")
.field("provider", &self.provider)
.field("transient_ids", &self.transient_ids)
.finish_non_exhaustive()
}
}
impl HasId for Visitor<'_> {
type Id = u32;
}
impl DistancesUnordered for Visitor<'_> {
type Error = AccessError;
fn distances_unordered<F>(&mut self, mut f: F) -> impl SendFuture<Result<(), Self::Error>>
where
F: Send + FnMut(Self::Id, f32),
{
async move {
for (i, vector) in self.provider.items.rows().enumerate() {
let id = i as u32;
if let Some(ids) = &self.transient_ids
&& ids.contains(&id)
{
return Err(AccessError::Transient(TransientGetError::new(id)));
}
self.get_element.increment();
let dist = self.computer.evaluate_similarity(vector);
f(id, dist);
}
Ok(())
}
}
}
#[derive(Debug, Clone, Error)]
#[error("dimension mismatch: provider expects {expected}, got {actual}")]
pub struct VisitorError {
pub expected: usize,
pub actual: usize,
}
convert_error!(VisitorError);