rust_rule_engine/rete/
propagation.rs

1//! Incremental Propagation Engine (P3 Feature - Advanced)
2//!
3//! This module implements incremental updates similar to Drools:
4//! - Only propagate changed facts through the network
5//! - Track affected rules and activations
6//! - Efficient re-evaluation after updates
7
8use std::collections::{HashMap, HashSet};
9use super::working_memory::{WorkingMemory, FactHandle};
10use super::network::{ReteUlNode, TypedReteUlRule};
11use super::facts::TypedFacts;
12use super::agenda::{AdvancedAgenda, Activation};
13use super::template::TemplateRegistry;
14use super::globals::GlobalsRegistry;
15use super::deffacts::DeffactsRegistry;
16
17/// Track which rules are affected by which fact types
18#[derive(Debug)]
19pub struct RuleDependencyGraph {
20    /// Map: fact_type -> set of rule indices that depend on it
21    fact_type_to_rules: HashMap<String, HashSet<usize>>,
22    /// Map: rule index -> set of fact types it depends on
23    rule_to_fact_types: HashMap<usize, HashSet<String>>,
24}
25
26impl RuleDependencyGraph {
27    /// Create new dependency graph
28    pub fn new() -> Self {
29        Self {
30            fact_type_to_rules: HashMap::new(),
31            rule_to_fact_types: HashMap::new(),
32        }
33    }
34
35    /// Add dependency: rule depends on fact type
36    pub fn add_dependency(&mut self, rule_idx: usize, fact_type: String) {
37        self.fact_type_to_rules
38            .entry(fact_type.clone())
39            .or_insert_with(HashSet::new)
40            .insert(rule_idx);
41
42        self.rule_to_fact_types
43            .entry(rule_idx)
44            .or_insert_with(HashSet::new)
45            .insert(fact_type);
46    }
47
48    /// Get rules affected by a fact type change
49    pub fn get_affected_rules(&self, fact_type: &str) -> HashSet<usize> {
50        self.fact_type_to_rules
51            .get(fact_type)
52            .cloned()
53            .unwrap_or_else(HashSet::new)
54    }
55
56    /// Get fact types that a rule depends on
57    pub fn get_rule_dependencies(&self, rule_idx: usize) -> HashSet<String> {
58        self.rule_to_fact_types
59            .get(&rule_idx)
60            .cloned()
61            .unwrap_or_else(HashSet::new)
62    }
63}
64
65impl Default for RuleDependencyGraph {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71/// Incremental Propagation Engine
72/// Only re-evaluates rules affected by changed facts
73pub struct IncrementalEngine {
74    /// Working memory
75    working_memory: WorkingMemory,
76    /// Rules
77    rules: Vec<TypedReteUlRule>,
78    /// Dependency graph
79    dependencies: RuleDependencyGraph,
80    /// Advanced agenda
81    agenda: AdvancedAgenda,
82    /// Track which facts each rule last matched
83    rule_matched_facts: HashMap<usize, HashSet<FactHandle>>,
84    /// Template registry for type-safe facts
85    templates: TemplateRegistry,
86    /// Global variables registry
87    globals: GlobalsRegistry,
88    /// Deffacts registry for initial facts
89    deffacts: DeffactsRegistry,
90}
91
92impl IncrementalEngine {
93    /// Create new incremental engine
94    pub fn new() -> Self {
95        Self {
96            working_memory: WorkingMemory::new(),
97            rules: Vec::new(),
98            dependencies: RuleDependencyGraph::new(),
99            agenda: AdvancedAgenda::new(),
100            rule_matched_facts: HashMap::new(),
101            templates: TemplateRegistry::new(),
102            globals: GlobalsRegistry::new(),
103            deffacts: DeffactsRegistry::new(),
104        }
105    }
106
107    /// Add rule and register its dependencies
108    pub fn add_rule(&mut self, rule: TypedReteUlRule, depends_on: Vec<String>) {
109        let rule_idx = self.rules.len();
110
111        // Register dependencies
112        for fact_type in depends_on {
113            self.dependencies.add_dependency(rule_idx, fact_type);
114        }
115
116        self.rules.push(rule);
117    }
118
119    /// Insert fact into working memory
120    pub fn insert(&mut self, fact_type: String, data: TypedFacts) -> FactHandle {
121        let handle = self.working_memory.insert(fact_type.clone(), data);
122
123        // Trigger incremental propagation for this fact type
124        self.propagate_changes_for_type(&fact_type);
125
126        handle
127    }
128
129    /// Update fact in working memory
130    pub fn update(&mut self, handle: FactHandle, data: TypedFacts) -> Result<(), String> {
131        // Get fact type before update
132        let fact_type = self.working_memory
133            .get(&handle)
134            .map(|f| f.fact_type.clone())
135            .ok_or_else(|| format!("FactHandle {} not found", handle))?;
136
137        self.working_memory.update(handle, data)?;
138
139        // Trigger incremental propagation for this fact type
140        self.propagate_changes_for_type(&fact_type);
141
142        Ok(())
143    }
144
145    /// Retract fact from working memory
146    pub fn retract(&mut self, handle: FactHandle) -> Result<(), String> {
147        // Get fact type before retract
148        let fact_type = self.working_memory
149            .get(&handle)
150            .map(|f| f.fact_type.clone())
151            .ok_or_else(|| format!("FactHandle {} not found", handle))?;
152
153        self.working_memory.retract(handle)?;
154
155        // Trigger incremental propagation for this fact type
156        self.propagate_changes_for_type(&fact_type);
157
158        Ok(())
159    }
160
161    /// Propagate changes for a specific fact type (incremental!)
162    fn propagate_changes_for_type(&mut self, fact_type: &str) {
163        // Get affected rules
164        let affected_rules = self.dependencies.get_affected_rules(fact_type);
165
166        if affected_rules.is_empty() {
167            return; // No rules depend on this fact type
168        }
169
170        // Flatten working memory to TypedFacts for evaluation
171        let facts = self.working_memory.to_typed_facts();
172
173        // Re-evaluate only affected rules
174        for &rule_idx in &affected_rules {
175            let rule = &self.rules[rule_idx];
176
177            // Evaluate rule condition
178            let matches = super::network::evaluate_rete_ul_node_typed(&rule.node, &facts);
179
180            if matches {
181                // Create activation
182                let activation = Activation::new(rule.name.clone(), rule.priority)
183                    .with_no_loop(rule.no_loop);
184
185                self.agenda.add_activation(activation);
186            }
187        }
188    }
189
190    /// Fire all pending activations
191    pub fn fire_all(&mut self) -> Vec<String> {
192        let mut fired_rules = Vec::new();
193
194        while let Some(activation) = self.agenda.get_next_activation() {
195            // Find rule
196            if let Some((idx, rule)) = self.rules
197                .iter_mut()
198                .enumerate()
199                .find(|(_, r)| r.name == activation.rule_name)
200            {
201                // Execute action
202                let mut facts = self.working_memory.to_typed_facts();
203                (rule.action)(&mut facts);
204
205                // Track fired rule
206                fired_rules.push(activation.rule_name.clone());
207                self.agenda.mark_rule_fired(&activation);
208
209                // TODO: Update working memory with changed facts
210                // This is complex and would require tracking what changed
211            }
212        }
213
214        fired_rules
215    }
216
217    /// Get working memory
218    pub fn working_memory(&self) -> &WorkingMemory {
219        &self.working_memory
220    }
221
222    /// Get mutable working memory
223    pub fn working_memory_mut(&mut self) -> &mut WorkingMemory {
224        &mut self.working_memory
225    }
226
227    /// Get agenda
228    pub fn agenda(&self) -> &AdvancedAgenda {
229        &self.agenda
230    }
231
232    /// Get mutable agenda
233    pub fn agenda_mut(&mut self) -> &mut AdvancedAgenda {
234        &mut self.agenda
235    }
236
237    /// Get statistics
238    pub fn stats(&self) -> IncrementalEngineStats {
239        IncrementalEngineStats {
240            rules: self.rules.len(),
241            working_memory: self.working_memory.stats(),
242            agenda: self.agenda.stats(),
243            dependencies: self.dependencies.fact_type_to_rules.len(),
244        }
245    }
246
247    /// Clear fired flags and reset agenda
248    pub fn reset(&mut self) {
249        self.agenda.reset_fired_flags();
250    }
251
252    /// Get template registry
253    pub fn templates(&self) -> &TemplateRegistry {
254        &self.templates
255    }
256
257    /// Get mutable template registry
258    pub fn templates_mut(&mut self) -> &mut TemplateRegistry {
259        &mut self.templates
260    }
261
262    /// Get global variables registry
263    pub fn globals(&self) -> &GlobalsRegistry {
264        &self.globals
265    }
266
267    /// Get mutable global variables registry
268    pub fn globals_mut(&mut self) -> &mut GlobalsRegistry {
269        &mut self.globals
270    }
271
272    /// Get deffacts registry
273    pub fn deffacts(&self) -> &DeffactsRegistry {
274        &self.deffacts
275    }
276
277    /// Get mutable deffacts registry
278    pub fn deffacts_mut(&mut self) -> &mut DeffactsRegistry {
279        &mut self.deffacts
280    }
281
282    /// Load all registered deffacts into working memory
283    /// Returns handles of all inserted facts
284    pub fn load_deffacts(&mut self) -> Vec<FactHandle> {
285        let mut handles = Vec::new();
286
287        // Get all facts from all registered deffacts
288        let all_facts = self.deffacts.get_all_facts();
289
290        for (_deffacts_name, fact_instance) in all_facts {
291            // Check if template exists for this fact type
292            let handle = if self.templates.get(&fact_instance.fact_type).is_some() {
293                // Use template validation
294                match self.insert_with_template(&fact_instance.fact_type, fact_instance.data) {
295                    Ok(h) => h,
296                    Err(_) => continue, // Skip invalid facts
297                }
298            } else {
299                // Insert without template validation
300                self.insert(fact_instance.fact_type, fact_instance.data)
301            };
302
303            handles.push(handle);
304        }
305
306        handles
307    }
308
309    /// Load a specific deffacts set by name
310    /// Returns handles of inserted facts or error if deffacts not found
311    pub fn load_deffacts_by_name(&mut self, name: &str) -> crate::errors::Result<Vec<FactHandle>> {
312        // Clone the facts to avoid borrow checker issues
313        let facts_to_insert = {
314            let deffacts = self.deffacts.get(name).ok_or_else(|| {
315                crate::errors::RuleEngineError::EvaluationError {
316                    message: format!("Deffacts '{}' not found", name),
317                }
318            })?;
319            deffacts.facts.clone()
320        };
321
322        let mut handles = Vec::new();
323
324        for fact_instance in facts_to_insert {
325            // Check if template exists for this fact type
326            let handle = if self.templates.get(&fact_instance.fact_type).is_some() {
327                // Use template validation
328                self.insert_with_template(&fact_instance.fact_type, fact_instance.data)?
329            } else {
330                // Insert without template validation
331                self.insert(fact_instance.fact_type, fact_instance.data)
332            };
333
334            handles.push(handle);
335        }
336
337        Ok(handles)
338    }
339
340    /// Reset engine and reload all deffacts (similar to CLIPS reset)
341    /// Clears working memory and agenda, then loads all deffacts
342    pub fn reset_with_deffacts(&mut self) -> Vec<FactHandle> {
343        // Clear working memory and agenda
344        self.working_memory = WorkingMemory::new();
345        self.agenda.clear();
346        self.rule_matched_facts.clear();
347
348        // Reload all deffacts
349        self.load_deffacts()
350    }
351
352    /// Insert a typed fact with template validation
353    pub fn insert_with_template(
354        &mut self,
355        template_name: &str,
356        data: TypedFacts,
357    ) -> crate::errors::Result<FactHandle> {
358        // Validate against template
359        self.templates.validate(template_name, &data)?;
360
361        // Insert into working memory
362        Ok(self.insert(template_name.to_string(), data))
363    }
364}
365
366impl Default for IncrementalEngine {
367    fn default() -> Self {
368        Self::new()
369    }
370}
371
372/// Engine statistics
373#[derive(Debug)]
374pub struct IncrementalEngineStats {
375    pub rules: usize,
376    pub working_memory: super::working_memory::WorkingMemoryStats,
377    pub agenda: super::agenda::AgendaStats,
378    pub dependencies: usize,
379}
380
381impl std::fmt::Display for IncrementalEngineStats {
382    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383        write!(
384            f,
385            "Engine Stats: {} rules, {} fact types tracked\nWM: {}\nAgenda: {}",
386            self.rules,
387            self.dependencies,
388            self.working_memory,
389            self.agenda
390        )
391    }
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397    use crate::rete::network::ReteUlNode;
398    use crate::rete::alpha::AlphaNode;
399
400    #[test]
401    fn test_dependency_graph() {
402        let mut graph = RuleDependencyGraph::new();
403
404        graph.add_dependency(0, "Person".to_string());
405        graph.add_dependency(1, "Person".to_string());
406        graph.add_dependency(1, "Order".to_string());
407
408        let affected = graph.get_affected_rules("Person");
409        assert_eq!(affected.len(), 2);
410        assert!(affected.contains(&0));
411        assert!(affected.contains(&1));
412
413        let deps = graph.get_rule_dependencies(1);
414        assert_eq!(deps.len(), 2);
415        assert!(deps.contains("Person"));
416        assert!(deps.contains("Order"));
417    }
418
419    #[test]
420    fn test_incremental_propagation() {
421        let mut engine = IncrementalEngine::new();
422
423        // Add rule that depends on "Person" type
424        let node = ReteUlNode::UlAlpha(AlphaNode {
425            field: "Person.age".to_string(),
426            operator: ">".to_string(),
427            value: "18".to_string(),
428        });
429
430        let rule = TypedReteUlRule {
431            name: "IsAdult".to_string(),
432            node,
433            priority: 0,
434            no_loop: true,
435            action: Box::new(|_| {}),
436        };
437
438        engine.add_rule(rule, vec!["Person".to_string()]);
439
440        // Insert Person fact
441        let mut person = TypedFacts::new();
442        person.set("age", 25i64);
443        let handle = engine.insert("Person".to_string(), person);
444
445        // Check that rule was activated
446        let stats = engine.stats();
447        assert!(stats.agenda.total_activations > 0);
448
449        // Update person
450        let mut updated = TypedFacts::new();
451        updated.set("age", 15i64); // Now under 18
452        engine.update(handle, updated).unwrap();
453
454        // Rule should be re-evaluated (incrementally)
455    }
456}