writerslogic_warm/core/cognition/
patterns.rs1use crate::core::hash::FastSet;
10
11use crate::core::triple_store::TripleStore;
12
13#[derive(Clone, Debug)]
15pub struct RelationPattern {
16 pub relation: String,
18 pub subjects: Vec<String>,
20 pub objects: Vec<String>,
22 pub frequency: usize,
24}
25
26#[derive(Clone, Debug)]
28pub struct CoOccurrence {
29 pub entity_a: String,
30 pub entity_b: String,
31 pub count: usize,
33}
34
35pub struct PatternScanner;
37
38impl PatternScanner {
39 pub fn scan_relation_patterns(
41 triple_store: &TripleStore,
42 min_freq: usize,
43 ) -> Vec<RelationPattern> {
44 let mut patterns = Vec::new();
45 for relation in triple_store.relation_ids() {
46 let mut subjects = FastSet::default();
47 let mut objects = FastSet::default();
48 let mut frequency = 0;
49 triple_store.visit(None, Some(&relation), None, |t| {
50 frequency += 1;
51 subjects.insert(t.subject_id.clone());
52 objects.insert(t.object_id.clone());
53 true
54 });
55 if frequency == 0 || frequency < min_freq {
56 continue;
57 }
58 let mut subjects: Vec<_> = subjects.into_iter().collect();
59 let mut objects: Vec<_> = objects.into_iter().collect();
60 subjects.sort();
61 objects.sort();
62 patterns.push(RelationPattern {
63 relation,
64 subjects,
65 objects,
66 frequency,
67 });
68 }
69
70 patterns.sort_by_key(|p| std::cmp::Reverse(p.frequency));
71 patterns
72 }
73
74 pub fn find_co_occurrences(triple_store: &TripleStore, min_shared: usize) -> Vec<CoOccurrence> {
77 let mut entities = Vec::new();
78 for entity in triple_store.subject_ids() {
79 let mut relations = FastSet::default();
80 triple_store.visit(Some(&entity), None, None, |t| {
81 relations.insert(t.relation_id.clone());
82 true
83 });
84 if relations.is_empty() {
85 continue;
86 }
87 let mut relations: Vec<_> = relations.into_iter().collect();
88 relations.sort();
89 entities.push((entity, relations));
90 }
91 let mut results = Vec::new();
92
93 for i in 0..entities.len() {
94 for j in (i + 1)..entities.len() {
95 let shared = count_shared_sorted(&entities[i].1, &entities[j].1);
96 if shared >= min_shared {
97 results.push(CoOccurrence {
98 entity_a: entities[i].0.clone(),
99 entity_b: entities[j].0.clone(),
100 count: shared,
101 });
102 }
103 }
104 }
105
106 results.sort_by_key(|r| std::cmp::Reverse(r.count));
107 results
108 }
109}
110
111fn count_shared_sorted(a: &[String], b: &[String]) -> usize {
113 let (mut i, mut j) = (0, 0);
114 let mut count = 0;
115 while i < a.len() && j < b.len() {
116 match a[i].cmp(&b[j]) {
117 std::cmp::Ordering::Less => i += 1,
118 std::cmp::Ordering::Greater => j += 1,
119 std::cmp::Ordering::Equal => {
120 count += 1;
121 i += 1;
122 j += 1;
123 }
124 }
125 }
126 count
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn make_store() -> TripleStore {
134 let store = TripleStore::new();
135 store.add("paris", "capital_of", "france", "c1");
136 store.add("berlin", "capital_of", "germany", "c2");
137 store.add("tokyo", "capital_of", "japan", "c3");
138 store.add("paris", "located_in", "europe", "c4");
139 store.add("berlin", "located_in", "europe", "c5");
140 store
141 }
142
143 #[test]
144 fn test_scan_relation_patterns() {
145 let store = make_store();
146 let patterns = PatternScanner::scan_relation_patterns(&store, 2);
147 assert!(!patterns.is_empty());
148 let capital = patterns
149 .iter()
150 .find(|p| p.relation == "capital_of")
151 .unwrap();
152 assert_eq!(capital.frequency, 3);
153 assert_eq!(capital.subjects.len(), 3);
154 assert_eq!(capital.objects.len(), 3);
155 }
156
157 #[test]
158 fn test_scan_below_threshold() {
159 let store = make_store();
160 let patterns = PatternScanner::scan_relation_patterns(&store, 10);
161 assert!(patterns.is_empty());
162 }
163
164 #[test]
165 fn test_co_occurrences() {
166 let store = make_store();
167 let co = PatternScanner::find_co_occurrences(&store, 2);
169 assert!(!co.is_empty());
170 let pair = co.iter().find(|c| {
171 (c.entity_a == "paris" && c.entity_b == "berlin")
172 || (c.entity_a == "berlin" && c.entity_b == "paris")
173 });
174 assert!(pair.is_some());
175 assert_eq!(pair.unwrap().count, 2);
176 }
177
178 #[test]
179 fn test_co_occurrences_empty() {
180 let store = TripleStore::new();
181 let co = PatternScanner::find_co_occurrences(&store, 1);
182 assert!(co.is_empty());
183 }
184}