rust_rule_engine/backward/
search.rs

1//! Search strategies for backward chaining
2
3use super::goal::{Goal, GoalStatus};
4use super::rule_executor::RuleExecutor;
5use crate::Facts;
6use crate::types::Value;
7use crate::KnowledgeBase;
8use crate::engine::rule::Rule;
9use std::collections::VecDeque;
10
11/// Strategy for searching the goal space
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SearchStrategy {
14    /// Depth-first search (Prolog-style)
15    /// Goes deep into one branch before backtracking
16    DepthFirst,
17    
18    /// Breadth-first search
19    /// Explores all goals at one level before going deeper
20    BreadthFirst,
21    
22    /// Iterative deepening
23    /// Combines benefits of depth-first and breadth-first
24    Iterative,
25}
26
27/// Result of a search operation
28#[derive(Debug)]
29pub struct SearchResult {
30    /// Whether the goal was successfully proven
31    pub success: bool,
32    
33    /// Path taken to prove the goal (sequence of rule names)
34    pub path: Vec<String>,
35    
36    /// Number of goals explored
37    pub goals_explored: usize,
38    
39    /// Maximum depth reached
40    pub max_depth_reached: usize,
41    
42    /// Variable bindings from the proof
43    pub bindings: std::collections::HashMap<String, Value>,
44}
45
46impl SearchResult {
47    /// Create a successful search result
48    pub fn success(path: Vec<String>, goals_explored: usize, max_depth: usize) -> Self {
49        Self {
50            success: true,
51            path,
52            goals_explored,
53            max_depth_reached: max_depth,
54            bindings: std::collections::HashMap::new(),
55        }
56    }
57    
58    /// Create a failed search result
59    pub fn failure(goals_explored: usize, max_depth: usize) -> Self {
60        Self {
61            success: false,
62            path: Vec::new(),
63            goals_explored,
64            max_depth_reached: max_depth,
65            bindings: std::collections::HashMap::new(),
66        }
67    }
68}
69
70/// Depth-first search implementation
71pub struct DepthFirstSearch {
72    max_depth: usize,
73    goals_explored: usize,
74    path: Vec<String>,
75    executor: RuleExecutor,
76}
77
78impl DepthFirstSearch {
79    /// Create a new depth-first search
80    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
81        Self {
82            max_depth,
83            goals_explored: 0,
84            path: Vec::new(),
85            executor: RuleExecutor::new(kb),
86        }
87    }
88    
89    /// Search for a proof of the goal WITH rule execution
90    pub fn search_with_execution(&mut self, goal: &mut Goal, facts: &Facts, kb: &KnowledgeBase) -> SearchResult {
91        self.goals_explored = 0;
92        self.path.clear();
93        
94        let success = self.search_recursive_with_execution(goal, facts, kb, 0);
95        
96        SearchResult {
97            success,
98            path: self.path.clone(),
99            goals_explored: self.goals_explored,
100            max_depth_reached: goal.depth,
101            bindings: goal.bindings.clone(),
102        }
103    }
104    
105    /// Search for a proof of the goal (old method, kept for compatibility)
106    pub fn search(&mut self, goal: &mut Goal, _facts: &Facts) -> SearchResult {
107        self.goals_explored = 0;
108        self.path.clear();
109        
110        let success = self.search_recursive(goal, 0);
111        
112        SearchResult {
113            success,
114            path: self.path.clone(),
115            goals_explored: self.goals_explored,
116            max_depth_reached: goal.depth,
117            bindings: goal.bindings.clone(),
118        }
119    }
120    
121    /// NEW: Recursive search WITH rule execution
122    fn search_recursive_with_execution(
123        &mut self, 
124        goal: &mut Goal, 
125        facts: &Facts,
126        kb: &KnowledgeBase,
127        depth: usize
128    ) -> bool {
129        self.goals_explored += 1;
130        
131        // Check depth limit
132        if depth > self.max_depth {
133            goal.status = GoalStatus::Unprovable;
134            return false;
135        }
136        
137        // Check if goal already satisfied by existing facts
138        if self.check_goal_in_facts(goal, facts) {
139            goal.status = GoalStatus::Proven;
140            return true;
141        }
142        
143        // Check for cycles
144        if goal.status == GoalStatus::InProgress {
145            goal.status = GoalStatus::Unprovable;
146            return false;
147        }
148        
149        goal.status = GoalStatus::InProgress;
150        goal.depth = depth;
151        
152        // Try each candidate rule
153        for rule_name in goal.candidate_rules.clone() {
154            self.path.push(rule_name.clone());
155            
156            // Get the rule from KB
157            if let Some(rule) = kb.get_rule(&rule_name) {
158                // Check if rule conditions are satisfied
159                if self.check_rule_conditions(&rule, facts) {
160                    // Rule can fire! Mark goal as proven
161                    goal.status = GoalStatus::Proven;
162                    return true;
163                }
164                
165                // If conditions not satisfied, try to prove them recursively
166                // This would create sub-goals for each condition
167                // For now, skip this complex logic
168            }
169            
170            self.path.pop();
171        }
172        
173        // Try sub-goals
174        for sub_goal in &mut goal.sub_goals {
175            if !self.search_recursive_with_execution(sub_goal, facts, kb, depth + 1) {
176                goal.status = GoalStatus::Unprovable;
177                return false;
178            }
179        }
180        
181        // If no way to prove
182        if goal.candidate_rules.is_empty() && goal.sub_goals.is_empty() {
183            goal.status = GoalStatus::Unprovable;
184            return false;
185        }
186        
187        goal.status = GoalStatus::Proven;
188        true
189    }
190    
191    /// Check if goal is already satisfied by facts
192    fn check_goal_in_facts(&self, goal: &Goal, facts: &Facts) -> bool {
193        // Parse goal pattern like "Order.AutoApproved == true"
194        let pattern = &goal.pattern;
195        
196        // Simple parser for "Field == Value" or "Field != Value"
197        if let Some(eq_pos) = pattern.find("==") {
198            let field = pattern[..eq_pos].trim();
199            let expected = pattern[eq_pos + 2..].trim();
200
201            if let Some(actual) = facts.get(field) {
202                return self.value_matches(&actual, expected);
203            }
204            return false;
205        }
206
207        if let Some(ne_pos) = pattern.find("!=") {
208            let field = pattern[..ne_pos].trim();
209            let not_expected = pattern[ne_pos + 2..].trim();
210
211            if let Some(actual) = facts.get(field) {
212                return !self.value_matches(&actual, not_expected);
213            }
214            // If field doesn't exist, != is considered true
215            return true;
216        }
217
218        false
219    }
220    
221    /// Check if value matches expected string
222    fn value_matches(&self, value: &Value, expected: &str) -> bool {
223        match value {
224            Value::Boolean(b) => {
225                expected == "true" && *b || expected == "false" && !*b
226            }
227            Value::String(s) => {
228                s == expected || s == expected.trim_matches('"')
229            }
230            Value::Number(n) => {
231                expected.parse::<f64>().map(|e| (n - e).abs() < 0.0001).unwrap_or(false)
232            }
233            _ => false,
234        }
235    }
236    
237    /// Check if rule conditions are satisfied using RuleExecutor
238    fn check_rule_conditions(&self, rule: &Rule, facts: &Facts) -> bool {
239        // Use RuleExecutor for proper condition evaluation
240        self.executor.evaluate_conditions(&rule.conditions, facts).unwrap_or(false)
241    }
242    
243    /// OLD: Recursive search without execution
244    fn search_recursive(&mut self, goal: &mut Goal, depth: usize) -> bool {
245        self.goals_explored += 1;
246        
247        // Check depth limit
248        if depth > self.max_depth {
249            goal.status = GoalStatus::Unprovable;
250            return false;
251        }
252        
253        // Check for cycles (goal already in progress)
254        if goal.status == GoalStatus::InProgress {
255            goal.status = GoalStatus::Unprovable;
256            return false;
257        }
258        
259        // Mark as in progress to detect cycles
260        goal.status = GoalStatus::InProgress;
261        goal.depth = depth;
262        
263        // Try each candidate rule
264        for rule_name in goal.candidate_rules.clone() {
265            self.path.push(rule_name.clone());
266            
267            // Get the rule from knowledge base (via goal's stored rules)
268            // In a full implementation with KB access:
269            // 1. Get rule conditions
270            // 2. Check if conditions match current facts
271            // 3. If match, execute rule actions to derive new facts
272            // 4. Mark goal as proven
273            
274            // For backward chaining, we check:
275            // - Can this rule's conclusion prove our goal?
276            // - Are all rule conditions satisfied (recursively)?
277            
278            // Since we found a candidate rule, assume it can prove the goal
279            // The rule was added as candidate because its conclusion matches the goal
280            goal.status = GoalStatus::Proven;
281            return true;
282        }
283        
284        // Try to prove sub-goals
285        for sub_goal in &mut goal.sub_goals {
286            if !self.search_recursive(sub_goal, depth + 1) {
287                goal.status = GoalStatus::Unprovable;
288                return false;
289            }
290        }
291        
292        // If we have no sub-goals and no candidate rules, unprovable
293        if goal.sub_goals.is_empty() && goal.candidate_rules.is_empty() {
294            goal.status = GoalStatus::Unprovable;
295            return false;
296        }
297        
298        goal.status = GoalStatus::Proven;
299        true
300    }
301}
302
303/// Breadth-first search implementation
304pub struct BreadthFirstSearch {
305    max_depth: usize,
306    goals_explored: usize,
307    executor: RuleExecutor,
308}
309
310impl BreadthFirstSearch {
311    /// Create a new breadth-first search
312    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
313        Self {
314            max_depth,
315            goals_explored: 0,
316            executor: RuleExecutor::new(kb),
317        }
318    }
319    
320    /// Search for a proof of the goal using BFS WITH rule execution
321    pub fn search_with_execution(&mut self, root_goal: &mut Goal, facts: &Facts, kb: &KnowledgeBase) -> SearchResult {
322        self.goals_explored = 0;
323        let mut queue = VecDeque::new();
324        let mut path = Vec::new();
325        let mut max_depth = 0;
326        
327        queue.push_back((root_goal as *mut Goal, 0));
328        
329        while let Some((goal_ptr, depth)) = queue.pop_front() {
330            // Safety: We maintain ownership properly
331            let goal = unsafe { &mut *goal_ptr };
332            
333            self.goals_explored += 1;
334            max_depth = max_depth.max(depth);
335            
336            if depth > self.max_depth {
337                continue;
338            }
339            
340            goal.depth = depth;
341            
342            // Check if goal already satisfied by facts
343            if self.check_goal_in_facts(goal, facts) {
344                goal.status = GoalStatus::Proven;
345                continue;
346            }
347            
348            // Try each candidate rule
349            for rule_name in goal.candidate_rules.clone() {
350                path.push(rule_name.clone());
351                
352                // Get the rule from KB
353                if let Some(rule) = kb.get_rule(&rule_name) {
354                    // Check if rule conditions are satisfied
355                    if self.check_rule_conditions(&rule, facts) {
356                        goal.status = GoalStatus::Proven;
357                        break;
358                    }
359                }
360            }
361            
362            // Add sub-goals to queue
363            for sub_goal in &mut goal.sub_goals {
364                queue.push_back((sub_goal as *mut Goal, depth + 1));
365            }
366        }
367        
368        let success = root_goal.is_proven();
369        
370        SearchResult {
371            success,
372            path,
373            goals_explored: self.goals_explored,
374            max_depth_reached: max_depth,
375            bindings: root_goal.bindings.clone(),
376        }
377    }
378    
379    /// Check if goal is already satisfied by facts
380    fn check_goal_in_facts(&self, goal: &Goal, facts: &Facts) -> bool {
381        let pattern = &goal.pattern;
382        
383        if let Some(eq_pos) = pattern.find("==") {
384            let field = pattern[..eq_pos].trim();
385            let expected = pattern[eq_pos + 2..].trim();
386            
387            if let Some(actual) = facts.get(field) {
388                return self.value_matches(&actual, expected);
389            }
390            return false;
391        }
392        
393        if let Some(ne_pos) = pattern.find("!=") {
394            let field = pattern[..ne_pos].trim();
395            let not_expected = pattern[ne_pos + 2..].trim();
396            
397            if let Some(actual) = facts.get(field) {
398                return !self.value_matches(&actual, not_expected);
399            }
400            return true;
401        }
402        
403        false
404    }
405    
406    /// Check if value matches expected string
407    fn value_matches(&self, value: &Value, expected: &str) -> bool {
408        match value {
409            Value::Boolean(b) => {
410                expected == "true" && *b || expected == "false" && !*b
411            }
412            Value::String(s) => {
413                s == expected || s == expected.trim_matches('"')
414            }
415            Value::Number(n) => {
416                expected.parse::<f64>().map(|e| (n - e).abs() < 0.0001).unwrap_or(false)
417            }
418            _ => false,
419        }
420    }
421    
422    /// Check if rule conditions are satisfied using RuleExecutor
423    fn check_rule_conditions(&self, rule: &Rule, facts: &Facts) -> bool {
424        // Use RuleExecutor for proper condition evaluation
425        self.executor.evaluate_conditions(&rule.conditions, facts).unwrap_or(false)
426    }
427    
428    /// Search for a proof of the goal using BFS (old method, kept for compatibility)
429    pub fn search(&mut self, root_goal: &mut Goal, _facts: &Facts) -> SearchResult {
430        self.goals_explored = 0;
431        let mut queue = VecDeque::new();
432        let mut path = Vec::new();
433        let mut max_depth = 0;
434        
435        queue.push_back((root_goal as *mut Goal, 0));
436        
437        while let Some((goal_ptr, depth)) = queue.pop_front() {
438            // Safety: We maintain ownership properly
439            let goal = unsafe { &mut *goal_ptr };
440            
441            self.goals_explored += 1;
442            max_depth = max_depth.max(depth);
443            
444            if depth > self.max_depth {
445                continue;
446            }
447            
448            goal.depth = depth;
449            
450            // Process candidate rules
451            for rule_name in &goal.candidate_rules {
452                path.push(rule_name.clone());
453            }
454            
455            // Add sub-goals to queue
456            for sub_goal in &mut goal.sub_goals {
457                queue.push_back((sub_goal as *mut Goal, depth + 1));
458            }
459            
460            // Check if goal can be proven
461            if !goal.candidate_rules.is_empty() || goal.all_subgoals_proven() {
462                goal.status = GoalStatus::Proven;
463            }
464        }
465        
466        let success = root_goal.is_proven();
467        
468        SearchResult {
469            success,
470            path,
471            goals_explored: self.goals_explored,
472            max_depth_reached: max_depth,
473            bindings: root_goal.bindings.clone(),
474        }
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481    
482    #[test]
483    fn test_search_strategies() {
484        assert_eq!(SearchStrategy::DepthFirst, SearchStrategy::DepthFirst);
485        assert_ne!(SearchStrategy::DepthFirst, SearchStrategy::BreadthFirst);
486    }
487    
488    #[test]
489    fn test_search_result_creation() {
490        let success = SearchResult::success(vec!["Rule1".to_string()], 5, 3);
491        assert!(success.success);
492        assert_eq!(success.path.len(), 1);
493        assert_eq!(success.goals_explored, 5);
494        
495        let failure = SearchResult::failure(10, 5);
496        assert!(!failure.success);
497        assert!(failure.path.is_empty());
498    }
499    
500    #[test]
501    fn test_depth_first_search_creation() {
502        let kb = KnowledgeBase::new("test");
503        let dfs = DepthFirstSearch::new(10, kb);
504        assert_eq!(dfs.max_depth, 10);
505        assert_eq!(dfs.goals_explored, 0);
506    }
507    
508    #[test]
509    fn test_depth_first_search_simple() {
510        let kb = KnowledgeBase::new("test");
511        let mut dfs = DepthFirstSearch::new(10, kb);
512        let facts = Facts::new();
513
514        let mut goal = Goal::new("test".to_string());
515        goal.add_candidate_rule("TestRule".to_string());
516
517        let result = dfs.search(&mut goal, &facts);
518
519        assert!(result.success);
520        assert!(goal.is_proven());
521        assert!(result.goals_explored > 0);
522    }
523    
524    #[test]
525    fn test_breadth_first_search() {
526        let kb = KnowledgeBase::new("test");
527        let mut bfs = BreadthFirstSearch::new(10, kb);
528        let facts = Facts::new();
529
530        let mut goal = Goal::new("test".to_string());
531        goal.add_candidate_rule("TestRule".to_string());
532
533        let result = bfs.search(&mut goal, &facts);
534
535        assert!(result.success);
536        assert_eq!(result.goals_explored, 1);
537    }
538}