Skip to main content

haystack_core/ontology/
taxonomy.rs

1// TaxonomyTree -- unified type hierarchy for Haystack 4 defs.
2
3use parking_lot::RwLock;
4use std::collections::{HashMap, HashSet, VecDeque};
5
6/// Unified inheritance graph for Haystack 4 defs.
7///
8/// Supports multiple inheritance (a def can have multiple supertypes via
9/// the `is` tag). Pre-computes mandatory marker sets on first access
10/// for fast `fits()` evaluation.
11pub struct TaxonomyTree {
12    /// child -> parent symbols
13    parents: HashMap<String, Vec<String>>,
14    /// parent -> child symbols
15    children: HashMap<String, Vec<String>>,
16    /// Cached mandatory tag sets per type (RwLock for thread safety)
17    mandatory_cache: RwLock<HashMap<String, HashSet<String>>>,
18}
19
20impl TaxonomyTree {
21    /// Create an empty taxonomy tree.
22    pub fn new() -> Self {
23        Self {
24            parents: HashMap::new(),
25            children: HashMap::new(),
26            mandatory_cache: RwLock::new(HashMap::new()),
27        }
28    }
29
30    /// Register a type with its supertypes.
31    pub fn add(&mut self, name: &str, supertypes: &[String]) {
32        self.parents.entry(name.to_string()).or_default();
33        for parent in supertypes {
34            let parents_list = self.parents.entry(name.to_string()).or_default();
35            if !parents_list.contains(parent) {
36                parents_list.push(parent.clone());
37            }
38            let children_list = self.children.entry(parent.clone()).or_default();
39            if !children_list.contains(&name.to_string()) {
40                children_list.push(name.to_string());
41            }
42            // Ensure parent exists in parents map
43            self.parents.entry(parent.clone()).or_default();
44        }
45        // Invalidate cache
46        self.mandatory_cache.write().clear();
47    }
48
49    /// Check if `child` is a subtype of `parent`.
50    ///
51    /// Uses BFS up the parent chain. Returns `true` if `child`
52    /// equals `parent` or if `parent` is an ancestor.
53    pub fn is_subtype(&self, child: &str, parent: &str) -> bool {
54        if child == parent {
55            return true;
56        }
57        let mut visited: HashSet<&str> = HashSet::new();
58        let mut queue: VecDeque<&str> = VecDeque::new();
59        queue.push_back(child);
60
61        while let Some(current) = queue.pop_front() {
62            if !visited.insert(current) {
63                continue;
64            }
65            if let Some(parents) = self.parents.get(current) {
66                for p in parents {
67                    if p == parent {
68                        return true;
69                    }
70                    queue.push_back(p.as_str());
71                }
72            }
73        }
74        false
75    }
76
77    /// Full ancestor chain (transitive, breadth-first).
78    ///
79    /// Returns all ancestors of `name`, nearest first.
80    pub fn supertypes_of(&self, name: &str) -> Vec<String> {
81        let mut result: Vec<String> = Vec::new();
82        let mut visited: HashSet<String> = HashSet::new();
83        let mut queue: VecDeque<String> = VecDeque::new();
84
85        if let Some(parents) = self.parents.get(name) {
86            for p in parents {
87                queue.push_back(p.clone());
88            }
89        }
90
91        while let Some(current) = queue.pop_front() {
92            if !visited.insert(current.clone()) {
93                continue;
94            }
95            result.push(current.clone());
96            if let Some(parents) = self.parents.get(&current) {
97                for p in parents {
98                    queue.push_back(p.clone());
99                }
100            }
101        }
102        result
103    }
104
105    /// Direct children of a type.
106    pub fn subtypes_of(&self, name: &str) -> Vec<String> {
107        self.children.get(name).cloned().unwrap_or_default()
108    }
109
110    /// All descendants (transitive, breadth-first).
111    pub fn all_subtypes(&self, name: &str) -> Vec<String> {
112        let mut result: Vec<String> = Vec::new();
113        let mut visited: HashSet<String> = HashSet::new();
114        let mut queue: VecDeque<String> = VecDeque::new();
115
116        if let Some(children) = self.children.get(name) {
117            for c in children {
118                queue.push_back(c.clone());
119            }
120        }
121
122        while let Some(current) = queue.pop_front() {
123            if !visited.insert(current.clone()) {
124                continue;
125            }
126            result.push(current.clone());
127            if let Some(children) = self.children.get(&current) {
128                for c in children {
129                    queue.push_back(c.clone());
130                }
131            }
132        }
133        result
134    }
135
136    /// Get mandatory marker tags for a type.
137    ///
138    /// Walks the supertype chain and collects all types that are
139    /// marked as mandatory. Results are cached.
140    pub fn mandatory_tags(&self, name: &str, mandatory_defs: &HashSet<String>) -> HashSet<String> {
141        if let Some(cached) = self.mandatory_cache.read().get(name) {
142            return cached.clone();
143        }
144
145        let mut tags: HashSet<String> = HashSet::new();
146
147        // Include self if mandatory
148        if mandatory_defs.contains(name) {
149            tags.insert(name.to_string());
150        }
151
152        // Walk supertypes
153        let supertypes = self.supertypes_of(name);
154        for sup in &supertypes {
155            if mandatory_defs.contains(sup) {
156                tags.insert(sup.clone());
157            }
158        }
159
160        self.mandatory_cache
161            .write()
162            .insert(name.to_string(), tags.clone());
163        tags
164    }
165
166    /// Check if a type is registered in the taxonomy.
167    pub fn contains(&self, name: &str) -> bool {
168        self.parents.contains_key(name)
169    }
170
171    /// Number of registered types.
172    pub fn len(&self) -> usize {
173        self.parents.len()
174    }
175
176    /// Returns true if no types are registered.
177    pub fn is_empty(&self) -> bool {
178        self.parents.is_empty()
179    }
180
181    /// Clear the mandatory tag cache (used after library unload).
182    pub fn clear_cache(&self) {
183        self.mandatory_cache.write().clear();
184    }
185}
186
187impl Default for TaxonomyTree {
188    fn default() -> Self {
189        Self::new()
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    fn build_tree() -> TaxonomyTree {
198        let mut tree = TaxonomyTree::new();
199        // marker
200        //   ├─ entity
201        //   │    ├─ equip
202        //   │    │    ├─ ahu
203        //   │    │    └─ meter
204        //   │    └─ point
205        //   └─ val
206        tree.add("marker", &[]);
207        tree.add("entity", &["marker".to_string()]);
208        tree.add("equip", &["entity".to_string()]);
209        tree.add("ahu", &["equip".to_string()]);
210        tree.add("meter", &["equip".to_string()]);
211        tree.add("point", &["entity".to_string()]);
212        tree.add("val", &["marker".to_string()]);
213        tree
214    }
215
216    #[test]
217    fn is_subtype_self() {
218        let tree = build_tree();
219        assert!(tree.is_subtype("ahu", "ahu"));
220    }
221
222    #[test]
223    fn is_subtype_direct_parent() {
224        let tree = build_tree();
225        assert!(tree.is_subtype("ahu", "equip"));
226    }
227
228    #[test]
229    fn is_subtype_ancestor() {
230        let tree = build_tree();
231        assert!(tree.is_subtype("ahu", "entity"));
232        assert!(tree.is_subtype("ahu", "marker"));
233    }
234
235    #[test]
236    fn is_subtype_false_for_unrelated() {
237        let tree = build_tree();
238        assert!(!tree.is_subtype("ahu", "point"));
239        assert!(!tree.is_subtype("ahu", "val"));
240    }
241
242    #[test]
243    fn is_subtype_false_for_child() {
244        let tree = build_tree();
245        // equip is NOT a subtype of ahu (it's the other way around)
246        assert!(!tree.is_subtype("equip", "ahu"));
247    }
248
249    #[test]
250    fn supertypes_of_bfs_order() {
251        let tree = build_tree();
252        let supers = tree.supertypes_of("ahu");
253        // BFS: equip first, then entity (from equip), then marker (from entity)
254        assert_eq!(supers, vec!["equip", "entity", "marker"]);
255    }
256
257    #[test]
258    fn supertypes_of_root() {
259        let tree = build_tree();
260        let supers = tree.supertypes_of("marker");
261        assert!(supers.is_empty());
262    }
263
264    #[test]
265    fn subtypes_of_direct_children() {
266        let tree = build_tree();
267        let mut children = tree.subtypes_of("equip");
268        children.sort();
269        assert_eq!(children, vec!["ahu", "meter"]);
270    }
271
272    #[test]
273    fn subtypes_of_leaf() {
274        let tree = build_tree();
275        let children = tree.subtypes_of("ahu");
276        assert!(children.is_empty());
277    }
278
279    #[test]
280    fn all_subtypes_full_tree() {
281        let tree = build_tree();
282        let mut descendants = tree.all_subtypes("entity");
283        descendants.sort();
284        assert_eq!(descendants, vec!["ahu", "equip", "meter", "point"]);
285    }
286
287    #[test]
288    fn all_subtypes_leaf() {
289        let tree = build_tree();
290        let descendants = tree.all_subtypes("ahu");
291        assert!(descendants.is_empty());
292    }
293
294    #[test]
295    fn mandatory_tags_basic() {
296        let tree = build_tree();
297        let mut mandatory_defs = HashSet::new();
298        mandatory_defs.insert("equip".to_string());
299        mandatory_defs.insert("entity".to_string());
300
301        let tags = tree.mandatory_tags("ahu", &mandatory_defs);
302        assert!(tags.contains("equip"));
303        assert!(tags.contains("entity"));
304        assert!(!tags.contains("marker"));
305        assert!(!tags.contains("ahu"));
306    }
307
308    #[test]
309    fn mandatory_tags_self_mandatory() {
310        let tree = build_tree();
311        let mut mandatory_defs = HashSet::new();
312        mandatory_defs.insert("ahu".to_string());
313
314        let tags = tree.mandatory_tags("ahu", &mandatory_defs);
315        assert!(tags.contains("ahu"));
316    }
317
318    #[test]
319    fn mandatory_tags_caching() {
320        let tree = build_tree();
321        let mandatory_defs: HashSet<String> = ["equip".to_string()].into();
322
323        let tags1 = tree.mandatory_tags("ahu", &mandatory_defs);
324        let tags2 = tree.mandatory_tags("ahu", &mandatory_defs);
325        assert_eq!(tags1, tags2);
326    }
327
328    #[test]
329    fn contains_and_len() {
330        let tree = build_tree();
331        assert!(tree.contains("ahu"));
332        assert!(tree.contains("marker"));
333        assert!(!tree.contains("nonexistent"));
334        assert_eq!(tree.len(), 7);
335    }
336
337    #[test]
338    fn empty_tree() {
339        let tree = TaxonomyTree::new();
340        assert!(tree.is_empty());
341        assert_eq!(tree.len(), 0);
342    }
343
344    #[test]
345    fn multiple_inheritance() {
346        let mut tree = TaxonomyTree::new();
347        tree.add("a", &[]);
348        tree.add("b", &[]);
349        tree.add("c", &["a".to_string(), "b".to_string()]);
350
351        assert!(tree.is_subtype("c", "a"));
352        assert!(tree.is_subtype("c", "b"));
353
354        let supers = tree.supertypes_of("c");
355        assert_eq!(supers.len(), 2);
356        assert!(supers.contains(&"a".to_string()));
357        assert!(supers.contains(&"b".to_string()));
358    }
359}