rust_rule_engine/rete/
tms.rs

1//! Truth Maintenance System (TMS)
2//!
3//! This module implements a Truth Maintenance System similar to CLIPS and Drools.
4//! 
5//! Key features:
6//! - Justification tracking: Track WHY facts exist
7//! - Logical assertions: Facts derived by rules vs. explicitly asserted
8//! - Automatic retraction: Auto-retract derived facts when premises change
9//! - Dependency chains: Track support relationships between facts
10//!
11//! Example:
12//! ```ignore
13//! // Rule derives a fact
14//! rule "InferPremium" {
15//!     when Customer.spent > 10000
16//!     then logicalAssert(Customer.tier = "Premium");
17//! }
18//! 
19//! // When Customer.spent changes to 5000:
20//! // → TMS auto-retracts Customer.tier = "Premium"
21//! ```
22
23use std::collections::{HashMap, HashSet};
24use std::time::Instant;
25use super::working_memory::FactHandle;
26
27/// Type of justification for a fact
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum JustificationType {
30    /// Fact was explicitly asserted by user
31    Explicit,
32    /// Fact was logically derived by a rule
33    Logical,
34}
35
36/// Justification records WHY a fact exists
37///
38/// A fact can have multiple justifications (multiple rules can derive the same fact).
39/// The fact is only retracted when ALL justifications are removed.
40#[derive(Debug, Clone)]
41pub struct Justification {
42    /// The fact being justified
43    pub fact_handle: FactHandle,
44    
45    /// Type of justification
46    pub justification_type: JustificationType,
47    
48    /// Rule that created this fact (if logical)
49    pub source_rule: Option<String>,
50    
51    /// Premise facts that caused this derivation
52    /// If any of these are retracted, this justification is invalid
53    pub premise_facts: Vec<FactHandle>,
54    
55    /// When this justification was created
56    pub created_at: Instant,
57    
58    /// Unique ID for this justification
59    pub id: u64,
60}
61
62impl Justification {
63    /// Create a new explicit justification (user asserted)
64    pub fn explicit(fact_handle: FactHandle, id: u64) -> Self {
65        Self {
66            fact_handle,
67            justification_type: JustificationType::Explicit,
68            source_rule: None,
69            premise_facts: Vec::new(),
70            created_at: Instant::now(),
71            id,
72        }
73    }
74    
75    /// Create a new logical justification (rule derived)
76    pub fn logical(
77        fact_handle: FactHandle,
78        source_rule: String,
79        premise_facts: Vec<FactHandle>,
80        id: u64,
81    ) -> Self {
82        Self {
83            fact_handle,
84            justification_type: JustificationType::Logical,
85            source_rule: Some(source_rule),
86            premise_facts,
87            created_at: Instant::now(),
88            id,
89        }
90    }
91    
92    /// Check if this justification is still valid
93    /// (all premise facts still exist)
94    pub fn is_valid(&self, retracted_facts: &HashSet<FactHandle>) -> bool {
95        // Explicit facts are always valid
96        if self.justification_type == JustificationType::Explicit {
97            return true;
98        }
99        
100        // Logical facts are valid if all premises still exist
101        !self.premise_facts.iter().any(|h| retracted_facts.contains(h))
102    }
103}
104
105/// Truth Maintenance System
106///
107/// Tracks justifications for facts and automatically maintains consistency
108/// by retracting derived facts when their premises become invalid.
109pub struct TruthMaintenanceSystem {
110    /// All justifications, indexed by justification ID
111    justifications: HashMap<u64, Justification>,
112    
113    /// Map: fact handle → list of justification IDs supporting it
114    fact_justifications: HashMap<FactHandle, Vec<u64>>,
115    
116    /// Map: fact handle → list of justification IDs that depend on it (as premise)
117    fact_dependents: HashMap<FactHandle, Vec<u64>>,
118    
119    /// Facts that are logically asserted (derived by rules)
120    logical_facts: HashSet<FactHandle>,
121    
122    /// Facts that are explicitly asserted (by user)
123    explicit_facts: HashSet<FactHandle>,
124    
125    /// Facts that have been retracted (for validation)
126    retracted_facts: HashSet<FactHandle>,
127    
128    /// Next justification ID
129    next_justification_id: u64,
130}
131
132impl TruthMaintenanceSystem {
133    /// Create a new TMS
134    pub fn new() -> Self {
135        Self {
136            justifications: HashMap::new(),
137            fact_justifications: HashMap::new(),
138            fact_dependents: HashMap::new(),
139            logical_facts: HashSet::new(),
140            explicit_facts: HashSet::new(),
141            retracted_facts: HashSet::new(),
142            next_justification_id: 1,
143        }
144    }
145    
146    /// Add an explicit justification (user asserted fact)
147    pub fn add_explicit_justification(&mut self, fact_handle: FactHandle) {
148        let id = self.next_justification_id;
149        self.next_justification_id += 1;
150        
151        let justification = Justification::explicit(fact_handle, id);
152        
153        self.justifications.insert(id, justification);
154        self.fact_justifications
155            .entry(fact_handle)
156            .or_insert_with(Vec::new)
157            .push(id);
158        
159        self.explicit_facts.insert(fact_handle);
160    }
161    
162    /// Add a logical justification (rule derived fact)
163    ///
164    /// # Arguments
165    /// * `fact_handle` - The derived fact
166    /// * `source_rule` - Name of the rule that derived it
167    /// * `premise_facts` - Facts that were matched in the rule's WHEN clause
168    pub fn add_logical_justification(
169        &mut self,
170        fact_handle: FactHandle,
171        source_rule: String,
172        premise_facts: Vec<FactHandle>,
173    ) {
174        let id = self.next_justification_id;
175        self.next_justification_id += 1;
176        
177        let justification = Justification::logical(
178            fact_handle,
179            source_rule,
180            premise_facts.clone(),
181            id,
182        );
183        
184        // Add to main storage
185        self.justifications.insert(id, justification);
186        
187        // Index by fact
188        self.fact_justifications
189            .entry(fact_handle)
190            .or_insert_with(Vec::new)
191            .push(id);
192        
193        // Index by premises (for dependency tracking)
194        for premise in premise_facts {
195            self.fact_dependents
196                .entry(premise)
197                .or_insert_with(Vec::new)
198                .push(id);
199        }
200        
201        self.logical_facts.insert(fact_handle);
202    }
203    
204    /// Check if a fact is logically asserted (derived by rules)
205    pub fn is_logical(&self, fact_handle: FactHandle) -> bool {
206        self.logical_facts.contains(&fact_handle)
207    }
208    
209    /// Check if a fact is explicitly asserted (by user)
210    pub fn is_explicit(&self, fact_handle: FactHandle) -> bool {
211        self.explicit_facts.contains(&fact_handle)
212    }
213    
214    /// Get all justifications for a fact
215    pub fn get_justifications(&self, fact_handle: FactHandle) -> Vec<&Justification> {
216        if let Some(just_ids) = self.fact_justifications.get(&fact_handle) {
217            just_ids
218                .iter()
219                .filter_map(|id| self.justifications.get(id))
220                .collect()
221        } else {
222            Vec::new()
223        }
224    }
225    
226    /// Check if a fact still has valid justifications
227    pub fn has_valid_justification(&self, fact_handle: FactHandle) -> bool {
228        if let Some(just_ids) = self.fact_justifications.get(&fact_handle) {
229            just_ids
230                .iter()
231                .filter_map(|id| self.justifications.get(id))
232                .any(|j| j.is_valid(&self.retracted_facts))
233        } else {
234            false
235        }
236    }
237    
238    /// Retract a fact and cascade to dependent facts
239    ///
240    /// Returns a list of facts that should be retracted due to cascade
241    pub fn retract_with_cascade(&mut self, fact_handle: FactHandle) -> Vec<FactHandle> {
242        let mut to_retract = Vec::new();
243        
244        // Mark this fact as retracted
245        self.retracted_facts.insert(fact_handle);
246        self.logical_facts.remove(&fact_handle);
247        self.explicit_facts.remove(&fact_handle);
248        
249        // Find all justifications that depend on this fact
250        if let Some(dependent_just_ids) = self.fact_dependents.get(&fact_handle) {
251            for just_id in dependent_just_ids.clone() {
252                if let Some(justification) = self.justifications.get(&just_id) {
253                    let dependent_fact = justification.fact_handle;
254                    
255                    // Check if the dependent fact still has other valid justifications
256                    if !self.has_valid_justification(dependent_fact) {
257                        // No valid justifications left → cascade retract
258                        if !self.retracted_facts.contains(&dependent_fact) {
259                            to_retract.push(dependent_fact);
260                            
261                            // Recursive cascade
262                            let cascaded = self.retract_with_cascade(dependent_fact);
263                            to_retract.extend(cascaded);
264                        }
265                    }
266                }
267            }
268        }
269        
270        to_retract
271    }
272    
273    /// Remove all justifications for a fact
274    pub fn remove_justifications(&mut self, fact_handle: FactHandle) {
275        if let Some(just_ids) = self.fact_justifications.remove(&fact_handle) {
276            for id in just_ids {
277                self.justifications.remove(&id);
278            }
279        }
280    }
281    
282    /// Get statistics about the TMS
283    pub fn stats(&self) -> TmsStats {
284        TmsStats {
285            total_justifications: self.justifications.len(),
286            logical_facts: self.logical_facts.len(),
287            explicit_facts: self.explicit_facts.len(),
288            retracted_facts: self.retracted_facts.len(),
289        }
290    }
291    
292    /// Clear all TMS data
293    pub fn clear(&mut self) {
294        self.justifications.clear();
295        self.fact_justifications.clear();
296        self.fact_dependents.clear();
297        self.logical_facts.clear();
298        self.explicit_facts.clear();
299        self.retracted_facts.clear();
300        self.next_justification_id = 1;
301    }
302    
303    /// Get all logical facts
304    pub fn get_logical_facts(&self) -> &HashSet<FactHandle> {
305        &self.logical_facts
306    }
307    
308    /// Get all explicit facts
309    pub fn get_explicit_facts(&self) -> &HashSet<FactHandle> {
310        &self.explicit_facts
311    }
312}
313
314impl Default for TruthMaintenanceSystem {
315    fn default() -> Self {
316        Self::new()
317    }
318}
319
320/// TMS statistics
321#[derive(Debug, Clone)]
322pub struct TmsStats {
323    pub total_justifications: usize,
324    pub logical_facts: usize,
325    pub explicit_facts: usize,
326    pub retracted_facts: usize,
327}
328
329impl std::fmt::Display for TmsStats {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        write!(
332            f,
333            "TMS Stats: {} justifications, {} logical facts, {} explicit facts, {} retracted",
334            self.total_justifications,
335            self.logical_facts,
336            self.explicit_facts,
337            self.retracted_facts
338        )
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345    
346    #[test]
347    fn test_explicit_justification() {
348        let mut tms = TruthMaintenanceSystem::new();
349        let fact = FactHandle::new(1);
350        
351        tms.add_explicit_justification(fact);
352        
353        assert!(tms.is_explicit(fact));
354        assert!(!tms.is_logical(fact));
355        assert!(tms.has_valid_justification(fact));
356    }
357    
358    #[test]
359    fn test_logical_justification() {
360        let mut tms = TruthMaintenanceSystem::new();
361        let premise = FactHandle::new(1);
362        let derived = FactHandle::new(2);
363        
364        tms.add_explicit_justification(premise);
365        tms.add_logical_justification(derived, "TestRule".to_string(), vec![premise]);
366        
367        assert!(tms.is_logical(derived));
368        assert!(!tms.is_explicit(derived));
369        assert!(tms.has_valid_justification(derived));
370    }
371    
372    #[test]
373    fn test_cascade_retraction() {
374        let mut tms = TruthMaintenanceSystem::new();
375        
376        // Setup: Fact A → Rule → Fact B → Rule → Fact C
377        let fact_a = FactHandle::new(1);
378        let fact_b = FactHandle::new(2);
379        let fact_c = FactHandle::new(3);
380        
381        tms.add_explicit_justification(fact_a);
382        tms.add_logical_justification(fact_b, "Rule1".to_string(), vec![fact_a]);
383        tms.add_logical_justification(fact_c, "Rule2".to_string(), vec![fact_b]);
384        
385        // Retract A → should cascade to B and C
386        let cascaded = tms.retract_with_cascade(fact_a);
387        
388        assert!(cascaded.contains(&fact_b));
389        assert!(cascaded.contains(&fact_c));
390        assert!(!tms.has_valid_justification(fact_b));
391        assert!(!tms.has_valid_justification(fact_c));
392    }
393    
394    #[test]
395    fn test_multiple_justifications() {
396        let mut tms = TruthMaintenanceSystem::new();
397        
398        let premise1 = FactHandle::new(1);
399        let premise2 = FactHandle::new(2);
400        let derived = FactHandle::new(3);
401        
402        tms.add_explicit_justification(premise1);
403        tms.add_explicit_justification(premise2);
404        
405        // Derived fact has TWO justifications
406        tms.add_logical_justification(derived, "Rule1".to_string(), vec![premise1]);
407        tms.add_logical_justification(derived, "Rule2".to_string(), vec![premise2]);
408        
409        // Retract one premise
410        let cascaded = tms.retract_with_cascade(premise1);
411        
412        // Derived fact should STILL be valid (has justification from premise2)
413        assert!(tms.has_valid_justification(derived));
414        assert!(cascaded.is_empty());
415        
416        // Retract second premise
417        let cascaded = tms.retract_with_cascade(premise2);
418        
419        // NOW the derived fact should be retracted
420        assert!(!tms.has_valid_justification(derived));
421        assert!(cascaded.contains(&derived));
422    }
423}