1pub mod codecs;
15
16use crate::error::{Result, RuvectorError};
17use crate::types::DistanceMetric;
18use serde::{Deserialize, Serialize};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24pub enum CodecKind {
25 Fp32,
27 Fp16,
29 Int8,
31 Turbo4,
33 Pq,
35 RaBitQ1,
37}
38
39impl CodecKind {
40 pub fn encoded_len(&self, dim: usize) -> Option<usize> {
43 match self {
44 CodecKind::Fp32 => Some(dim * 4),
45 CodecKind::Fp16 => Some(dim * 2),
46 CodecKind::Int8 => Some(dim + 8),
47 CodecKind::Turbo4 => Some(dim / 2 + 8),
48 CodecKind::Pq | CodecKind::RaBitQ1 => None,
49 }
50 }
51}
52
53pub trait VectorCodec: Send + Sync {
56 fn kind(&self) -> CodecKind;
58 fn dim(&self) -> usize;
60 fn encoded_len(&self) -> usize;
62 fn encode(&self, v: &[f32]) -> Result<Vec<u8>>;
64 fn decode(&self, blob: &[u8]) -> Result<Vec<f32>>;
66 fn distance(&self, a: &[u8], b: &[u8]) -> Result<f32>;
68 fn make_query(&self, q: &[f32]) -> Result<Box<dyn EncodedQuery>>;
70}
71
72pub trait EncodedQuery: Send + Sync {
74 fn distance_to(&self, blob: &[u8]) -> f32;
78}
79
80pub fn codec_for(
83 kind: CodecKind,
84 dim: usize,
85 metric: DistanceMetric,
86 rotation_seed: u64,
87) -> Result<Box<dyn VectorCodec>> {
88 match kind {
89 CodecKind::Fp32 => Ok(Box::new(codecs::Fp32Codec::new(dim, metric))),
90 CodecKind::Fp16 => Ok(Box::new(codecs::Fp16Codec::new(dim, metric))),
91 CodecKind::Int8 => Ok(Box::new(codecs::Int8Codec::new(dim, metric))),
92 CodecKind::Turbo4 => Ok(Box::new(codecs::Turbo4PlaneCodec::new(
93 dim,
94 metric,
95 rotation_seed,
96 )?)),
97 CodecKind::Pq | CodecKind::RaBitQ1 => Err(RuvectorError::InvalidParameter(format!(
98 "{kind:?} codec is provided by its own crate and not yet wired \
99 into the core plane (ADR-297 phase C)"
100 ))),
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
108pub struct VectorProvenance {
109 pub model_id: Option<String>,
111 pub codec: CodecKind,
113 pub codec_version: u16,
116 pub rotation_seed: Option<u64>,
118 pub dim: usize,
120 pub metric: DistanceMetric,
122 pub source_hash: Option<String>,
124 pub lineage: Vec<String>,
126}
127
128pub const CODEC_VERSION: u16 = 1;
130
131impl VectorProvenance {
132 pub fn new(codec: &dyn VectorCodec, metric: DistanceMetric) -> Self {
134 Self {
135 model_id: None,
136 codec: codec.kind(),
137 codec_version: CODEC_VERSION,
138 rotation_seed: None,
139 dim: codec.dim(),
140 metric,
141 source_hash: None,
142 lineage: Vec::new(),
143 }
144 }
145
146 pub fn push_migration(&mut self, entry: impl Into<String>) {
148 self.lineage.push(entry.into());
149 }
150}
151
152pub(crate) fn metric_distance(metric: DistanceMetric, a: &[f32], b: &[f32]) -> f32 {
156 use crate::simd_intrinsics as si;
157 match metric {
158 DistanceMetric::Euclidean => si::euclidean_distance_simd(a, b),
159 DistanceMetric::Cosine => (1.0_f32 - si::cosine_similarity_simd(a, b)).max(0.0),
160 DistanceMetric::DotProduct => (-si::dot_product_simd(a, b)).max(0.0),
161 DistanceMetric::Manhattan => si::manhattan_distance_simd(a, b),
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn encoded_len_matches_kind_table() {
171 for (kind, dim, expect) in [
172 (CodecKind::Fp32, 128, 512),
173 (CodecKind::Fp16, 128, 256),
174 (CodecKind::Int8, 128, 136),
175 (CodecKind::Turbo4, 128, 72),
176 ] {
177 assert_eq!(kind.encoded_len(dim), Some(expect));
178 let codec = codec_for(kind, dim, DistanceMetric::Euclidean, 42).unwrap();
179 assert_eq!(codec.encoded_len(), expect);
180 assert_eq!(codec.kind(), kind);
181 }
182 assert_eq!(CodecKind::Pq.encoded_len(128), None);
183 assert!(codec_for(CodecKind::Pq, 128, DistanceMetric::Euclidean, 42).is_err());
184 }
185
186 #[test]
187 fn provenance_roundtrips_serde() {
188 let codec = codec_for(CodecKind::Turbo4, 64, DistanceMetric::Cosine, 42).unwrap();
189 let mut p = VectorProvenance::new(codec.as_ref(), DistanceMetric::Cosine);
190 p.rotation_seed = Some(42);
191 p.model_id = Some("test-model".into());
192 p.push_migration("fp32→turbo4@2026-08-06");
193 let json = serde_json::to_string(&p).unwrap();
194 let back: VectorProvenance = serde_json::from_str(&json).unwrap();
195 assert_eq!(p, back);
196 assert_eq!(back.lineage.len(), 1);
197 }
198}