Skip to main content

rust_rule_engine/backward/
backward_engine.rs

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