use ipfrs_core::{Error, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScalarQuantizerConfig {
pub bits: u8,
pub signed: bool,
pub min_values: Vec<f32>,
pub max_values: Vec<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScalarQuantizer {
config: ScalarQuantizerConfig,
dimension: usize,
trained: bool,
}
impl ScalarQuantizer {
pub fn new(dimension: usize, signed: bool) -> Self {
Self {
config: ScalarQuantizerConfig {
bits: 8,
signed,
min_values: vec![f32::MAX; dimension],
max_values: vec![f32::MIN; dimension],
},
dimension,
trained: false,
}
}
pub fn uint8(dimension: usize) -> Self {
Self::new(dimension, false)
}
pub fn int8(dimension: usize) -> Self {
Self::new(dimension, true)
}
pub fn train(&mut self, vectors: &[Vec<f32>]) -> Result<()> {
if vectors.is_empty() {
return Err(Error::InvalidInput(
"Cannot train on empty vector set".to_string(),
));
}
for (i, vec) in vectors.iter().enumerate() {
if vec.len() != self.dimension {
return Err(Error::InvalidInput(format!(
"Vector {} has dimension {}, expected {}",
i,
vec.len(),
self.dimension
)));
}
}
self.config.min_values = vec![f32::MAX; self.dimension];
self.config.max_values = vec![f32::MIN; self.dimension];
for vec in vectors {
for (i, &val) in vec.iter().enumerate() {
if val < self.config.min_values[i] {
self.config.min_values[i] = val;
}
if val > self.config.max_values[i] {
self.config.max_values[i] = val;
}
}
}
for i in 0..self.dimension {
let range = self.config.max_values[i] - self.config.min_values[i];
if range < 1e-6 {
self.config.min_values[i] -= 0.5;
self.config.max_values[i] += 0.5;
} else {
let margin = range * 0.01;
self.config.min_values[i] -= margin;
self.config.max_values[i] += margin;
}
}
self.trained = true;
Ok(())
}
pub fn train_incremental(&mut self, vector: &[f32]) -> Result<()> {
if vector.len() != self.dimension {
return Err(Error::InvalidInput(format!(
"Vector has dimension {}, expected {}",
vector.len(),
self.dimension
)));
}
for (i, &val) in vector.iter().enumerate() {
if val < self.config.min_values[i] {
self.config.min_values[i] = val;
}
if val > self.config.max_values[i] {
self.config.max_values[i] = val;
}
}
self.trained = true;
Ok(())
}
pub fn is_trained(&self) -> bool {
self.trained
}
pub fn quantize(&self, vector: &[f32]) -> Result<QuantizedVector> {
if !self.trained {
return Err(Error::InvalidInput(
"Quantizer must be trained before use".to_string(),
));
}
if vector.len() != self.dimension {
return Err(Error::InvalidInput(format!(
"Vector has dimension {}, expected {}",
vector.len(),
self.dimension
)));
}
let mut quantized = Vec::with_capacity(self.dimension);
for (i, &val) in vector.iter().enumerate() {
let min = self.config.min_values[i];
let max = self.config.max_values[i];
let range = max - min;
let normalized = if range > 1e-6 {
((val - min) / range).clamp(0.0, 1.0)
} else {
0.5
};
let q = if self.config.signed {
((normalized * 255.0 - 128.0).round() as i8) as u8
} else {
(normalized * 255.0).round() as u8
};
quantized.push(q);
}
Ok(QuantizedVector {
data: quantized,
signed: self.config.signed,
})
}
pub fn dequantize(&self, quantized: &QuantizedVector) -> Result<Vec<f32>> {
if !self.trained {
return Err(Error::InvalidInput(
"Quantizer must be trained before use".to_string(),
));
}
if quantized.data.len() != self.dimension {
return Err(Error::InvalidInput(format!(
"Quantized vector has dimension {}, expected {}",
quantized.data.len(),
self.dimension
)));
}
let mut result = Vec::with_capacity(self.dimension);
for (i, &q) in quantized.data.iter().enumerate() {
let min = self.config.min_values[i];
let max = self.config.max_values[i];
let range = max - min;
let normalized = if self.config.signed {
((q as i8) as f32 + 128.0) / 255.0
} else {
q as f32 / 255.0
};
let val = min + normalized * range;
result.push(val);
}
Ok(result)
}
pub fn distance_l2_quantized(&self, a: &QuantizedVector, b: &QuantizedVector) -> Result<f32> {
if a.data.len() != b.data.len() {
return Err(Error::InvalidInput(
"Vectors must have same dimension".to_string(),
));
}
let mut sum_sq: i64 = 0;
for (qa, qb) in a.data.iter().zip(b.data.iter()) {
let diff = if a.signed {
(*qa as i8 as i64) - (*qb as i8 as i64)
} else {
(*qa as i64) - (*qb as i64)
};
sum_sq += diff * diff;
}
Ok((sum_sq as f32).sqrt() / 255.0)
}
pub fn dot_product_quantized(&self, a: &QuantizedVector, b: &QuantizedVector) -> Result<f32> {
if a.data.len() != b.data.len() {
return Err(Error::InvalidInput(
"Vectors must have same dimension".to_string(),
));
}
let mut sum: i64 = 0;
for (qa, qb) in a.data.iter().zip(b.data.iter()) {
if a.signed {
sum += (*qa as i8 as i64) * (*qb as i8 as i64);
} else {
sum += (*qa as i64) * (*qb as i64);
}
}
Ok(sum as f32 / (255.0 * 255.0))
}
pub fn dimension(&self) -> usize {
self.dimension
}
pub fn compression_ratio(&self) -> f32 {
4.0 }
pub fn memory_estimate(&self, num_vectors: usize) -> usize {
num_vectors * self.dimension + 2 * self.dimension * 4
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuantizedVector {
pub data: Vec<u8>,
pub signed: bool,
}
impl QuantizedVector {
pub fn new(data: Vec<u8>, signed: bool) -> Self {
Self { data, signed }
}
pub fn dimension(&self) -> usize {
self.data.len()
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn size_bytes(&self) -> usize {
self.data.len()
}
}