Skip to main content

oxirs_vec/
distance_metrics.rs

1//! Extended distance metrics for vector similarity
2//!
3//! This module provides a comprehensive collection of distance metrics
4//! including specialized metrics for different data types and use cases.
5//!
6//! # Custom Metrics
7//!
8//! User-defined distance metrics can be registered globally and referenced by
9//! their numeric `id` via [`ExtendedDistanceMetric::Custom`]:
10//!
11//! ```
12//! use oxirs_vec::distance_metrics::{register_custom_metric, ExtendedDistanceMetric};
13//! use oxirs_vec::Vector;
14//!
15//! // Register a simple inverted-cosine metric as ID 0
16//! register_custom_metric(0, |a, b| {
17//!     let dot: f32 = a.iter().zip(b).map(|(x, y)| x * y).sum();
18//!     let na: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
19//!     let nb: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
20//!     if na == 0.0 || nb == 0.0 { Ok(1.0) } else { Ok(1.0 - dot / (na * nb)) }
21//! });
22//!
23//! let a = Vector::new(vec![1.0, 0.0]);
24//! let b = Vector::new(vec![1.0, 0.0]);
25//! let d = ExtendedDistanceMetric::Custom(0).distance(&a, &b).unwrap();
26//! assert!(d < 0.01);
27//! ```
28
29use crate::Vector;
30use anyhow::Result;
31use parking_lot::RwLock;
32use serde::{Deserialize, Serialize};
33use std::collections::HashMap;
34use std::sync::OnceLock;
35
36/// The type of a user-supplied custom distance function.
37///
38/// Receives two float slices of equal length and returns a non-negative distance.
39pub type CustomMetricFn = fn(&[f32], &[f32]) -> Result<f32>;
40
41/// Global registry of user-defined distance metrics, keyed by `u32` ID.
42static 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
48/// Register a custom distance metric.
49///
50/// The `id` must match the value stored in [`ExtendedDistanceMetric::Custom`].
51/// Registering the same `id` twice silently replaces the previous function.
52///
53/// # Example
54///
55/// ```
56/// use oxirs_vec::distance_metrics::register_custom_metric;
57///
58/// register_custom_metric(42, |a, b| {
59///     let sum: f32 = a.iter().zip(b).map(|(x, y)| (x - y).abs()).sum();
60///     Ok(sum)
61/// });
62/// ```
63pub fn register_custom_metric(id: u32, f: CustomMetricFn) {
64    custom_metric_registry().write().insert(id, f);
65}
66
67/// Remove a previously registered custom metric.
68///
69/// Returns `true` if the metric existed and was removed, `false` otherwise.
70pub fn unregister_custom_metric(id: u32) -> bool {
71    custom_metric_registry().write().remove(&id).is_some()
72}
73
74/// Extended distance metric types
75#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
76pub enum ExtendedDistanceMetric {
77    // Standard metrics
78    Cosine,
79    Euclidean,
80    Manhattan,
81    Chebyshev,
82    Minkowski { p: f32 },
83
84    // Specialized metrics
85    Hamming,
86    Jaccard,
87    Dice,
88    Pearson,
89    Spearman,
90    Kendall,
91
92    // Statistical metrics
93    KLDivergence,
94    JensenShannon,
95    Bhattacharyya,
96    Hellinger,
97
98    // Edit distance metrics
99    Levenshtein,
100    DamerauLevenshtein,
101
102    // Information-theoretic metrics
103    MutualInformation,
104    NormalizedCompressionDistance,
105
106    // Specialized for embeddings
107    Mahalanobis,
108    BrayCurtis,
109
110    // Custom metric (user-defined)
111    Custom(u32), // ID for custom metric lookup
112}
113
114impl ExtendedDistanceMetric {
115    /// Calculate distance between two vectors
116    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    // Standard distance metrics
166
167    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    // Specialized distance metrics
222
223    fn hamming_distance(a: &[f32], b: &[f32]) -> Result<f32> {
224        let threshold = 0.5; // Threshold for binary conversion
225        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        // Convert to ranks
317        let rank_a = Self::rank_vector(a);
318        let rank_b = Self::rank_vector(b);
319
320        // Calculate Pearson on ranks
321        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    // Statistical distance metrics
352
353    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    // Edit distance metrics
390
391    #[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        // Simplified Damerau-Levenshtein (allows transpositions)
430        // Full implementation is complex, this is an approximation
431        Self::levenshtein_distance(a, b)
432    }
433
434    // Information-theoretic metrics
435
436    fn mutual_information(a: &[f32], b: &[f32]) -> Result<f32> {
437        // Simplified mutual information calculation
438        // Full implementation would require histogram binning
439        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        // Normalized Compression Distance
447        // Approximation using simple compression ratios
448        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    // Advanced distance metrics
463
464    fn mahalanobis_distance(a: &[f32], b: &[f32]) -> Result<f32> {
465        // Simplified Mahalanobis distance (assuming identity covariance)
466        // Full implementation would require covariance matrix
467        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    // Helper functions
487
488    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        // Rough estimate based on unique values and entropy
529        // Since f32 doesn't implement Eq/Hash, we'll use a different approach
530        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); // Should be close to 0
562        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); // Should be 5.0
572        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); // 2 positions differ
582        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); // Perfect correlation
602        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); // |1-4| + |2-5| + |3-6| = 9
612        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        // Use a unique ID to avoid interference between tests.
631        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        // Register a "always 0" metric
652        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        // Overwrite with Euclidean
659        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}