rust_rule_engine/backward/
backward_engine.rs

1//! Backward chaining engine implementation
2
3use super::goal::{Goal, GoalManager, GoalStatus};
4use super::search::{DepthFirstSearch, BreadthFirstSearch, IterativeDeepeningSearch, SearchStrategy, SearchResult};
5use super::query::{QueryParser, QueryResult, QueryStats, ProofTrace};
6use crate::{Facts, KnowledgeBase};
7use crate::errors::Result;
8use std::sync::Arc;
9
10/// Configuration for backward chaining engine
11#[derive(Debug, Clone)]
12pub struct BackwardConfig {
13    /// Maximum depth for goal search
14    pub max_depth: usize,
15    
16    /// Search strategy to use
17    pub strategy: SearchStrategy,
18    
19    /// Enable memoization of proven goals
20    pub enable_memoization: bool,
21    
22    /// Maximum number of solutions to find
23    pub max_solutions: usize,
24}
25
26impl Default for BackwardConfig {
27    fn default() -> Self {
28        Self {
29            max_depth: 10,
30            strategy: SearchStrategy::DepthFirst,
31            enable_memoization: true,
32            max_solutions: 1,
33        }
34    }
35}
36
37/// Backward chaining engine for goal-driven reasoning
38pub struct BackwardEngine {
39    knowledge_base: Arc<KnowledgeBase>,
40    config: BackwardConfig,
41    goal_manager: GoalManager,
42}
43
44impl BackwardEngine {
45    /// Create a new backward chaining engine
46    pub fn new(kb: KnowledgeBase) -> Self {
47        Self {
48            knowledge_base: Arc::new(kb),
49            config: BackwardConfig::default(),
50            goal_manager: GoalManager::default(),
51        }
52    }
53    
54    /// Create with custom configuration
55    pub fn with_config(kb: KnowledgeBase, config: BackwardConfig) -> Self {
56        Self {
57            knowledge_base: Arc::new(kb),
58            goal_manager: GoalManager::new(config.max_depth),
59            config,
60        }
61    }
62    
63    /// Update configuration
64    pub fn set_config(&mut self, config: BackwardConfig) {
65        self.goal_manager = GoalManager::new(config.max_depth);
66        self.config = config;
67    }
68    
69    /// Query whether a goal can be proven
70    ///
71    /// # Example
72    ///
73    /// ```ignore
74    /// let engine = BackwardEngine::new(kb);
75    /// let result = engine.query("User.IsVIP == true", &mut facts)?;
76    ///
77    /// if result.provable {
78    ///     println!("User is VIP!");
79    /// }
80    /// ```
81    pub fn query(&mut self, query_str: &str, facts: &mut Facts) -> Result<QueryResult> {
82        // Backward-compatible: no RETE engine provided
83        self.query_with_rete_engine(query_str, facts, None)
84    }
85
86    /// Query with optional RETE IncrementalEngine for TMS integration
87    pub fn query_with_rete_engine(
88        &mut self,
89        query_str: &str,
90        facts: &mut Facts,
91        rete_engine: Option<std::sync::Arc<std::sync::Mutex<crate::rete::propagation::IncrementalEngine>>>,
92    ) -> Result<QueryResult> {
93        // Parse query into goal
94        let mut goal = QueryParser::parse(query_str)
95            .map_err(|e| crate::errors::RuleEngineError::ParseError { message: e })?;
96
97        // Check cache if memoization enabled
98        if self.config.enable_memoization {
99            if let Some(cached) = self.goal_manager.is_cached(query_str) {
100                return Ok(if cached {
101                    QueryResult::success(
102                        goal.bindings.to_map(), // Convert Bindings to HashMap
103                        ProofTrace::from_goal(&goal),
104                        QueryStats::default(),
105                    )
106                } else {
107                    QueryResult::failure(vec![], QueryStats::default())
108                });
109            }
110        }
111
112        // Find candidate rules that can prove this goal
113        self.find_candidate_rules(&mut goal)?;
114
115        // Execute search strategy with optional rete_engine
116        let search_result = match self.config.strategy {
117            SearchStrategy::DepthFirst => {
118                let mut dfs = DepthFirstSearch::new_with_engine(self.config.max_depth, (*self.knowledge_base).clone(), rete_engine.clone());
119                dfs.search_with_execution(&mut goal, facts, &self.knowledge_base)
120            }
121            SearchStrategy::BreadthFirst => {
122                let mut bfs = BreadthFirstSearch::new_with_engine(self.config.max_depth, (*self.knowledge_base).clone(), rete_engine.clone());
123                bfs.search_with_execution(&mut goal, facts, &self.knowledge_base)
124            }
125            SearchStrategy::Iterative => {
126                let mut ids = IterativeDeepeningSearch::new_with_engine(self.config.max_depth, (*self.knowledge_base).clone(), rete_engine.clone());
127                ids.search_with_execution(&mut goal, facts, &self.knowledge_base)
128            }
129        };
130
131        // Cache result if enabled
132        if self.config.enable_memoization {
133            self.goal_manager.cache_result(query_str.to_string(), search_result.success);
134        }
135
136        // Build query result
137        let stats = QueryStats {
138            goals_explored: search_result.goals_explored,
139            rules_evaluated: search_result.path.len(),
140            max_depth: search_result.max_depth_reached,
141            duration_ms: None,
142        };
143
144        Ok(if search_result.success {
145            QueryResult::success(
146                search_result.bindings,
147                ProofTrace::from_goal(&goal),
148                stats,
149            )
150        } else {
151            QueryResult::failure(self.find_missing_facts(&goal), stats)
152        })
153    }
154    
155    /// Find all candidate rules that could prove a goal
156    fn find_candidate_rules(&self, goal: &mut Goal) -> Result<()> {
157        // In a full implementation, we would:
158        // 1. Parse goal pattern
159        // 2. Find rules whose conclusions match the goal
160        // 3. Add them as candidate rules
161
162        // For now, add all rules as candidates
163        for rule in self.knowledge_base.get_rules() {
164            // Simple check: if rule name or actions might prove this goal
165            if self.rule_could_prove_goal(&rule, goal) {
166                goal.add_candidate_rule(rule.name.clone());
167            }
168        }
169
170        Ok(())
171    }
172    
173    /// Check if a rule could potentially prove a goal
174    fn rule_could_prove_goal(&self, rule: &crate::engine::rule::Rule, goal: &Goal) -> bool {
175        // Simple heuristic: check if goal pattern appears in rule actions
176        // In a full implementation, this would be more sophisticated
177
178        // Check rule name
179        if goal.pattern.contains(&rule.name) {
180            return true;
181        }
182
183        // Check actions
184        for action in &rule.actions {
185            match action {
186                crate::types::ActionType::Set { field, .. } => {
187                    if goal.pattern.contains(field) {
188                        return true;
189                    }
190                }
191                crate::types::ActionType::MethodCall { object, method, .. } => {
192                    if goal.pattern.contains(object) || goal.pattern.contains(method) {
193                        return true;
194                    }
195                }
196                _ => {}
197            }
198        }
199
200        false
201    }
202
203    /// Find what facts are missing to prove a goal
204    fn find_missing_facts(&self, goal: &Goal) -> Vec<String> {
205        let mut missing = Vec::new();
206        
207        // Collect unprovable sub-goals
208        self.collect_missing_recursive(goal, &mut missing);
209        
210        missing
211    }
212    
213    fn collect_missing_recursive(&self, goal: &Goal, missing: &mut Vec<String>) {
214        if goal.status == GoalStatus::Unprovable {
215            missing.push(goal.pattern.clone());
216        }
217        
218        for sub_goal in &goal.sub_goals {
219            self.collect_missing_recursive(sub_goal, missing);
220        }
221    }
222    
223    /// Explain why a goal was proven (or not)
224    pub fn explain_why(&mut self, query_str: &str, facts: &mut Facts) -> Result<String> {
225        let result = self.query(query_str, facts)?;
226
227        if result.provable {
228            Ok(format!(
229                "Goal '{}' is PROVABLE\nProof trace:\n{:#?}",
230                query_str, result.proof_trace
231            ))
232        } else {
233            Ok(format!(
234                "Goal '{}' is NOT provable\nMissing facts: {:?}",
235                query_str, result.missing_facts
236            ))
237        }
238    }
239    
240    /// Get the configuration
241    pub fn config(&self) -> &BackwardConfig {
242        &self.config
243    }
244    
245    /// Get the knowledge base
246    pub fn knowledge_base(&self) -> &KnowledgeBase {
247        &self.knowledge_base
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254    use crate::KnowledgeBase;
255    use crate::types::Value;
256    
257    #[test]
258    fn test_engine_creation() {
259        let kb = KnowledgeBase::new("test");
260        let engine = BackwardEngine::new(kb);
261        
262        assert_eq!(engine.config.max_depth, 10);
263        assert_eq!(engine.config.strategy, SearchStrategy::DepthFirst);
264    }
265    
266    #[test]
267    fn test_config_customization() {
268        let kb = KnowledgeBase::new("test");
269        let config = BackwardConfig {
270            max_depth: 5,
271            strategy: SearchStrategy::BreadthFirst,
272            enable_memoization: false,
273            max_solutions: 10,
274        };
275        
276        let engine = BackwardEngine::with_config(kb, config);
277        assert_eq!(engine.config.max_depth, 5);
278        assert_eq!(engine.config.strategy, SearchStrategy::BreadthFirst);
279        assert!(!engine.config.enable_memoization);
280    }
281    
282    #[test]
283    fn test_query_simple() {
284        let kb = KnowledgeBase::new("test");
285        let mut engine = BackwardEngine::new(kb);
286        let mut facts = Facts::new();
287
288        // This will fail because no rules, but tests the flow
289        let result = engine.query("User.IsVIP == true", &mut facts);
290        assert!(result.is_ok());
291    }
292
293    #[test]
294    fn test_function_call_condition_len() {
295        use crate::engine::rule::{Rule, Condition, ConditionGroup};
296        use crate::types::{Operator, ActionType};
297
298        let mut kb = KnowledgeBase::new("test");
299
300        // Rule: If User.Name length > 3, then User.HasLongName = true
301        let conditions = ConditionGroup::Single(
302            Condition::with_function(
303                "len".to_string(),
304                vec!["User.Name".to_string()],
305                Operator::GreaterThan,
306                Value::Number(3.0),
307            )
308        );
309        let actions = vec![ActionType::Set {
310            field: "User.HasLongName".to_string(),
311            value: Value::Boolean(true),
312        }];
313
314        let rule = Rule::new("CheckNameLength".to_string(), conditions, actions);
315        kb.add_rule(rule);
316
317        let mut engine = BackwardEngine::new(kb);
318        let mut facts = Facts::new();
319        facts.set("User.Name", Value::String("John".to_string()));
320
321        // Query if User.HasLongName == true
322        let result = engine.query("User.HasLongName == true", &mut facts);
323        assert!(result.is_ok());
324        let query_result = result.unwrap();
325
326        // Should be provable because "John".len() = 4 > 3
327        assert!(query_result.provable, "Query should be provable with len() function");
328    }
329
330    #[test]
331    fn test_function_call_condition_isempty() {
332        use crate::engine::rule::{Rule, Condition, ConditionGroup};
333        use crate::types::{Operator, ActionType};
334
335        let mut kb = KnowledgeBase::new("test");
336
337        // Rule: If User.Description is NOT empty, then User.HasDescription = true
338        let conditions = ConditionGroup::Single(
339            Condition::with_function(
340                "isEmpty".to_string(),
341                vec!["User.Description".to_string()],
342                Operator::Equal,
343                Value::Boolean(false),
344            )
345        );
346        let actions = vec![ActionType::Set {
347            field: "User.HasDescription".to_string(),
348            value: Value::Boolean(true),
349        }];
350
351        let rule = Rule::new("CheckDescription".to_string(), conditions, actions);
352        kb.add_rule(rule);
353
354        let mut engine = BackwardEngine::new(kb);
355        let mut facts = Facts::new();
356        facts.set("User.Description", Value::String("A great user".to_string()));
357
358        let result = engine.query("User.HasDescription == true", &mut facts);
359        assert!(result.is_ok());
360        let query_result = result.unwrap();
361
362        // Should be provable because description is not empty
363        assert!(query_result.provable, "Query should be provable with isEmpty() function");
364    }
365
366    #[test]
367    fn test_test_expression_exists() {
368        use crate::engine::rule::{Rule, Condition, ConditionGroup, ConditionExpression};
369        use crate::types::{Operator, ActionType};
370
371        let mut kb = KnowledgeBase::new("test");
372
373        // Rule: If User.Email exists, then User.HasEmail = true
374        let condition = Condition {
375            field: "User.Email".to_string(),  // Required field
376            expression: ConditionExpression::Test {
377                name: "exists".to_string(),
378                args: vec!["User.Email".to_string()],
379            },
380            operator: Operator::Equal,
381            value: Value::Boolean(true),
382        };
383        let conditions = ConditionGroup::Single(condition);
384        let actions = vec![ActionType::Set {
385            field: "User.HasEmail".to_string(),
386            value: Value::Boolean(true),
387        }];
388
389        let rule = Rule::new("CheckEmail".to_string(), conditions, actions);
390        kb.add_rule(rule);
391
392        let mut engine = BackwardEngine::new(kb);
393        let mut facts = Facts::new();
394        facts.set("User.Email", Value::String("user@example.com".to_string()));
395
396        let result = engine.query("User.HasEmail == true", &mut facts);
397        assert!(result.is_ok());
398        let query_result = result.unwrap();
399
400        // Should be provable because User.Email exists
401        assert!(query_result.provable, "Query should be provable with exists test");
402    }
403
404    #[test]
405    fn test_multifield_count_operation() {
406        use crate::engine::rule::{Rule, Condition, ConditionGroup, ConditionExpression};
407        use crate::types::{Operator, ActionType};
408
409        let mut kb = KnowledgeBase::new("test");
410
411        // Rule: If User.Orders count > 5, then User.IsFrequentBuyer = true
412        let condition = Condition {
413            field: "User.Orders".to_string(),  // Required field
414            expression: ConditionExpression::MultiField {
415                field: "User.Orders".to_string(),
416                operation: "count".to_string(),
417                variable: None,
418            },
419            operator: Operator::GreaterThan,
420            value: Value::Number(5.0),
421        };
422        let conditions = ConditionGroup::Single(condition);
423        let actions = vec![ActionType::Set {
424            field: "User.IsFrequentBuyer".to_string(),
425            value: Value::Boolean(true),
426        }];
427
428        let rule = Rule::new("CheckFrequentBuyer".to_string(), conditions, actions);
429        kb.add_rule(rule);
430
431        let mut engine = BackwardEngine::new(kb);
432        let mut facts = Facts::new();
433
434        // Create array of 6 orders
435        let orders = Value::Array(vec![
436            Value::Number(1.0),
437            Value::Number(2.0),
438            Value::Number(3.0),
439            Value::Number(4.0),
440            Value::Number(5.0),
441            Value::Number(6.0),
442        ]);
443        facts.set("User.Orders", orders);
444
445        let result = engine.query("User.IsFrequentBuyer == true", &mut facts);
446        assert!(result.is_ok());
447        let query_result = result.unwrap();
448
449        // Should be provable because orders.count() = 6 > 5
450        assert!(query_result.provable, "Query should be provable with count multifield operation");
451    }
452
453    #[test]
454    fn test_fact_derivation_basic() {
455        use crate::engine::rule::{Rule, Condition, ConditionGroup};
456        use crate::types::{Operator, ActionType};
457
458        let mut kb = KnowledgeBase::new("test");
459
460        // Rule: If User.Age > 18, then User.IsAdult = true
461        let conditions = ConditionGroup::Single(
462            Condition::new(
463                "User.Age".to_string(),
464                Operator::GreaterThan,
465                Value::Number(18.0),
466            )
467        );
468        let actions = vec![ActionType::Set {
469            field: "User.IsAdult".to_string(),
470            value: Value::Boolean(true),
471        }];
472
473        let rule = Rule::new("DetermineAdult".to_string(), conditions, actions);
474        kb.add_rule(rule);
475
476        let mut engine = BackwardEngine::new(kb);
477        let mut facts = Facts::new();
478        facts.set("User.Age", Value::Number(25.0));
479
480        // Query will trigger rule execution which should set User.IsAdult
481        let result = engine.query("User.IsAdult == true", &mut facts);
482        assert!(result.is_ok());
483        let query_result = result.unwrap();
484
485        assert!(query_result.provable, "Fact should be derived by rule action");
486    }
487
488    #[test]
489    fn test_rule_chaining_two_levels() {
490        use crate::engine::rule::{Rule, Condition, ConditionGroup};
491        use crate::types::{Operator, ActionType, LogicalOperator};
492
493        let mut kb = KnowledgeBase::new("test");
494
495        // Rule 1: If User.LoyaltyPoints > 100, then User.IsVIP = true
496        let conditions1 = ConditionGroup::Single(
497            Condition::new(
498                "User.LoyaltyPoints".to_string(),
499                Operator::GreaterThan,
500                Value::Number(100.0),
501            )
502        );
503        let actions1 = vec![ActionType::Set {
504            field: "User.IsVIP".to_string(),
505            value: Value::Boolean(true),
506        }];
507        let rule1 = Rule::new("DetermineVIP".to_string(), conditions1, actions1);
508
509        // Rule 2: If User.IsVIP == true AND Order.Amount < 10000, then Order.AutoApproved = true
510        let conditions2 = ConditionGroup::Compound {
511            left: Box::new(ConditionGroup::Single(
512                Condition::new(
513                    "User.IsVIP".to_string(),
514                    Operator::Equal,
515                    Value::Boolean(true),
516                )
517            )),
518            operator: LogicalOperator::And,
519            right: Box::new(ConditionGroup::Single(
520                Condition::new(
521                    "Order.Amount".to_string(),
522                    Operator::LessThan,
523                    Value::Number(10000.0),
524                )
525            )),
526        };
527        let actions2 = vec![ActionType::Set {
528            field: "Order.AutoApproved".to_string(),
529            value: Value::Boolean(true),
530        }];
531        let rule2 = Rule::new("AutoApproveVIP".to_string(), conditions2, actions2);
532
533        kb.add_rule(rule1);
534        kb.add_rule(rule2);
535
536        let mut engine = BackwardEngine::new(kb);
537        let mut facts = Facts::new();
538        facts.set("User.LoyaltyPoints", Value::Number(150.0));
539        facts.set("Order.Amount", Value::Number(5000.0));
540
541        // Query Order.AutoApproved - should chain through:
542        // 1. Check Order.AutoApproved rule (rule2)
543        // 2. Needs User.IsVIP == true
544        // 3. Check User.IsVIP rule (rule1)
545        // 4. Verify User.LoyaltyPoints > 100 (satisfied)
546        // 5. Execute rule1 action -> sets User.IsVIP = true
547        // 6. Now rule2 conditions satisfied -> sets Order.AutoApproved = true
548        let result = engine.query("Order.AutoApproved == true", &mut facts);
549        assert!(result.is_ok());
550        let query_result = result.unwrap();
551
552        assert!(query_result.provable, "Query should be provable through 2-level rule chaining");
553    }
554
555    #[test]
556    fn test_fact_derivation_with_log_action() {
557        use crate::engine::rule::{Rule, Condition, ConditionGroup};
558        use crate::types::{Operator, ActionType};
559
560        let mut kb = KnowledgeBase::new("test");
561
562        // Rule: If User.Score > 90, log message and set HighScore
563        let conditions = ConditionGroup::Single(
564            Condition::new(
565                "User.Score".to_string(),
566                Operator::GreaterThan,
567                Value::Number(90.0),
568            )
569        );
570        let actions = vec![
571            ActionType::Log {
572                message: "High score achieved!".to_string(),
573            },
574            ActionType::Set {
575                field: "User.HasHighScore".to_string(),
576                value: Value::Boolean(true),
577            }
578        ];
579
580        let rule = Rule::new("HighScoreRule".to_string(), conditions, actions);
581        kb.add_rule(rule);
582
583        let mut engine = BackwardEngine::new(kb);
584        let mut facts = Facts::new();
585        facts.set("User.Score", Value::Number(95.0));
586
587        let result = engine.query("User.HasHighScore == true", &mut facts);
588        assert!(result.is_ok());
589        let query_result = result.unwrap();
590
591        // Should be provable and log action should have been executed
592        assert!(query_result.provable, "Query should be provable with log action");
593    }
594}