use serde::{Deserialize, Serialize};
use super::error::StorageError;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DenseVector {
F32(Vec<f32>),
F64(Vec<f64>),
I16(Vec<i16>),
I32(Vec<i32>),
I64(Vec<i64>),
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum VectorCoercionPolicy {
Exact,
Lossless,
Lossy,
}
impl DenseVector {
pub fn len(&self) -> usize {
match self {
Self::F32(v) => v.len(),
Self::F64(v) => v.len(),
Self::I16(v) => v.len(),
Self::I32(v) => v.len(),
Self::I64(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_f32(&self, policy: VectorCoercionPolicy) -> Result<Vec<f32>, StorageError> {
match self {
Self::F32(v) => Ok(v.clone()),
Self::F64(v) => Self::convert_f64_to_f32(v, policy),
Self::I16(v) => Self::convert_i16_to_f32(v, policy),
Self::I32(v) => Self::convert_i32_to_f32(v, policy),
Self::I64(v) => Self::convert_i64_to_f32(v, policy),
}
}
fn conversion_rejected(source: &'static str, policy: VectorCoercionPolicy) -> StorageError {
StorageError::InvalidRequest(format!(
"cannot convert {source} dense vector to f32 under {policy:?} policy"
))
}
fn value_rejected(
source: &'static str,
index: usize,
value: impl std::fmt::Display,
policy: VectorCoercionPolicy,
) -> StorageError {
StorageError::InvalidRequest(format!(
"cannot convert {source} dense vector value at index {index} (`{value}`) to f32 without precision loss under {policy:?} policy"
))
}
fn convert_f64_to_f32(
values: &[f64],
policy: VectorCoercionPolicy,
) -> Result<Vec<f32>, StorageError> {
if !policy.allows_representation_change() {
return Err(Self::conversion_rejected("f64", policy));
}
values
.iter()
.enumerate()
.map(|(index, value)| {
let converted = *value as f32;
if policy.allows_lossy()
|| (converted.is_finite() && f64::from(converted) == *value)
{
Ok(converted)
} else {
Err(Self::value_rejected("f64", index, value, policy))
}
})
.collect()
}
fn convert_i16_to_f32(
values: &[i16],
policy: VectorCoercionPolicy,
) -> Result<Vec<f32>, StorageError> {
if !policy.allows_representation_change() {
return Err(Self::conversion_rejected("i16", policy));
}
Ok(values.iter().map(|value| f32::from(*value)).collect())
}
fn convert_i32_to_f32(
values: &[i32],
policy: VectorCoercionPolicy,
) -> Result<Vec<f32>, StorageError> {
if !policy.allows_representation_change() {
return Err(Self::conversion_rejected("i32", policy));
}
values
.iter()
.enumerate()
.map(|(index, value)| {
let converted = *value as f32;
if policy.allows_lossy() || converted as i128 == i128::from(*value) {
Ok(converted)
} else {
Err(Self::value_rejected("i32", index, value, policy))
}
})
.collect()
}
fn convert_i64_to_f32(
values: &[i64],
policy: VectorCoercionPolicy,
) -> Result<Vec<f32>, StorageError> {
if !policy.allows_representation_change() {
return Err(Self::conversion_rejected("i64", policy));
}
values
.iter()
.enumerate()
.map(|(index, value)| {
let converted = *value as f32;
if policy.allows_lossy() || converted as i128 == i128::from(*value) {
Ok(converted)
} else {
Err(Self::value_rejected("i64", index, value, policy))
}
})
.collect()
}
}
impl VectorCoercionPolicy {
fn allows_representation_change(self) -> bool {
matches!(self, Self::Lossless | Self::Lossy)
}
fn allows_lossy(self) -> bool {
matches!(self, Self::Lossy)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum DistanceMetric {
L2,
Cosine,
Hamming,
}