1use std::fmt;
4use std::num::NonZeroU32;
5
6#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10pub enum DistanceMetric {
11 Cosine,
13 Euclidean,
15 DotProduct,
17}
18
19impl DistanceMetric {
20 #[must_use]
22 pub const fn tag(self) -> u8 {
23 match self {
24 Self::Cosine => 0x01,
25 Self::Euclidean => 0x02,
26 Self::DotProduct => 0x03,
27 }
28 }
29
30 #[must_use]
32 pub const fn from_tag(tag: u8) -> Option<Self> {
33 match tag {
34 0x01 => Some(Self::Cosine),
35 0x02 => Some(Self::Euclidean),
36 0x03 => Some(Self::DotProduct),
37 _ => None,
38 }
39 }
40
41 #[must_use]
43 pub const fn keyword(self) -> &'static str {
44 match self {
45 Self::Cosine => "cosine",
46 Self::Euclidean => "euclidean",
47 Self::DotProduct => "dot_product",
48 }
49 }
50}
51
52impl fmt::Display for DistanceMetric {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.write_str(self.keyword())
55 }
56}
57
58pub const MAX_VECTOR_DIMENSION: u32 = 4_096;
60
61#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
63pub struct VectorDimension(NonZeroU32);
64
65impl VectorDimension {
66 #[must_use]
68 pub const fn new(value: u32) -> Option<Self> {
69 if value == 0 || value > MAX_VECTOR_DIMENSION {
70 return None;
71 }
72 match NonZeroU32::new(value) {
73 Some(inner) => Some(Self(inner)),
74 None => None,
75 }
76 }
77
78 #[must_use]
80 pub const fn get(self) -> u32 {
81 self.0.get()
82 }
83}
84
85impl fmt::Display for VectorDimension {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 write!(f, "{}", self.0)
88 }
89}
90
91#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
96pub struct StaleEntityCountThreshold(NonZeroU32);
97
98impl StaleEntityCountThreshold {
99 #[must_use]
101 pub const fn new(value: u32) -> Option<Self> {
102 match NonZeroU32::new(value) {
103 Some(inner) => Some(Self(inner)),
104 None => None,
105 }
106 }
107
108 #[must_use]
110 pub const fn get(self) -> u32 {
111 self.0.get()
112 }
113
114 #[must_use]
116 pub const fn is_breached_by(self, stale_count: u64) -> bool {
117 stale_count > self.0.get() as u64
118 }
119}
120
121impl fmt::Display for StaleEntityCountThreshold {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "{} stale entities", self.0)
124 }
125}
126
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub enum CanonicalVectorError {
130 DimensionOutOfRange {
132 actual: usize,
134 maximum: u32,
136 },
137 NonFiniteComponent {
140 index: usize,
142 },
143}
144
145impl fmt::Display for CanonicalVectorError {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 match self {
148 Self::DimensionOutOfRange { actual, maximum } => write!(
149 f,
150 "vector has {actual} components; the dimension must be in 1..={maximum}"
151 ),
152 Self::NonFiniteComponent { index } => {
153 write!(f, "vector component {index} is not a finite number")
154 }
155 }
156 }
157}
158
159impl std::error::Error for CanonicalVectorError {}
160
161#[derive(Clone, PartialEq)]
177pub struct CanonicalVector {
178 components: Vec<f32>,
180}
181crate::inspect_owned_capacity_struct_v1!(CanonicalVector {
182 owned: [components],
183 inline: []
184});
185
186impl CanonicalVector {
187 pub fn new(mut components: Vec<f32>) -> Result<Self, CanonicalVectorError> {
193 let len = components.len();
194 if len == 0 || len > MAX_VECTOR_DIMENSION as usize {
195 return Err(CanonicalVectorError::DimensionOutOfRange {
196 actual: len,
197 maximum: MAX_VECTOR_DIMENSION,
198 });
199 }
200 for (index, component) in components.iter_mut().enumerate() {
201 if !component.is_finite() {
202 return Err(CanonicalVectorError::NonFiniteComponent { index });
203 }
204 if *component == 0.0 {
205 *component = 0.0;
208 }
209 }
210 Ok(Self { components })
211 }
212
213 #[must_use]
215 pub fn dimension(&self) -> u32 {
216 self.components.len() as u32
217 }
218
219 #[must_use]
221 pub fn components(&self) -> &[f32] {
222 &self.components
223 }
224
225 #[must_use]
227 pub fn into_components(self) -> Vec<f32> {
228 self.components
229 }
230
231 #[must_use]
233 pub fn byte_size(&self) -> usize {
234 self.components.len() * 4
235 }
236}
237
238impl fmt::Debug for CanonicalVector {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 write!(f, "CanonicalVector(dim={},[REDACTED])", self.dimension())
241 }
242}
243
244impl Eq for CanonicalVector {}
248
249impl std::hash::Hash for CanonicalVector {
255 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
256 for component in &self.components {
257 state.write_u32(component.to_bits());
258 }
259 }
260}
261
262impl PartialOrd for CanonicalVector {
263 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
264 Some(self.cmp(other))
265 }
266}
267
268impl Ord for CanonicalVector {
269 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
270 self.components
271 .len()
272 .cmp(&other.components.len())
273 .then_with(|| {
274 for (a, b) in self.components.iter().zip(other.components.iter()) {
275 let ordering = a.to_bits().cmp(&b.to_bits());
276 if ordering != std::cmp::Ordering::Equal {
277 return ordering;
278 }
279 }
280 std::cmp::Ordering::Equal
281 })
282 }
283}
284
285#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
287pub struct EmbeddingMetadata {
288 model_identity: String,
290 model_version: String,
292}
293
294impl EmbeddingMetadata {
295 pub const MAX_MODEL_STRING_LEN: usize = 256;
297
298 #[must_use]
300 pub fn new(
301 model_identity: impl Into<String>,
302 model_version: impl Into<String>,
303 ) -> Option<Self> {
304 let model_identity = model_identity.into();
305 let model_version = model_version.into();
306 if model_identity.is_empty()
307 || model_identity.len() > Self::MAX_MODEL_STRING_LEN
308 || model_version.is_empty()
309 || model_version.len() > Self::MAX_MODEL_STRING_LEN
310 {
311 return None;
312 }
313 Some(Self {
314 model_identity,
315 model_version,
316 })
317 }
318
319 #[must_use]
321 pub fn model_identity(&self) -> &str {
322 &self.model_identity
323 }
324
325 #[must_use]
327 pub fn model_version(&self) -> &str {
328 &self.model_version
329 }
330}
331
332crate::inspect_owned_capacity_copy_v1!(VectorDimension);
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn stale_entity_count_threshold_is_positive_and_strict() {
341 assert!(StaleEntityCountThreshold::new(0).is_none());
342 let threshold = StaleEntityCountThreshold::new(3).expect("positive threshold");
343 assert_eq!(threshold.get(), 3);
344 assert!(!threshold.is_breached_by(3));
345 assert!(threshold.is_breached_by(4));
346 }
347
348 #[test]
349 fn construction_rejects_non_finite_components_as_typed_errors() {
350 for (components, index) in [
351 (vec![f32::NAN], 0),
352 (vec![1.0, f32::INFINITY], 1),
353 (vec![1.0, 2.0, f32::NEG_INFINITY], 2),
354 ] {
355 assert_eq!(
356 CanonicalVector::new(components),
357 Err(CanonicalVectorError::NonFiniteComponent { index })
358 );
359 }
360 }
361
362 #[test]
363 fn construction_rejects_out_of_range_dimensions() {
364 assert_eq!(
365 CanonicalVector::new(Vec::new()),
366 Err(CanonicalVectorError::DimensionOutOfRange {
367 actual: 0,
368 maximum: MAX_VECTOR_DIMENSION,
369 })
370 );
371 let oversized = vec![0.5; MAX_VECTOR_DIMENSION as usize + 1];
372 assert_eq!(
373 CanonicalVector::new(oversized),
374 Err(CanonicalVectorError::DimensionOutOfRange {
375 actual: MAX_VECTOR_DIMENSION as usize + 1,
376 maximum: MAX_VECTOR_DIMENSION,
377 })
378 );
379 assert!(CanonicalVector::new(vec![0.5; MAX_VECTOR_DIMENSION as usize]).is_ok());
380 }
381
382 #[test]
383 fn negative_zero_is_canonicalized_at_construction() {
384 let negative = CanonicalVector::new(vec![-0.0_f32]).expect("finite");
385 let positive = CanonicalVector::new(vec![0.0_f32]).expect("finite");
386 assert_eq!(
388 negative.components()[0].to_bits(),
389 positive.components()[0].to_bits()
390 );
391 assert_eq!(negative, positive);
392 assert_eq!(negative.cmp(&positive), std::cmp::Ordering::Equal);
393 let hash_of = |vector: &CanonicalVector| {
394 use std::hash::{Hash, Hasher};
395 let mut hasher = std::collections::hash_map::DefaultHasher::new();
396 vector.hash(&mut hasher);
397 hasher.finish()
398 };
399 assert_eq!(hash_of(&negative), hash_of(&positive));
400 }
401
402 #[test]
403 fn equality_is_reflexive_and_agrees_with_ordering() {
404 let vector = CanonicalVector::new(vec![1.5, -2.25, 0.0]).expect("finite");
405 assert_eq!(vector, vector.clone());
408 assert_eq!(vector.cmp(&vector.clone()), std::cmp::Ordering::Equal);
409 let other = CanonicalVector::new(vec![1.5, -2.25, 0.5]).expect("finite");
410 assert_ne!(vector, other);
411 assert_ne!(vector.cmp(&other), std::cmp::Ordering::Equal);
412 }
413}
414
415crate::inspect_owned_capacity_copy_v1!(DistanceMetric);
416
417crate::inspect_owned_capacity_struct_v1!(EmbeddingMetadata {
418 owned: [model_identity, model_version],
419 inline: []
420});