qualia_core_db/solvers/graph_match/
fuzzy_similarity.rs1use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct FuzzyTriple {
12 pub s: usize,
13 pub p: usize,
14 pub o: usize,
15 pub degree: f64,
16}
17
18fn to_map(g: &[FuzzyTriple]) -> HashMap<(usize, usize, usize), f64> {
19 let mut m = HashMap::new();
20 for t in g {
21 let e = m.entry((t.s, t.p, t.o)).or_insert(0.0);
23 if t.degree > *e {
24 *e = t.degree.clamp(0.0, 1.0);
25 }
26 }
27 m
28}
29
30pub fn fuzzy_jaccard(g1: &[FuzzyTriple], g2: &[FuzzyTriple]) -> f64 {
34 let m1 = to_map(g1);
35 let m2 = to_map(g2);
36 let mut keys: std::collections::HashSet<(usize, usize, usize)> = m1.keys().copied().collect();
37 keys.extend(m2.keys().copied());
38 if keys.is_empty() {
39 return 1.0;
40 }
41 let mut num = 0.0;
42 let mut den = 0.0;
43 for k in keys {
44 let a = *m1.get(&k).unwrap_or(&0.0);
45 let b = *m2.get(&k).unwrap_or(&0.0);
46 num += a.min(b);
47 den += a.max(b);
48 }
49 if den > 0.0 {
50 num / den
51 } else {
52 1.0
53 }
54}
55
56pub fn fuzzy_dice(g1: &[FuzzyTriple], g2: &[FuzzyTriple]) -> f64 {
59 let m1 = to_map(g1);
60 let m2 = to_map(g2);
61 let sum1: f64 = m1.values().sum();
62 let sum2: f64 = m2.values().sum();
63 if sum1 + sum2 == 0.0 {
64 return 1.0;
65 }
66 let mut inter = 0.0;
67 for (k, &a) in &m1 {
68 if let Some(&b) = m2.get(k) {
69 inter += a.min(b);
70 }
71 }
72 2.0 * inter / (sum1 + sum2)
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn t(s: usize, p: usize, o: usize, d: f64) -> FuzzyTriple {
80 FuzzyTriple { s, p, o, degree: d }
81 }
82
83 #[test]
84 fn identical_graphs_are_one() {
85 let g = [t(0, 1, 2, 0.8), t(2, 1, 3, 0.5)];
86 assert!((fuzzy_jaccard(&g, &g) - 1.0).abs() < 1e-12);
87 assert!((fuzzy_dice(&g, &g) - 1.0).abs() < 1e-12);
88 }
89
90 #[test]
91 fn disjoint_graphs_are_zero() {
92 let g1 = [t(0, 1, 2, 0.9)];
93 let g2 = [t(5, 6, 7, 0.9)];
94 assert!(fuzzy_jaccard(&g1, &g2).abs() < 1e-12);
95 }
96
97 #[test]
98 fn partial_overlap_with_degrees() {
99 let g1 = [t(0, 1, 2, 0.8), t(3, 1, 4, 0.6)];
101 let g2 = [t(0, 1, 2, 0.4), t(5, 1, 6, 0.7)];
102 let j = fuzzy_jaccard(&g1, &g2);
103 assert!((j - 0.4 / 2.1).abs() < 1e-9, "jaccard {j}");
105 assert!(j > 0.0 && j < 1.0);
106 }
107
108 #[test]
109 fn empty_graphs_are_similar() {
110 assert!((fuzzy_jaccard(&[], &[]) - 1.0).abs() < 1e-12);
111 }
112}