1use crate::Vector;
30use anyhow::Result;
31use parking_lot::RwLock;
32use serde::{Deserialize, Serialize};
33use std::collections::HashMap;
34use std::sync::OnceLock;
35
36pub type CustomMetricFn = fn(&[f32], &[f32]) -> Result<f32>;
40
41static CUSTOM_METRIC_REGISTRY: OnceLock<RwLock<HashMap<u32, CustomMetricFn>>> = OnceLock::new();
43
44fn custom_metric_registry() -> &'static RwLock<HashMap<u32, CustomMetricFn>> {
45 CUSTOM_METRIC_REGISTRY.get_or_init(|| RwLock::new(HashMap::new()))
46}
47
48pub fn register_custom_metric(id: u32, f: CustomMetricFn) {
64 custom_metric_registry().write().insert(id, f);
65}
66
67pub fn unregister_custom_metric(id: u32) -> bool {
71 custom_metric_registry().write().remove(&id).is_some()
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
76pub enum ExtendedDistanceMetric {
77 Cosine,
79 Euclidean,
80 Manhattan,
81 Chebyshev,
82 Minkowski { p: f32 },
83
84 Hamming,
86 Jaccard,
87 Dice,
88 Pearson,
89 Spearman,
90 Kendall,
91
92 KLDivergence,
94 JensenShannon,
95 Bhattacharyya,
96 Hellinger,
97
98 Levenshtein,
100 DamerauLevenshtein,
101
102 MutualInformation,
104 NormalizedCompressionDistance,
105
106 Mahalanobis,
108 BrayCurtis,
109
110 Custom(u32), }
113
114impl ExtendedDistanceMetric {
115 pub fn distance(&self, a: &Vector, b: &Vector) -> Result<f32> {
117 let a_f32 = a.as_f32();
118 let b_f32 = b.as_f32();
119
120 if a_f32.len() != b_f32.len() {
121 return Err(anyhow::anyhow!(
122 "Vector dimensions must match: {} != {}",
123 a_f32.len(),
124 b_f32.len()
125 ));
126 }
127
128 match self {
129 ExtendedDistanceMetric::Cosine => Self::cosine_distance(&a_f32, &b_f32),
130 ExtendedDistanceMetric::Euclidean => Self::euclidean_distance(&a_f32, &b_f32),
131 ExtendedDistanceMetric::Manhattan => Self::manhattan_distance(&a_f32, &b_f32),
132 ExtendedDistanceMetric::Chebyshev => Self::chebyshev_distance(&a_f32, &b_f32),
133 ExtendedDistanceMetric::Minkowski { p } => Self::minkowski_distance(&a_f32, &b_f32, *p),
134 ExtendedDistanceMetric::Hamming => Self::hamming_distance(&a_f32, &b_f32),
135 ExtendedDistanceMetric::Jaccard => Self::jaccard_distance(&a_f32, &b_f32),
136 ExtendedDistanceMetric::Dice => Self::dice_distance(&a_f32, &b_f32),
137 ExtendedDistanceMetric::Pearson => Self::pearson_distance(&a_f32, &b_f32),
138 ExtendedDistanceMetric::Spearman => Self::spearman_distance(&a_f32, &b_f32),
139 ExtendedDistanceMetric::Kendall => Self::kendall_distance(&a_f32, &b_f32),
140 ExtendedDistanceMetric::KLDivergence => Self::kl_divergence(&a_f32, &b_f32),
141 ExtendedDistanceMetric::JensenShannon => Self::jensen_shannon(&a_f32, &b_f32),
142 ExtendedDistanceMetric::Bhattacharyya => Self::bhattacharyya(&a_f32, &b_f32),
143 ExtendedDistanceMetric::Hellinger => Self::hellinger(&a_f32, &b_f32),
144 ExtendedDistanceMetric::Levenshtein => Self::levenshtein_distance(&a_f32, &b_f32),
145 ExtendedDistanceMetric::DamerauLevenshtein => {
146 Self::damerau_levenshtein_distance(&a_f32, &b_f32)
147 }
148 ExtendedDistanceMetric::MutualInformation => Self::mutual_information(&a_f32, &b_f32),
149 ExtendedDistanceMetric::NormalizedCompressionDistance => Self::ncd(&a_f32, &b_f32),
150 ExtendedDistanceMetric::Mahalanobis => Self::mahalanobis_distance(&a_f32, &b_f32),
151 ExtendedDistanceMetric::BrayCurtis => Self::bray_curtis_distance(&a_f32, &b_f32),
152 ExtendedDistanceMetric::Custom(id) => {
153 let registry = custom_metric_registry().read();
154 match registry.get(id) {
155 Some(metric_fn) => metric_fn(&a_f32, &b_f32),
156 None => Err(anyhow::anyhow!(
157 "Custom metric id={id} not found — register it first with \
158 oxirs_vec::distance_metrics::register_custom_metric()"
159 )),
160 }
161 }
162 }
163 }
164
165 fn cosine_distance(a: &[f32], b: &[f32]) -> Result<f32> {
168 let dot: f32 = a.iter().zip(b).map(|(x, y)| x * y).sum();
169 let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
170 let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
171
172 if norm_a == 0.0 || norm_b == 0.0 {
173 return Ok(1.0);
174 }
175
176 Ok(1.0 - (dot / (norm_a * norm_b)))
177 }
178
179 fn euclidean_distance(a: &[f32], b: &[f32]) -> Result<f32> {
180 let dist: f32 = a
181 .iter()
182 .zip(b)
183 .map(|(x, y)| (x - y).powi(2))
184 .sum::<f32>()
185 .sqrt();
186 Ok(dist)
187 }
188
189 fn manhattan_distance(a: &[f32], b: &[f32]) -> Result<f32> {
190 let dist: f32 = a.iter().zip(b).map(|(x, y)| (x - y).abs()).sum();
191 Ok(dist)
192 }
193
194 fn chebyshev_distance(a: &[f32], b: &[f32]) -> Result<f32> {
195 let dist = a
196 .iter()
197 .zip(b)
198 .map(|(x, y)| (x - y).abs())
199 .fold(0.0f32, |max, val| max.max(val));
200 Ok(dist)
201 }
202
203 fn minkowski_distance(a: &[f32], b: &[f32], p: f32) -> Result<f32> {
204 if p <= 0.0 {
205 return Err(anyhow::anyhow!("p must be positive for Minkowski distance"));
206 }
207
208 if p == f32::INFINITY {
209 return Self::chebyshev_distance(a, b);
210 }
211
212 let dist = a
213 .iter()
214 .zip(b)
215 .map(|(x, y)| (x - y).abs().powf(p))
216 .sum::<f32>()
217 .powf(1.0 / p);
218 Ok(dist)
219 }
220
221 fn hamming_distance(a: &[f32], b: &[f32]) -> Result<f32> {
224 let threshold = 0.5; let dist = a
226 .iter()
227 .zip(b)
228 .filter(|(x, y)| {
229 let x_bin = **x > threshold;
230 let y_bin = **y > threshold;
231 x_bin != y_bin
232 })
233 .count();
234 Ok(dist as f32)
235 }
236
237 fn jaccard_distance(a: &[f32], b: &[f32]) -> Result<f32> {
238 let threshold = 0.5;
239 let mut intersection = 0;
240 let mut union = 0;
241
242 for (x, y) in a.iter().zip(b) {
243 let x_bin = *x > threshold;
244 let y_bin = *y > threshold;
245
246 if x_bin || y_bin {
247 union += 1;
248 if x_bin && y_bin {
249 intersection += 1;
250 }
251 }
252 }
253
254 if union == 0 {
255 return Ok(0.0);
256 }
257
258 Ok(1.0 - (intersection as f32 / union as f32))
259 }
260
261 fn dice_distance(a: &[f32], b: &[f32]) -> Result<f32> {
262 let threshold = 0.5;
263 let mut intersection = 0;
264 let mut a_count = 0;
265 let mut b_count = 0;
266
267 for (x, y) in a.iter().zip(b) {
268 let x_bin = *x > threshold;
269 let y_bin = *y > threshold;
270
271 if x_bin {
272 a_count += 1;
273 }
274 if y_bin {
275 b_count += 1;
276 }
277 if x_bin && y_bin {
278 intersection += 1;
279 }
280 }
281
282 let sum = a_count + b_count;
283 if sum == 0 {
284 return Ok(0.0);
285 }
286
287 Ok(1.0 - (2.0 * intersection as f32 / sum as f32))
288 }
289
290 fn pearson_distance(a: &[f32], b: &[f32]) -> Result<f32> {
291 let n = a.len() as f32;
292 let mean_a: f32 = a.iter().sum::<f32>() / n;
293 let mean_b: f32 = b.iter().sum::<f32>() / n;
294
295 let mut numerator = 0.0;
296 let mut sum_sq_a = 0.0;
297 let mut sum_sq_b = 0.0;
298
299 for (x, y) in a.iter().zip(b) {
300 let da = x - mean_a;
301 let db = y - mean_b;
302 numerator += da * db;
303 sum_sq_a += da * da;
304 sum_sq_b += db * db;
305 }
306
307 if sum_sq_a == 0.0 || sum_sq_b == 0.0 {
308 return Ok(1.0);
309 }
310
311 let correlation = numerator / (sum_sq_a.sqrt() * sum_sq_b.sqrt());
312 Ok(1.0 - correlation)
313 }
314
315 fn spearman_distance(a: &[f32], b: &[f32]) -> Result<f32> {
316 let rank_a = Self::rank_vector(a);
318 let rank_b = Self::rank_vector(b);
319
320 Self::pearson_distance(&rank_a, &rank_b)
322 }
323
324 fn kendall_distance(a: &[f32], b: &[f32]) -> Result<f32> {
325 let n = a.len();
326 let mut concordant = 0;
327 let mut discordant = 0;
328
329 for i in 0..n {
330 for j in (i + 1)..n {
331 let sign_a = (a[j] - a[i]).signum();
332 let sign_b = (b[j] - b[i]).signum();
333
334 if sign_a * sign_b > 0.0 {
335 concordant += 1;
336 } else if sign_a * sign_b < 0.0 {
337 discordant += 1;
338 }
339 }
340 }
341
342 let total_pairs = (n * (n - 1)) / 2;
343 if total_pairs == 0 {
344 return Ok(0.0);
345 }
346
347 let tau = (concordant - discordant) as f32 / total_pairs as f32;
348 Ok(1.0 - tau)
349 }
350
351 fn kl_divergence(p: &[f32], q: &[f32]) -> Result<f32> {
354 let epsilon = 1e-10;
355 let mut divergence = 0.0;
356
357 for (pi, qi) in p.iter().zip(q) {
358 let pi_safe = pi.max(epsilon);
359 let qi_safe = qi.max(epsilon);
360 divergence += pi_safe * (pi_safe / qi_safe).ln();
361 }
362
363 Ok(divergence)
364 }
365
366 fn jensen_shannon(p: &[f32], q: &[f32]) -> Result<f32> {
367 let m: Vec<f32> = p.iter().zip(q).map(|(pi, qi)| (pi + qi) / 2.0).collect();
368
369 let kl_pm = Self::kl_divergence(p, &m)?;
370 let kl_qm = Self::kl_divergence(q, &m)?;
371
372 Ok((kl_pm + kl_qm) / 2.0)
373 }
374
375 fn bhattacharyya(p: &[f32], q: &[f32]) -> Result<f32> {
376 let bc: f32 = p.iter().zip(q).map(|(pi, qi)| (pi * qi).sqrt()).sum();
377 Ok(-bc.ln())
378 }
379
380 fn hellinger(p: &[f32], q: &[f32]) -> Result<f32> {
381 let sum: f32 = p
382 .iter()
383 .zip(q)
384 .map(|(pi, qi)| (pi.sqrt() - qi.sqrt()).powi(2))
385 .sum();
386 Ok((sum / 2.0).sqrt())
387 }
388
389 #[allow(clippy::needless_range_loop)]
392 fn levenshtein_distance(a: &[f32], b: &[f32]) -> Result<f32> {
393 let threshold = 0.5;
394 let a_bin: Vec<bool> = a.iter().map(|x| *x > threshold).collect();
395 let b_bin: Vec<bool> = b.iter().map(|x| *x > threshold).collect();
396
397 let m = a_bin.len();
398 let n = b_bin.len();
399
400 if m == 0 {
401 return Ok(n as f32);
402 }
403 if n == 0 {
404 return Ok(m as f32);
405 }
406
407 let mut dp = vec![vec![0; n + 1]; m + 1];
408
409 for i in 0..=m {
410 dp[i][0] = i;
411 }
412 for j in 0..=n {
413 dp[0][j] = j;
414 }
415
416 for i in 1..=m {
417 for j in 1..=n {
418 let cost = if a_bin[i - 1] == b_bin[j - 1] { 0 } else { 1 };
419 dp[i][j] = (dp[i - 1][j] + 1)
420 .min(dp[i][j - 1] + 1)
421 .min(dp[i - 1][j - 1] + cost);
422 }
423 }
424
425 Ok(dp[m][n] as f32)
426 }
427
428 fn damerau_levenshtein_distance(a: &[f32], b: &[f32]) -> Result<f32> {
429 Self::levenshtein_distance(a, b)
432 }
433
434 fn mutual_information(a: &[f32], b: &[f32]) -> Result<f32> {
437 let joint_entropy = Self::calculate_entropy(a)? + Self::calculate_entropy(b)?;
440 let individual_entropy = Self::calculate_joint_entropy(a, b)?;
441
442 Ok(joint_entropy - individual_entropy)
443 }
444
445 fn ncd(a: &[f32], b: &[f32]) -> Result<f32> {
446 let ca = Self::estimate_compression_size(a);
449 let cb = Self::estimate_compression_size(b);
450 let cab = Self::estimate_joint_compression_size(a, b);
451
452 let min_c = ca.min(cb);
453 let max_c = ca.max(cb);
454
455 if max_c == 0.0 {
456 return Ok(0.0);
457 }
458
459 Ok((cab - min_c) / max_c)
460 }
461
462 fn mahalanobis_distance(a: &[f32], b: &[f32]) -> Result<f32> {
465 Self::euclidean_distance(a, b)
468 }
469
470 fn bray_curtis_distance(a: &[f32], b: &[f32]) -> Result<f32> {
471 let mut numerator = 0.0;
472 let mut denominator = 0.0;
473
474 for (x, y) in a.iter().zip(b) {
475 numerator += (x - y).abs();
476 denominator += x + y;
477 }
478
479 if denominator == 0.0 {
480 return Ok(0.0);
481 }
482
483 Ok(numerator / denominator)
484 }
485
486 fn rank_vector(v: &[f32]) -> Vec<f32> {
489 let mut indexed: Vec<(usize, f32)> = v.iter().enumerate().map(|(i, &x)| (i, x)).collect();
490 indexed.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
491
492 let mut ranks = vec![0.0; v.len()];
493 for (rank, (original_index, _)) in indexed.iter().enumerate() {
494 ranks[*original_index] = rank as f32;
495 }
496
497 ranks
498 }
499
500 fn calculate_entropy(v: &[f32]) -> Result<f32> {
501 let epsilon = 1e-10;
502 let mut entropy = 0.0;
503
504 for &x in v {
505 if x > epsilon {
506 entropy -= x * x.ln();
507 }
508 }
509
510 Ok(entropy)
511 }
512
513 fn calculate_joint_entropy(a: &[f32], b: &[f32]) -> Result<f32> {
514 let epsilon = 1e-10;
515 let mut entropy = 0.0;
516
517 for (x, y) in a.iter().zip(b) {
518 let joint = x * y;
519 if joint > epsilon {
520 entropy -= joint * joint.ln();
521 }
522 }
523
524 Ok(entropy)
525 }
526
527 fn estimate_compression_size(v: &[f32]) -> f32 {
528 let mut sorted = v.to_vec();
531 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
532
533 let mut unique_count = 1;
534 for i in 1..sorted.len() {
535 if (sorted[i] - sorted[i - 1]).abs() > 1e-6 {
536 unique_count += 1;
537 }
538 }
539
540 unique_count as f32
541 }
542
543 fn estimate_joint_compression_size(a: &[f32], b: &[f32]) -> f32 {
544 let mut combined = Vec::with_capacity(a.len() + b.len());
545 combined.extend_from_slice(a);
546 combined.extend_from_slice(b);
547 Self::estimate_compression_size(&combined)
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554
555 #[test]
556 fn test_cosine_distance() -> Result<()> {
557 let a = Vector::new(vec![1.0, 0.0, 0.0]);
558 let b = Vector::new(vec![1.0, 0.0, 0.0]);
559
560 let distance = ExtendedDistanceMetric::Cosine.distance(&a, &b)?;
561 assert!(distance < 0.01); Ok(())
563 }
564
565 #[test]
566 fn test_euclidean_distance() -> Result<()> {
567 let a = Vector::new(vec![0.0, 0.0]);
568 let b = Vector::new(vec![3.0, 4.0]);
569
570 let distance = ExtendedDistanceMetric::Euclidean.distance(&a, &b)?;
571 assert!((distance - 5.0).abs() < 0.01); Ok(())
573 }
574
575 #[test]
576 fn test_hamming_distance() -> Result<()> {
577 let a = Vector::new(vec![1.0, 1.0, 0.0, 0.0]);
578 let b = Vector::new(vec![1.0, 0.0, 1.0, 0.0]);
579
580 let distance = ExtendedDistanceMetric::Hamming.distance(&a, &b)?;
581 assert_eq!(distance, 2.0); Ok(())
583 }
584
585 #[test]
586 fn test_jaccard_distance() -> Result<()> {
587 let a = Vector::new(vec![1.0, 1.0, 0.0, 0.0]);
588 let b = Vector::new(vec![1.0, 0.0, 1.0, 0.0]);
589
590 let distance = ExtendedDistanceMetric::Jaccard.distance(&a, &b)?;
591 assert!(distance > 0.0 && distance < 1.0);
592 Ok(())
593 }
594
595 #[test]
596 fn test_pearson_distance() -> Result<()> {
597 let a = Vector::new(vec![1.0, 2.0, 3.0, 4.0]);
598 let b = Vector::new(vec![1.0, 2.0, 3.0, 4.0]);
599
600 let distance = ExtendedDistanceMetric::Pearson.distance(&a, &b)?;
601 assert!(distance < 0.01); Ok(())
603 }
604
605 #[test]
606 fn test_manhattan_distance() -> Result<()> {
607 let a = Vector::new(vec![1.0, 2.0, 3.0]);
608 let b = Vector::new(vec![4.0, 5.0, 6.0]);
609
610 let distance = ExtendedDistanceMetric::Manhattan.distance(&a, &b)?;
611 assert_eq!(distance, 9.0); Ok(())
613 }
614
615 #[test]
616 fn test_custom_metric_unregistered_returns_error() {
617 let a = Vector::new(vec![1.0, 0.0]);
618 let b = Vector::new(vec![0.0, 1.0]);
619 let result = ExtendedDistanceMetric::Custom(9999).distance(&a, &b);
620 assert!(
621 result.is_err(),
622 "unregistered custom metric should return Err"
623 );
624 let msg = result.unwrap_err().to_string();
625 assert!(msg.contains("9999"), "error should mention the missing id");
626 }
627
628 #[test]
629 fn test_custom_metric_manhattan_via_registry() -> Result<()> {
630 const MY_MANHATTAN: u32 = 0xCAFE_0001;
632 super::register_custom_metric(MY_MANHATTAN, |a, b| {
633 Ok(a.iter().zip(b).map(|(x, y)| (x - y).abs()).sum())
634 });
635
636 let a = Vector::new(vec![1.0, 2.0, 3.0]);
637 let b = Vector::new(vec![4.0, 5.0, 6.0]);
638 let dist = ExtendedDistanceMetric::Custom(MY_MANHATTAN).distance(&a, &b)?;
639 assert!(
640 (dist - 9.0).abs() < 1e-6,
641 "custom Manhattan distance should be 9"
642 );
643
644 super::unregister_custom_metric(MY_MANHATTAN);
645 Ok(())
646 }
647
648 #[test]
649 fn test_custom_metric_overwrite() -> Result<()> {
650 const MY_ID: u32 = 0xCAFE_0002;
651 super::register_custom_metric(MY_ID, |_a, _b| Ok(0.0));
653 let a = Vector::new(vec![1.0, 2.0]);
654 let b = Vector::new(vec![3.0, 4.0]);
655 let d1 = ExtendedDistanceMetric::Custom(MY_ID).distance(&a, &b)?;
656 assert!((d1 - 0.0).abs() < 1e-6);
657
658 super::register_custom_metric(MY_ID, |a, b| {
660 Ok(a.iter()
661 .zip(b)
662 .map(|(x, y)| (x - y).powi(2))
663 .sum::<f32>()
664 .sqrt())
665 });
666 let d2 = ExtendedDistanceMetric::Custom(MY_ID).distance(&a, &b)?;
667 assert!(
668 d2 > 0.0,
669 "overwritten metric should produce non-zero distance"
670 );
671
672 super::unregister_custom_metric(MY_ID);
673 Ok(())
674 }
675}