use embeddenator_vsa::{ReversibleVSAConfig, SparseVec};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KernelInteropError {
MissingVector { id: usize },
}
impl fmt::Display for KernelInteropError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KernelInteropError::MissingVector { id } => {
write!(f, "missing vector for id {id}")
}
}
}
}
impl std::error::Error for KernelInteropError {}
pub trait VsaBackend {
type Vector: Clone + Send + Sync + 'static;
fn zero(&self) -> Self::Vector;
fn bundle(&self, a: &Self::Vector, b: &Self::Vector) -> Self::Vector;
fn bind(&self, a: &Self::Vector, b: &Self::Vector) -> Self::Vector;
fn cosine(&self, a: &Self::Vector, b: &Self::Vector) -> f64;
fn encode_data(
&self,
data: &[u8],
config: &ReversibleVSAConfig,
path: Option<&str>,
) -> Self::Vector;
fn decode_data(
&self,
vec: &Self::Vector,
config: &ReversibleVSAConfig,
path: Option<&str>,
expected_size: usize,
) -> Vec<u8>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SparseVecBackend;
impl VsaBackend for SparseVecBackend {
type Vector = SparseVec;
fn zero(&self) -> Self::Vector {
SparseVec::new()
}
fn bundle(&self, a: &Self::Vector, b: &Self::Vector) -> Self::Vector {
a.bundle(b)
}
fn bind(&self, a: &Self::Vector, b: &Self::Vector) -> Self::Vector {
a.bind(b)
}
fn cosine(&self, a: &Self::Vector, b: &Self::Vector) -> f64 {
a.cosine(b)
}
fn encode_data(
&self,
data: &[u8],
config: &ReversibleVSAConfig,
path: Option<&str>,
) -> Self::Vector {
SparseVec::encode_data(data, config, path)
}
fn decode_data(
&self,
vec: &Self::Vector,
config: &ReversibleVSAConfig,
path: Option<&str>,
expected_size: usize,
) -> Vec<u8> {
vec.decode_data(config, path, expected_size)
}
}
pub trait VectorStore<V> {
fn get(&self, id: usize) -> Option<&V>;
}
impl VectorStore<SparseVec> for HashMap<usize, SparseVec> {
fn get(&self, id: usize) -> Option<&SparseVec> {
self.get(&id)
}
}
pub trait CandidateGenerator<V> {
type Candidate;
fn candidates(&self, query: &V, k: usize) -> Vec<Self::Candidate>;
}
pub fn rerank_top_k_by_cosine<B, S>(
backend: &B,
store: &S,
query: &B::Vector,
candidate_ids: impl IntoIterator<Item = usize>,
k: usize,
) -> Result<Vec<(usize, f64)>, KernelInteropError>
where
B: VsaBackend,
S: VectorStore<B::Vector>,
{
if k == 0 {
return Ok(Vec::new());
}
let mut scored = Vec::new();
for id in candidate_ids {
let vec = store
.get(id)
.ok_or(KernelInteropError::MissingVector { id })?;
scored.push((id, backend.cosine(query, vec)));
}
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
scored.truncate(k);
Ok(scored)
}