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, .. } => {
226                    if goal.pattern.contains(field) {
227                        return true;
228                    }
229                }
230                crate::types::ActionType::MethodCall { object, method, .. } => {
231                    if goal.pattern.contains(object) || goal.pattern.contains(method) {
232                        return true;
233                    }
234                }
235                _ => {}
236            }
237        }
238
239        false
240    }
241
242    /// Find what facts are missing to prove a goal
243    fn find_missing_facts(&self, goal: &Goal) -> Vec<String> {
244        let mut missing = Vec::new();
245
246        // Collect unprovable sub-goals
247        Self::collect_missing_recursive(goal, &mut missing);
248
249        missing
250    }
251
252    fn collect_missing_recursive(goal: &Goal, missing: &mut Vec<String>) {
253        if goal.status == GoalStatus::Unprovable {
254            missing.push(goal.pattern.clone());
255        }
256
257        for sub_goal in &goal.sub_goals {
258            Self::collect_missing_recursive(sub_goal, missing);
259        }
260    }
261
262    /// Explain why a goal was proven (or not)
263    pub fn explain_why(&mut self, query_str: &str, facts: &mut Facts) -> Result<String> {
264        let result = self.query(query_str, facts)?;
265
266        if result.provable {
267            Ok(format!(
268                "Goal '{}' is PROVABLE\nProof trace:\n{:#?}",
269                query_str, result.proof_trace
270            ))
271        } else {
272            Ok(format!(
273                "Goal '{}' is NOT provable\nMissing facts: {:?}",
274                query_str, result.missing_facts
275            ))
276        }
277    }
278
279    /// Get the configuration
280    pub fn config(&self) -> &BackwardConfig {
281        &self.config
282    }
283
284    /// Get the knowledge base
285    pub fn knowledge_base(&self) -> &KnowledgeBase {
286        &self.knowledge_base
287    }
288
289    /// Get conclusion index statistics
290    pub fn index_stats(&self) -> super::conclusion_index::IndexStats {
291        self.conclusion_index.stats()
292    }
293
294    /// Rebuild the conclusion index (call after modifying knowledge base)
295    pub fn rebuild_index(&mut self) {
296        let rules = self.knowledge_base.get_rules();
297        self.conclusion_index = ConclusionIndex::from_rules(&rules);
298    }
299
300    /// Query with aggregation functions
301    ///
302    /// Supports: count, sum, avg, min, max, first, last
303    ///
304    /// # Example
305    ///
306    /// ```ignore
307    /// // Count all employees
308    /// let count = engine.query_aggregate(
309    ///     "count(?x) WHERE employee(?x)",
310    ///     &mut facts
311    /// )?;
312    ///
313    /// // Sum of salaries over 50000
314    /// let total = engine.query_aggregate(
315    ///     "sum(?salary) WHERE salary(?name, ?salary) AND ?salary > 50000",
316    ///     &mut facts
317    /// )?;
318    ///
319    /// // Average price
320    /// let avg_price = engine.query_aggregate(
321    ///     "avg(?price) WHERE product(?name, ?price)",
322    ///     &mut facts
323    /// )?;
324    /// ```
325    pub fn query_aggregate(
326        &mut self,
327        query: &str,
328        facts: &mut Facts,
329    ) -> Result<crate::types::Value> {
330        use super::aggregation::{apply_aggregate, parse_aggregate_query};
331
332        // Parse the aggregate query
333        let agg_query = parse_aggregate_query(query)?;
334
335        // Set max_solutions to unlimited for aggregation
336        let original_max = self.config.max_solutions;
337        self.config.max_solutions = usize::MAX;
338
339        // Execute the underlying pattern query to get all solutions
340        let result = self.query(&agg_query.pattern, facts)?;
341
342        // Restore original max_solutions
343        self.config.max_solutions = original_max;
344
345        // Apply aggregation to solutions
346        let value = apply_aggregate(&agg_query.function, &result.solutions)?;
347
348        Ok(value)
349    }
350}
351
352#[cfg(test)]
353#[allow(deprecated)]
354mod tests {
355    use super::*;
356    use crate::types::Value;
357    use crate::KnowledgeBase;
358
359    #[test]
360    fn test_engine_creation() {
361        let kb = KnowledgeBase::new("test");
362        let engine = BackwardEngine::new(kb);
363
364        assert_eq!(engine.config.max_depth, 10);
365        assert_eq!(engine.config.strategy, SearchStrategy::DepthFirst);
366    }
367
368    #[test]
369    fn test_config_customization() {
370        let kb = KnowledgeBase::new("test");
371        let config = BackwardConfig {
372            max_depth: 5,
373            strategy: SearchStrategy::BreadthFirst,
374            enable_memoization: false,
375            max_solutions: 10,
376        };
377
378        let engine = BackwardEngine::with_config(kb, config);
379        assert_eq!(engine.config.max_depth, 5);
380        assert_eq!(engine.config.strategy, SearchStrategy::BreadthFirst);
381        assert!(!engine.config.enable_memoization);
382    }
383
384    #[test]
385    fn test_query_simple() {
386        let kb = KnowledgeBase::new("test");
387        let mut engine = BackwardEngine::new(kb);
388        let mut facts = Facts::new();
389
390        // This will fail because no rules, but tests the flow
391        let result = engine.query("User.IsVIP == true", &mut facts);
392        assert!(result.is_ok());
393    }
394
395    #[test]
396    fn test_function_call_condition_len() {
397        use crate::engine::rule::{Condition, ConditionGroup, Rule};
398        use crate::types::{ActionType, Operator};
399
400        let kb = KnowledgeBase::new("test");
401
402        // Rule: If User.Name length > 3, then User.HasLongName = true
403        let conditions = ConditionGroup::Single(Condition::with_function(
404            "len".to_string(),
405            vec!["User.Name".to_string()],
406            Operator::GreaterThan,
407            Value::Number(3.0),
408        ));
409        let actions = vec![ActionType::Set {
410            field: "User.HasLongName".to_string(),
411            value: Value::Boolean(true),
412        }];
413
414        let rule = Rule::new("CheckNameLength".to_string(), conditions, actions);
415        let _ = kb.add_rule(rule);
416
417        let mut engine = BackwardEngine::new(kb);
418        let mut facts = Facts::new();
419        facts.set("User.Name", Value::String("John".to_string()));
420
421        // Query if User.HasLongName == true
422        let result = engine.query("User.HasLongName == true", &mut facts);
423        assert!(result.is_ok());
424        let query_result = result.unwrap();
425
426        // Should be provable because "John".len() = 4 > 3
427        assert!(
428            query_result.provable,
429            "Query should be provable with len() function"
430        );
431    }
432
433    #[test]
434    fn test_function_call_condition_isempty() {
435        use crate::engine::rule::{Condition, ConditionGroup, Rule};
436        use crate::types::{ActionType, Operator};
437
438        let kb = KnowledgeBase::new("test");
439
440        // Rule: If User.Description is NOT empty, then User.HasDescription = true
441        let conditions = ConditionGroup::Single(Condition::with_function(
442            "isEmpty".to_string(),
443            vec!["User.Description".to_string()],
444            Operator::Equal,
445            Value::Boolean(false),
446        ));
447        let actions = vec![ActionType::Set {
448            field: "User.HasDescription".to_string(),
449            value: Value::Boolean(true),
450        }];
451
452        let rule = Rule::new("CheckDescription".to_string(), conditions, actions);
453        let _ = kb.add_rule(rule);
454
455        let mut engine = BackwardEngine::new(kb);
456        let mut facts = Facts::new();
457        facts.set(
458            "User.Description",
459            Value::String("A great user".to_string()),
460        );
461
462        let result = engine.query("User.HasDescription == true", &mut facts);
463        assert!(result.is_ok());
464        let query_result = result.unwrap();
465
466        // Should be provable because description is not empty
467        assert!(
468            query_result.provable,
469            "Query should be provable with isEmpty() function"
470        );
471    }
472
473    #[test]
474    fn test_test_expression_exists() {
475        use crate::engine::rule::{Condition, ConditionExpression, ConditionGroup, Rule};
476        use crate::types::{ActionType, Operator};
477
478        let kb = KnowledgeBase::new("test");
479
480        // Rule: If User.Email exists, then User.HasEmail = true
481        let condition = Condition {
482            field: "User.Email".to_string(), // Required field
483            expression: ConditionExpression::Test {
484                name: "exists".to_string(),
485                args: vec!["User.Email".to_string()],
486            },
487            operator: Operator::Equal,
488            value: Value::Boolean(true),
489        };
490        let conditions = ConditionGroup::Single(condition);
491        let actions = vec![ActionType::Set {
492            field: "User.HasEmail".to_string(),
493            value: Value::Boolean(true),
494        }];
495
496        let rule = Rule::new("CheckEmail".to_string(), conditions, actions);
497        let _ = kb.add_rule(rule);
498
499        let mut engine = BackwardEngine::new(kb);
500        let mut facts = Facts::new();
501        facts.set("User.Email", Value::String("user@example.com".to_string()));
502
503        let result = engine.query("User.HasEmail == true", &mut facts);
504        assert!(result.is_ok());
505        let query_result = result.unwrap();
506
507        // Should be provable because User.Email exists
508        assert!(
509            query_result.provable,
510            "Query should be provable with exists test"
511        );
512    }
513
514    #[test]
515    fn test_multifield_count_operation() {
516        use crate::engine::rule::{Condition, ConditionExpression, ConditionGroup, Rule};
517        use crate::types::{ActionType, Operator};
518
519        let kb = KnowledgeBase::new("test");
520
521        // Rule: If User.Orders count > 5, then User.IsFrequentBuyer = true
522        let condition = Condition {
523            field: "User.Orders".to_string(), // Required field
524            expression: ConditionExpression::MultiField {
525                field: "User.Orders".to_string(),
526                operation: "count".to_string(),
527                variable: None,
528            },
529            operator: Operator::GreaterThan,
530            value: Value::Number(5.0),
531        };
532        let conditions = ConditionGroup::Single(condition);
533        let actions = vec![ActionType::Set {
534            field: "User.IsFrequentBuyer".to_string(),
535            value: Value::Boolean(true),
536        }];
537
538        let rule = Rule::new("CheckFrequentBuyer".to_string(), conditions, actions);
539        let _ = kb.add_rule(rule);
540
541        let mut engine = BackwardEngine::new(kb);
542        let mut facts = Facts::new();
543
544        // Create array of 6 orders
545        let orders = Value::Array(vec![
546            Value::Number(1.0),
547            Value::Number(2.0),
548            Value::Number(3.0),
549            Value::Number(4.0),
550            Value::Number(5.0),
551            Value::Number(6.0),
552        ]);
553        facts.set("User.Orders", orders);
554
555        let result = engine.query("User.IsFrequentBuyer == true", &mut facts);
556        assert!(result.is_ok());
557        let query_result = result.unwrap();
558
559        // Should be provable because orders.count() = 6 > 5
560        assert!(
561            query_result.provable,
562            "Query should be provable with count multifield operation"
563        );
564    }
565
566    #[test]
567    fn test_fact_derivation_basic() {
568        use crate::engine::rule::{Condition, ConditionGroup, Rule};
569        use crate::types::{ActionType, Operator};
570
571        let kb = KnowledgeBase::new("test");
572
573        // Rule: If User.Age > 18, then User.IsAdult = true
574        let conditions = ConditionGroup::Single(Condition::new(
575            "User.Age".to_string(),
576            Operator::GreaterThan,
577            Value::Number(18.0),
578        ));
579        let actions = vec![ActionType::Set {
580            field: "User.IsAdult".to_string(),
581            value: Value::Boolean(true),
582        }];
583
584        let rule = Rule::new("DetermineAdult".to_string(), conditions, actions);
585        let _ = kb.add_rule(rule);
586
587        let mut engine = BackwardEngine::new(kb);
588        let mut facts = Facts::new();
589        facts.set("User.Age", Value::Number(25.0));
590
591        // Query will trigger rule execution which should set User.IsAdult
592        let result = engine.query("User.IsAdult == true", &mut facts);
593        assert!(result.is_ok());
594        let query_result = result.unwrap();
595
596        assert!(
597            query_result.provable,
598            "Fact should be derived by rule action"
599        );
600    }
601
602    #[test]
603    fn test_rule_chaining_two_levels() {
604        use crate::engine::rule::{Condition, ConditionGroup, Rule};
605        use crate::types::{ActionType, LogicalOperator, Operator};
606
607        let kb = KnowledgeBase::new("test");
608
609        // Rule 1: If User.LoyaltyPoints > 100, then User.IsVIP = true
610        let conditions1 = ConditionGroup::Single(Condition::new(
611            "User.LoyaltyPoints".to_string(),
612            Operator::GreaterThan,
613            Value::Number(100.0),
614        ));
615        let actions1 = vec![ActionType::Set {
616            field: "User.IsVIP".to_string(),
617            value: Value::Boolean(true),
618        }];
619        let rule1 = Rule::new("DetermineVIP".to_string(), conditions1, actions1);
620
621        // Rule 2: If User.IsVIP == true AND Order.Amount < 10000, then Order.AutoApproved = true
622        let conditions2 = ConditionGroup::Compound {
623            left: Box::new(ConditionGroup::Single(Condition::new(
624                "User.IsVIP".to_string(),
625                Operator::Equal,
626                Value::Boolean(true),
627            ))),
628            operator: LogicalOperator::And,
629            right: Box::new(ConditionGroup::Single(Condition::new(
630                "Order.Amount".to_string(),
631                Operator::LessThan,
632                Value::Number(10000.0),
633            ))),
634        };
635        let actions2 = vec![ActionType::Set {
636            field: "Order.AutoApproved".to_string(),
637            value: Value::Boolean(true),
638        }];
639        let rule2 = Rule::new("AutoApproveVIP".to_string(), conditions2, actions2);
640
641        let _ = kb.add_rule(rule1);
642        let _ = kb.add_rule(rule2);
643
644        let mut engine = BackwardEngine::new(kb);
645        let mut facts = Facts::new();
646        facts.set("User.LoyaltyPoints", Value::Number(150.0));
647        facts.set("Order.Amount", Value::Number(5000.0));
648
649        // Query Order.AutoApproved - should chain through:
650        // 1. Check Order.AutoApproved rule (rule2)
651        // 2. Needs User.IsVIP == true
652        // 3. Check User.IsVIP rule (rule1)
653        // 4. Verify User.LoyaltyPoints > 100 (satisfied)
654        // 5. Execute rule1 action -> sets User.IsVIP = true
655        // 6. Now rule2 conditions satisfied -> sets Order.AutoApproved = true
656        let result = engine.query("Order.AutoApproved == true", &mut facts);
657        assert!(result.is_ok());
658        let query_result = result.unwrap();
659
660        assert!(
661            query_result.provable,
662            "Query should be provable through 2-level rule chaining"
663        );
664    }
665
666    #[test]
667    fn test_fact_derivation_with_log_action() {
668        use crate::engine::rule::{Condition, ConditionGroup, Rule};
669        use crate::types::{ActionType, Operator};
670
671        let kb = KnowledgeBase::new("test");
672
673        // Rule: If User.Score > 90, log message and set HighScore
674        let conditions = ConditionGroup::Single(Condition::new(
675            "User.Score".to_string(),
676            Operator::GreaterThan,
677            Value::Number(90.0),
678        ));
679        let actions = vec![
680            ActionType::Log {
681                message: "High score achieved!".to_string(),
682            },
683            ActionType::Set {
684                field: "User.HasHighScore".to_string(),
685                value: Value::Boolean(true),
686            },
687        ];
688
689        let rule = Rule::new("HighScoreRule".to_string(), conditions, actions);
690        let _ = kb.add_rule(rule);
691
692        let mut engine = BackwardEngine::new(kb);
693        let mut facts = Facts::new();
694        facts.set("User.Score", Value::Number(95.0));
695
696        let result = engine.query("User.HasHighScore == true", &mut facts);
697        assert!(result.is_ok());
698        let query_result = result.unwrap();
699
700        // Should be provable and log action should have been executed
701        assert!(
702            query_result.provable,
703            "Query should be provable with log action"
704        );
705    }
706}