rust_rule_engine/backward/
backward_engine.rs

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