1use serde::{Deserialize, Serialize};
2
3use crate::{Error, Result};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(into = "usize", try_from = "usize")]
8pub struct Dimension(usize);
9
10impl Dimension {
11 pub const D128: Self = Self(128);
12 pub const D256: Self = Self(256);
13 pub const D512: Self = Self(512);
14 pub const D768: Self = Self(768);
15 pub const ALL: [Self; 4] = [Self::D128, Self::D256, Self::D512, Self::D768];
17 pub fn get(self) -> usize {
18 self.0
19 }
20}
21
22impl Default for Dimension {
24 fn default() -> Self {
25 Self::D256
26 }
27}
28
29impl From<Dimension> for usize {
30 fn from(d: Dimension) -> usize {
31 d.get()
32 }
33}
34
35impl TryFrom<usize> for Dimension {
36 type Error = Error;
37
38 fn try_from(value: usize) -> Result<Self> {
39 if value == 0 {
40 Err(Error::Config("embedding dimension must be nonzero".into()))
41 } else {
42 Ok(Self(value))
43 }
44 }
45}
46
47impl std::fmt::Display for Dimension {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{}", self.get())
50 }
51}
52
53impl std::str::FromStr for Dimension {
54 type Err = Error;
55
56 fn from_str(s: &str) -> Result<Self> {
57 let n: usize = s
58 .parse()
59 .map_err(|_| Error::Config(format!("invalid dimension `{s}`")))?;
60 Dimension::try_from(n)
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct Embedding(Vec<f32>);
73
74impl Embedding {
75 pub fn from_raw(mut raw: Vec<f32>, dim: Dimension) -> Result<Self> {
80 let d = dim.get();
81 if raw.len() < d {
82 return Err(Error::DimensionMismatch {
83 expected: d,
84 actual: raw.len(),
85 });
86 }
87 raw.truncate(d);
88 l2_normalize(&mut raw);
89 if raw.iter().any(|x| !x.is_finite()) {
90 return Err(Error::Embedding(
91 "model produced a non-finite embedding".into(),
92 ));
93 }
94 Ok(Self(raw))
95 }
96
97 pub fn from_preserved(values: Vec<f32>) -> Result<Self> {
100 if values.is_empty() {
101 return Err(Error::Embedding(
102 "cannot preserve an empty embedding".into(),
103 ));
104 }
105 if values.iter().any(|value| !value.is_finite()) {
106 return Err(Error::Embedding(
107 "cannot preserve a non-finite embedding".into(),
108 ));
109 }
110 Ok(Self(values))
111 }
112
113 pub fn as_slice(&self) -> &[f32] {
114 &self.0
115 }
116
117 pub fn dim(&self) -> usize {
118 self.0.len()
119 }
120
121 pub fn dimension(&self) -> usize {
123 self.dim()
124 }
125
126 pub fn normalized(&self) -> bool {
128 let norm = self.0.iter().map(|v| v * v).sum::<f32>().sqrt();
129 norm.is_finite() && (norm - 1.0).abs() < 1e-5
130 }
131
132 pub fn into_vec(self) -> Vec<f32> {
133 self.0
134 }
135
136 pub fn cosine(&self, other: &Embedding) -> f32 {
140 dot(&self.0, &other.0)
141 }
142}
143
144pub(crate) fn l2_normalize(v: &mut [f32]) {
146 let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt();
147 if norm > 0.0 {
148 v.iter_mut().for_each(|x| *x /= norm);
149 }
150}
151
152#[inline]
158pub(crate) fn dot(a: &[f32], b: &[f32]) -> f32 {
159 debug_assert_eq!(a.len(), b.len());
160 let ((ca, ra), (cb, rb)) = (a.as_chunks::<8>(), b.as_chunks::<8>());
161 let tail: f32 = ra.iter().zip(rb).map(|(x, y)| x * y).sum();
162 let mut acc = [0.0f32; 8];
163 for (x, y) in ca.iter().zip(cb) {
164 for i in 0..8 {
165 acc[i] += x[i] * y[i];
166 }
167 }
168 acc.iter().sum::<f32>() + tail
169}
170
171pub fn dot_product(a: &[f32], b: &[f32]) -> Result<f32> {
173 if a.len() != b.len() {
174 return Err(Error::DimensionMismatch {
175 expected: a.len(),
176 actual: b.len(),
177 });
178 }
179 let value = dot(a, b);
180 if !value.is_finite() {
181 return Err(Error::Embedding("non-finite dot product".into()));
182 }
183 Ok(value)
184}
185
186pub fn cosine_similarity(a: &Embedding, b: &Embedding) -> Result<f32> {
189 let product = dot_product(a.as_slice(), b.as_slice())?;
190 let norm_a = a.as_slice().iter().map(|v| v * v).sum::<f32>().sqrt();
191 let norm_b = b.as_slice().iter().map(|v| v * v).sum::<f32>().sqrt();
192 let denominator = norm_a * norm_b;
193 if denominator == 0.0 || !denominator.is_finite() {
194 return Err(Error::Embedding(
195 "cosine requires finite nonzero norms".into(),
196 ));
197 }
198 Ok(product / denominator)
199}
200
201pub fn normalize(values: &mut [f32]) -> Result<()> {
204 if values.iter().any(|v| !v.is_finite()) {
205 return Err(Error::Embedding(
206 "cannot normalize non-finite values".into(),
207 ));
208 }
209 let norm = values
210 .iter()
211 .map(|&v| f64::from(v).powi(2))
212 .sum::<f64>()
213 .sqrt();
214 if norm == 0.0 {
215 return Err(Error::Embedding("cannot normalize a zero vector".into()));
216 }
217 for value in values {
218 *value = (f64::from(*value) / norm) as f32;
219 }
220 Ok(())
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn preserved_vectors_round_trip_exact_bits() {
229 for values in [
230 vec![0.0, -0.0],
231 vec![
232 f32::from_bits(1),
233 -f32::from_bits(1),
234 f32::from_bits(0x007fffff),
235 ],
236 vec![3.0, -4.0, f32::MIN_POSITIVE, f32::MIN, f32::MAX],
237 vec![2.0; 769],
238 ] {
239 let bits: Vec<_> = values.iter().map(|v| v.to_bits()).collect();
240 let embedding = Embedding::from_preserved(values).unwrap();
241 assert_eq!(embedding.dim(), bits.len());
242 assert_eq!(
243 embedding
244 .as_slice()
245 .iter()
246 .map(|v| v.to_bits())
247 .collect::<Vec<_>>(),
248 bits
249 );
250 assert_eq!(
251 embedding
252 .into_vec()
253 .iter()
254 .map(|v| v.to_bits())
255 .collect::<Vec<_>>(),
256 bits
257 );
258 }
259 }
260
261 #[test]
262 fn preserved_vectors_reject_empty_and_non_finite_input() {
263 assert!(matches!(
264 Embedding::from_preserved(vec![]),
265 Err(Error::Embedding(_))
266 ));
267 for value in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
268 assert!(matches!(
269 Embedding::from_preserved(vec![1.0, value]),
270 Err(Error::Embedding(_))
271 ));
272 }
273 }
274
275 #[test]
276 fn truncation_renormalizes() {
277 let raw: Vec<f32> = (0..768).map(|i| (i as f32).sin()).collect();
278 let e = Embedding::from_raw(raw, Dimension::D128).unwrap();
279 assert_eq!(e.dim(), 128);
280 let norm: f32 = e.as_slice().iter().map(|x| x * x).sum::<f32>().sqrt();
281 assert!((norm - 1.0).abs() < 1e-5);
282 }
283
284 #[test]
285 fn rejects_short_vectors() {
286 assert!(Embedding::from_raw(vec![1.0; 100], Dimension::D128).is_err());
287 }
288
289 #[test]
290 fn dot_matches_naive_sum() {
291 for n in [1, 7, 8, 9, 128, 131, 768] {
292 let a: Vec<f32> = (0..n).map(|i| (i as f32 * 0.37).sin()).collect();
293 let b: Vec<f32> = (0..n).map(|i| (i as f32 * 0.11).cos()).collect();
294 let naive: f32 = a.iter().zip(&b).map(|(x, y)| x * y).sum();
295 assert!((dot(&a, &b) - naive).abs() < 1e-4, "n={n}");
296 }
297 }
298
299 #[test]
300 fn dimension_parse() {
301 assert_eq!("512".parse::<Dimension>().unwrap(), Dimension::D512);
302 assert_eq!("300".parse::<Dimension>().unwrap().get(), 300);
303 assert!("0".parse::<Dimension>().is_err());
304 }
305}