Skip to main content

rust_rule_engine/backward/
search.rs

1//! Search strategies for backward chaining
2
3#![allow(deprecated)]
4
5use super::goal::{Goal, GoalStatus};
6use super::proof_graph::{FactKey, SharedProofGraph};
7use super::rule_executor::RuleExecutor;
8use crate::engine::rule::Rule;
9use crate::rete::propagation::IncrementalEngine;
10use crate::types::Value;
11use crate::Facts;
12use crate::KnowledgeBase;
13use std::collections::VecDeque;
14use std::sync::{Arc, Mutex};
15
16/// Strategy for searching the goal space
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SearchStrategy {
19    /// Depth-first search (Prolog-style)
20    /// Goes deep into one branch before backtracking
21    DepthFirst,
22
23    /// Breadth-first search
24    /// Explores all goals at one level before going deeper
25    BreadthFirst,
26
27    /// Iterative deepening
28    /// Combines benefits of depth-first and breadth-first
29    Iterative,
30}
31
32/// A single solution found during search
33#[derive(Debug, Clone)]
34pub struct Solution {
35    /// Path taken to prove the goal (sequence of rule names)
36    pub path: Vec<String>,
37
38    /// Variable bindings from this proof
39    pub bindings: std::collections::HashMap<String, Value>,
40}
41
42/// Result of a search operation
43#[derive(Debug)]
44pub struct SearchResult {
45    /// Whether the goal was successfully proven
46    pub success: bool,
47
48    /// Path taken to prove the goal (sequence of rule names)
49    pub path: Vec<String>,
50
51    /// Number of goals explored
52    pub goals_explored: usize,
53
54    /// Maximum depth reached
55    pub max_depth_reached: usize,
56
57    /// Variable bindings from the proof
58    pub bindings: std::collections::HashMap<String, Value>,
59
60    /// All solutions found (if max_solutions > 1)
61    pub solutions: Vec<Solution>,
62}
63
64impl SearchResult {
65    /// Create a successful search result
66    pub fn success(path: Vec<String>, goals_explored: usize, max_depth: usize) -> Self {
67        Self {
68            success: true,
69            path,
70            goals_explored,
71            max_depth_reached: max_depth,
72            bindings: std::collections::HashMap::new(),
73            solutions: Vec::new(),
74        }
75    }
76
77    /// Create a failed search result
78    pub fn failure(goals_explored: usize, max_depth: usize) -> Self {
79        Self {
80            success: false,
81            path: Vec::new(),
82            goals_explored,
83            max_depth_reached: max_depth,
84            bindings: std::collections::HashMap::new(),
85            solutions: Vec::new(),
86        }
87    }
88}
89
90/// Depth-first search implementation
91pub struct DepthFirstSearch {
92    max_depth: usize,
93    goals_explored: usize,
94    path: Vec<String>,
95    executor: RuleExecutor,
96    max_solutions: usize,
97    solutions: Vec<Solution>,
98    proof_graph: Option<SharedProofGraph>,
99}
100
101impl DepthFirstSearch {
102    /// Create a new depth-first search
103    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
104        Self {
105            max_depth,
106            goals_explored: 0,
107            path: Vec::new(),
108            executor: RuleExecutor::new_with_inserter(kb, None),
109            max_solutions: 1,
110            solutions: Vec::new(),
111            proof_graph: None,
112        }
113    }
114
115    /// Set maximum number of solutions to find
116    pub fn with_max_solutions(mut self, max_solutions: usize) -> Self {
117        self.max_solutions = max_solutions;
118        self
119    }
120
121    /// Create a new depth-first search and wire an optional IncrementalEngine
122    /// to enable TMS logical insertion. The engine is provided as Arc<Mutex<>>
123    /// and the inserter closure will call `insert_logical` on it.
124    pub fn new_with_engine(
125        max_depth: usize,
126        kb: KnowledgeBase,
127        engine: Option<Arc<Mutex<IncrementalEngine>>>,
128    ) -> Self {
129        // Create shared proof graph for caching ONLY if engine is provided
130        let proof_graph = engine.as_ref().map(|_| super::proof_graph::new_shared());
131        let proof_graph_clone = proof_graph.clone();
132
133        // Build inserter closure if engine is provided
134        let inserter = engine.map(|eng| {
135            let eng = eng.clone();
136            let pg = proof_graph_clone.clone();
137
138            std::sync::Arc::new(
139                move |fact_type: String,
140                      data: crate::rete::TypedFacts,
141                      rule_name: String,
142                      premises: Vec<String>| {
143                    if let Ok(mut e) = eng.lock() {
144                        // Resolve premise keys into FactHandles when possible
145                        let handles = e.resolve_premise_keys(premises.clone());
146
147                        // Insert logical fact and get handle
148                        let handle = e.insert_logical(
149                            fact_type.clone(),
150                            data.clone(),
151                            rule_name.clone(),
152                            handles.clone(),
153                        );
154
155                        // Update proof graph with this derivation
156                        if let Some(ref graph) = pg {
157                            if let Ok(mut g) = graph.lock() {
158                                // Create fact key from fact_type and data
159                                let pattern = format!("{}.{}", fact_type, "derived");
160                                let key = FactKey::from_pattern(&pattern);
161
162                                g.insert_proof(handle, key, rule_name, handles, premises);
163                            }
164                        }
165                    }
166                },
167            )
168                as std::sync::Arc<
169                    dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync,
170                >
171        });
172
173        Self {
174            max_depth,
175            goals_explored: 0,
176            path: Vec::new(),
177            executor: RuleExecutor::new_with_inserter(kb, inserter),
178            max_solutions: 1,
179            solutions: Vec::new(),
180            proof_graph,
181        }
182    }
183
184    /// Search for a proof of the goal WITH rule execution
185    pub fn search_with_execution(
186        &mut self,
187        goal: &mut Goal,
188        facts: &mut Facts,
189        kb: &KnowledgeBase,
190    ) -> SearchResult {
191        self.goals_explored = 0;
192        self.path.clear();
193        self.solutions.clear();
194
195        let success = self.search_recursive_with_execution(goal, facts, kb, 0);
196
197        SearchResult {
198            success,
199            path: self.path.clone(),
200            goals_explored: self.goals_explored,
201            max_depth_reached: goal.depth,
202            bindings: goal.bindings.to_map(),
203            solutions: self.solutions.clone(),
204        }
205    }
206
207    /// Search for a proof of the goal (old method, kept for compatibility)
208    pub fn search(&mut self, goal: &mut Goal, _facts: &Facts) -> SearchResult {
209        self.goals_explored = 0;
210        self.path.clear();
211
212        let success = self.search_recursive(goal, 0);
213
214        SearchResult {
215            success,
216            path: self.path.clone(),
217            goals_explored: self.goals_explored,
218            max_depth_reached: goal.depth,
219            bindings: goal.bindings.to_map(),
220            solutions: Vec::new(),
221        }
222    }
223
224    /// NEW: Recursive search WITH rule execution
225    fn search_recursive_with_execution(
226        &mut self,
227        goal: &mut Goal,
228        facts: &mut Facts, // ✅ Made mutable to allow rule execution
229        kb: &KnowledgeBase,
230        depth: usize,
231    ) -> bool {
232        self.goals_explored += 1;
233
234        // Check depth limit
235        if depth > self.max_depth {
236            goal.status = GoalStatus::Unprovable;
237            return false;
238        }
239
240        // Check if goal already satisfied by existing facts
241        let fact_proven = self.check_goal_in_facts(goal, facts);
242
243        // Handle negated goals (closed-world assumption)
244        if goal.is_negated {
245            // For negated goals: success if CANNOT be proven
246            if fact_proven {
247                goal.status = GoalStatus::Unprovable;
248                return false; // Goal IS provable, so NOT goal fails
249            }
250            // Continue to check if it can be derived via rules
251            // If no rules can prove it, then negation succeeds
252        } else {
253            // Normal goal: success if proven
254            if fact_proven {
255                goal.status = GoalStatus::Proven;
256                return true;
257            }
258        }
259
260        // Check for cycles
261        if goal.status == GoalStatus::InProgress {
262            goal.status = GoalStatus::Unprovable;
263            return false;
264        }
265
266        goal.status = GoalStatus::InProgress;
267        goal.depth = depth;
268
269        // Try each candidate rule
270        for rule_name in goal.candidate_rules.clone() {
271            self.path.push(rule_name.clone());
272
273            // Start an undo frame before trying this candidate so we can rollback
274            // any speculative changes if this candidate doesn't lead to a proof.
275            facts.begin_undo_frame();
276
277            // Get the rule from KB
278            if let Some(rule) = kb.get_rule(&rule_name) {
279                // Try to execute rule (checks conditions AND executes actions)
280                match self.executor.try_execute_rule(&rule, facts) {
281                    Ok(true) if self.check_goal_in_facts(goal, facts) => {
282                        // Rule executed successfully and goal is now proven
283                        goal.status = GoalStatus::Proven;
284
285                        // Save this solution
286                        self.solutions.push(Solution {
287                            path: self.path.clone(),
288                            bindings: goal.bindings.to_map(),
289                        });
290
291                        // If we only want one solution OR we've found enough, stop searching
292                        if self.max_solutions == 1 || self.solutions.len() >= self.max_solutions {
293                            return true; // keep changes
294                        }
295
296                        // Otherwise (max_solutions > 1 and not enough yet), rollback and continue
297                        facts.rollback_undo_frame();
298                        self.path.pop();
299                        continue;
300                    }
301                    Ok(true) => {
302                        // Rule executed but did not prove the goal yet
303                    }
304                    Ok(false) => {
305                        // Conditions not satisfied - try to prove them recursively!
306                        if self.try_prove_rule_conditions(&rule, facts, kb, depth + 1) {
307                            // All conditions now satisfied! Try executing rule again
308                            match self.executor.try_execute_rule(&rule, facts) {
309                                Ok(true) if self.check_goal_in_facts(goal, facts) => {
310                                    goal.status = GoalStatus::Proven;
311
312                                    // Save this solution
313                                    self.solutions.push(Solution {
314                                        path: self.path.clone(),
315                                        bindings: goal.bindings.to_map(),
316                                    });
317
318                                    // If we only want one solution OR we've found enough, stop searching
319                                    if self.max_solutions == 1
320                                        || self.solutions.len() >= self.max_solutions
321                                    {
322                                        return true; // keep changes
323                                    }
324
325                                    // Otherwise, rollback and continue searching
326                                    facts.rollback_undo_frame();
327                                    self.path.pop();
328                                    continue;
329                                }
330                                Ok(true) => {
331                                    // Executed but goal not yet proven
332                                }
333                                _ => {
334                                    // execution failed on second attempt
335                                }
336                            }
337                        }
338                    }
339                    Err(_) => {
340                        // Execution error - continue to next rule
341                    }
342                }
343            }
344
345            // Candidate failed to prove goal — rollback speculative changes
346            facts.rollback_undo_frame();
347            self.path.pop();
348        }
349
350        // Try sub-goals (begin undo frame before attempting sub-goals so we can rollback
351        // if any sub-goal fails)
352        let mut all_subgoals_proven = true;
353        if !goal.sub_goals.is_empty() {
354            facts.begin_undo_frame();
355            for sub_goal in &mut goal.sub_goals {
356                if !self.search_recursive_with_execution(sub_goal, facts, kb, depth + 1) {
357                    all_subgoals_proven = false;
358                    break;
359                }
360            }
361
362            if all_subgoals_proven {
363                // commit sub-goal frame (keep changes)
364                facts.commit_undo_frame();
365                goal.status = GoalStatus::Proven;
366                return true;
367            }
368
369            // rollback any changes from failed sub-goal exploration
370            facts.rollback_undo_frame();
371        }
372
373        // If we found at least one solution (even if less than max_solutions), consider it proven
374        if !self.solutions.is_empty() {
375            goal.status = GoalStatus::Proven;
376            // For negated goals, finding a proof means negation fails
377            return !goal.is_negated;
378        }
379
380        // If we have no candidate rules and no sub-goals, or nothing worked
381        if goal.is_negated {
382            // For negated goals: if we couldn't prove it, then NOT succeeds (closed-world assumption)
383            goal.status = GoalStatus::Proven;
384            true
385        } else {
386            // For normal goals: if we couldn't prove it, it's unprovable
387            goal.status = GoalStatus::Unprovable;
388            false
389        }
390    }
391
392    /// Check if goal is already satisfied by facts
393    ///
394    /// This method now reuses ConditionEvaluator for proper evaluation
395    /// and checks ProofGraph cache first for previously proven facts
396    fn check_goal_in_facts(&self, goal: &Goal, facts: &Facts) -> bool {
397        // First check ProofGraph cache if available
398        if let Some(ref graph_arc) = self.proof_graph {
399            if let Ok(mut graph) = graph_arc.lock() {
400                let key = FactKey::from_pattern(&goal.pattern);
401                if graph.is_proven(&key) {
402                    // Cache hit! Goal was previously proven
403                    return true;
404                }
405            }
406        }
407
408        // For negated goals, use the expression directly (parser strips NOT)
409        if goal.is_negated {
410            if let Some(ref expr) = goal.expression {
411                // Expression.evaluate() returns Value, need to convert to bool
412                match expr.evaluate(facts) {
413                    Ok(Value::Boolean(b)) => return b,
414                    Ok(_) => return false, // Non-boolean values are false
415                    Err(_) => return false,
416                }
417            }
418            return false;
419        }
420
421        // Parse goal pattern into a Condition and use ConditionEvaluator
422        if let Some(condition) = self.parse_goal_pattern(&goal.pattern) {
423            // Use RuleExecutor's evaluator (which delegates to ConditionEvaluator)
424            self.executor
425                .evaluate_condition(&condition, facts)
426                .unwrap_or(false)
427        } else {
428            false
429        }
430    }
431
432    /// Parse goal pattern string into a Condition object
433    ///
434    /// Examples:
435    /// - "User.Score >= 80" → Condition { field: "User.Score", operator: GreaterThanOrEqual, value: Number(80) }
436    /// - "User.IsVIP == true" → Condition { field: "User.IsVIP", operator: Equal, value: Boolean(true) }
437    fn parse_goal_pattern(&self, pattern: &str) -> Option<crate::engine::rule::Condition> {
438        use crate::engine::rule::{Condition, ConditionExpression};
439        use crate::types::Operator;
440
441        // Try parsing operators in order (longest first to avoid conflicts)
442        let operators = [
443            (">=", Operator::GreaterThanOrEqual),
444            ("<=", Operator::LessThanOrEqual),
445            ("==", Operator::Equal),
446            ("!=", Operator::NotEqual),
447            (" > ", Operator::GreaterThan),
448            (" < ", Operator::LessThan),
449            (" contains ", Operator::Contains),
450            (" not_contains ", Operator::NotContains),
451            (" starts_with ", Operator::StartsWith),
452            (" startsWith ", Operator::StartsWith),
453            (" ends_with ", Operator::EndsWith),
454            (" endsWith ", Operator::EndsWith),
455            (" matches ", Operator::Matches),
456        ];
457
458        for (op_str, operator) in operators {
459            if let Some(pos) = pattern.find(op_str) {
460                let field = pattern[..pos].trim().to_string();
461                let value_str = pattern[pos + op_str.len()..].trim();
462
463                // Parse value
464                let value = self.parse_value_string(value_str);
465
466                return Some(Condition {
467                    field: field.clone(),
468                    expression: ConditionExpression::Field(field),
469                    operator,
470                    value,
471                });
472            }
473        }
474
475        None
476    }
477
478    /// Parse value string into a Value
479    fn parse_value_string(&self, s: &str) -> Value {
480        let s = s.trim();
481
482        // Boolean
483        if s == "true" {
484            return Value::Boolean(true);
485        }
486        if s == "false" {
487            return Value::Boolean(false);
488        }
489
490        // Null
491        if s == "null" {
492            return Value::Null;
493        }
494
495        // String (quoted)
496        if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
497            return Value::String(s[1..s.len() - 1].to_string());
498        }
499
500        // Number (float) - parse first to handle decimal numbers
501        if let Ok(n) = s.parse::<f64>() {
502            return Value::Number(n);
503        }
504
505        // Integer - only if float parsing fails
506        if let Ok(i) = s.parse::<i64>() {
507            return Value::Integer(i);
508        }
509
510        // Default to string
511        Value::String(s.to_string())
512    }
513
514    /// Try to prove all conditions of a rule by creating sub-goals
515    /// This is the core of recursive backward chaining!
516    fn try_prove_rule_conditions(
517        &mut self,
518        rule: &Rule,
519        facts: &mut Facts,
520        kb: &KnowledgeBase,
521        depth: usize,
522    ) -> bool {
523        // Extract all conditions from the condition group and try to prove them
524        self.try_prove_condition_group(&rule.conditions, facts, kb, depth)
525    }
526
527    /// Recursively prove a condition group
528    fn try_prove_condition_group(
529        &mut self,
530        group: &crate::engine::rule::ConditionGroup,
531        facts: &mut Facts,
532        kb: &KnowledgeBase,
533        depth: usize,
534    ) -> bool {
535        use crate::engine::rule::ConditionGroup;
536
537        match group {
538            ConditionGroup::Single(condition) => {
539                // Try to prove this single condition
540                self.try_prove_single_condition(condition, facts, kb, depth)
541            }
542            ConditionGroup::Compound {
543                left,
544                operator,
545                right,
546            } => {
547                // Handle AND/OR/NOT logic
548                use crate::types::LogicalOperator;
549                match operator {
550                    LogicalOperator::And => {
551                        // Both must be proven
552                        self.try_prove_condition_group(left, facts, kb, depth)
553                            && self.try_prove_condition_group(right, facts, kb, depth)
554                    }
555                    LogicalOperator::Or => {
556                        // At least one must be proven
557                        self.try_prove_condition_group(left, facts, kb, depth)
558                            || self.try_prove_condition_group(right, facts, kb, depth)
559                    }
560                    LogicalOperator::Not => {
561                        // Left should fail, right doesn't apply
562                        !self.try_prove_condition_group(left, facts, kb, depth)
563                    }
564                }
565            }
566            ConditionGroup::Not(_)
567            | ConditionGroup::Exists(_)
568            | ConditionGroup::Forall(_)
569            | ConditionGroup::Accumulate { .. } => {
570                // Complex conditions (Not, Exists, Forall, Accumulate) cannot be proven backward;
571                // they can only be evaluated against current facts.
572                // Use the executor's condition evaluator to check them.
573                self.executor
574                    .evaluate_conditions(group, facts)
575                    .unwrap_or(false)
576            }
577            #[cfg(feature = "streaming")]
578            ConditionGroup::StreamPattern { .. } => {
579                // StreamPattern cannot be proven backward; evaluate against current facts.
580                self.executor
581                    .evaluate_conditions(group, facts)
582                    .unwrap_or(false)
583            }
584        }
585    }
586
587    /// Try to prove a single condition
588    fn try_prove_single_condition(
589        &mut self,
590        condition: &crate::engine::rule::Condition,
591        facts: &mut Facts,
592        kb: &KnowledgeBase,
593        depth: usize,
594    ) -> bool {
595        // First check if condition is already satisfied by facts
596        if let Ok(satisfied) = self.executor.evaluate_condition(condition, facts) {
597            if satisfied {
598                return true;
599            }
600        }
601
602        // Condition not satisfied - try to prove it by finding rules that can derive it
603        let goal_pattern = self.condition_to_goal_pattern(condition);
604
605        // Create a sub-goal for this condition
606        let mut sub_goal = Goal::new(goal_pattern.clone());
607        sub_goal.depth = depth;
608
609        // Find candidate rules that could prove this sub-goal
610        for candidate_rule in kb.get_rules() {
611            if self.rule_could_prove_pattern(&candidate_rule, &goal_pattern) {
612                sub_goal.add_candidate_rule(candidate_rule.name.clone());
613            }
614        }
615
616        // Try to prove this sub-goal recursively
617        self.search_recursive_with_execution(&mut sub_goal, facts, kb, depth)
618    }
619
620    /// Convert a condition to a goal pattern string
621    fn condition_to_goal_pattern(&self, condition: &crate::engine::rule::Condition) -> String {
622        use crate::engine::rule::ConditionExpression;
623
624        let field = match &condition.expression {
625            ConditionExpression::Field(f) => f.clone(),
626            ConditionExpression::FunctionCall { name, .. } => name.clone(),
627            ConditionExpression::Test { name, .. } => format!("test({})", name),
628            ConditionExpression::MultiField { field, .. } => field.clone(),
629        };
630
631        let op_str = match condition.operator {
632            crate::types::Operator::Equal => "==",
633            crate::types::Operator::NotEqual => "!=",
634            crate::types::Operator::GreaterThan => ">",
635            crate::types::Operator::LessThan => "<",
636            crate::types::Operator::GreaterThanOrEqual => ">=",
637            crate::types::Operator::LessThanOrEqual => "<=",
638            crate::types::Operator::Contains => "contains",
639            crate::types::Operator::NotContains => "not_contains",
640            crate::types::Operator::StartsWith => "starts_with",
641            crate::types::Operator::EndsWith => "ends_with",
642            crate::types::Operator::Matches => "matches",
643            crate::types::Operator::In => "in",
644        };
645
646        // Convert value to string format that matches goal patterns
647        let value_str = match &condition.value {
648            Value::Boolean(b) => b.to_string(),
649            Value::Number(n) => n.to_string(),
650            Value::Integer(i) => i.to_string(),
651            Value::String(s) => format!("\"{}\"", s),
652            Value::Null => "null".to_string(),
653            _ => format!("{:?}", condition.value),
654        };
655
656        format!("{} {} {}", field, op_str, value_str)
657    }
658
659    /// Check if a rule could prove a specific goal pattern
660    fn rule_could_prove_pattern(&self, rule: &Rule, pattern: &str) -> bool {
661        // Simple heuristic: check if pattern mentions fields that this rule sets
662        for action in &rule.actions {
663            match action {
664                crate::types::ActionType::Set { field, .. } if pattern.contains(field) => {
665                    return true;
666                }
667                crate::types::ActionType::MethodCall { object, method, .. }
668                    if (pattern.contains(object) || pattern.contains(method)) =>
669                {
670                    return true;
671                }
672                _ => {}
673            }
674        }
675        false
676    }
677
678    /// OLD: Recursive search without execution
679    fn search_recursive(&mut self, goal: &mut Goal, depth: usize) -> bool {
680        self.goals_explored += 1;
681
682        // Check depth limit
683        if depth > self.max_depth {
684            goal.status = GoalStatus::Unprovable;
685            return false;
686        }
687
688        // Check for cycles (goal already in progress)
689        if goal.status == GoalStatus::InProgress {
690            goal.status = GoalStatus::Unprovable;
691            return false;
692        }
693
694        // Mark as in progress to detect cycles
695        goal.status = GoalStatus::InProgress;
696        goal.depth = depth;
697
698        // Try each candidate rule
699        if let Some(rule_name) = goal.candidate_rules.clone().into_iter().next() {
700            self.path.push(rule_name.clone());
701
702            // Get the rule from knowledge base (via goal's stored rules)
703            // In a full implementation with KB access:
704            // 1. Get rule conditions
705            // 2. Check if conditions match current facts
706            // 3. If match, execute rule actions to derive new facts
707            // 4. Mark goal as proven
708
709            // For backward chaining, we check:
710            // - Can this rule's conclusion prove our goal?
711            // - Are all rule conditions satisfied (recursively)?
712
713            // Since we found a candidate rule, assume it can prove the goal
714            // The rule was added as candidate because its conclusion matches the goal
715            goal.status = GoalStatus::Proven;
716            return true;
717        }
718
719        // Try to prove sub-goals
720        for sub_goal in &mut goal.sub_goals {
721            if !self.search_recursive(sub_goal, depth + 1) {
722                goal.status = GoalStatus::Unprovable;
723                return false;
724            }
725        }
726
727        // If we have no sub-goals and no candidate rules, unprovable
728        if goal.sub_goals.is_empty() && goal.candidate_rules.is_empty() {
729            goal.status = GoalStatus::Unprovable;
730            return false;
731        }
732
733        goal.status = GoalStatus::Proven;
734        true
735    }
736}
737
738/// Breadth-first search implementation
739pub struct BreadthFirstSearch {
740    max_depth: usize,
741    goals_explored: usize,
742    executor: RuleExecutor,
743    proof_graph: Option<SharedProofGraph>,
744}
745
746/// Iterative deepening search implementation
747pub struct IterativeDeepeningSearch {
748    max_depth: usize,
749    goals_explored: usize,
750    kb: KnowledgeBase,
751    engine: Option<Arc<Mutex<IncrementalEngine>>>,
752}
753
754impl IterativeDeepeningSearch {
755    /// Create a new iterative deepening search
756    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
757        Self {
758            max_depth,
759            goals_explored: 0,
760            kb,
761            engine: None,
762        }
763    }
764
765    /// Create with optional IncrementalEngine for TMS integration
766    pub fn new_with_engine(
767        max_depth: usize,
768        kb: KnowledgeBase,
769        engine: Option<Arc<Mutex<IncrementalEngine>>>,
770    ) -> Self {
771        // Store the engine so we can pass it to DFS instances
772        Self {
773            max_depth,
774            goals_explored: 0,
775            kb,
776            engine,
777        }
778    }
779
780    /// Search with execution: probe with increasing depth using non-executing DFS,
781    /// then run a final executing DFS at the discovered depth to mutate facts.
782    pub fn search_with_execution(
783        &mut self,
784        root_goal: &mut Goal,
785        facts: &mut Facts,
786        kb: &KnowledgeBase,
787    ) -> SearchResult {
788        self.goals_explored = 0;
789        let mut cumulative_goals = 0usize;
790
791        // Try increasing depth limits
792        for depth_limit in 0..=self.max_depth {
793            // Probe using a non-executing depth-first search on a cloned goal
794            let mut probe_goal = root_goal.clone();
795            let probe_kb = self.kb.clone();
796            let mut probe_dfs = DepthFirstSearch::new(depth_limit, probe_kb);
797            let probe_result = probe_dfs.search(&mut probe_goal, facts);
798            cumulative_goals += probe_result.goals_explored;
799
800            if probe_result.success {
801                // Found a depth where a proof exists; execute for real at this depth
802                let exec_kb = self.kb.clone();
803                let mut exec_dfs =
804                    DepthFirstSearch::new_with_engine(depth_limit, exec_kb, self.engine.clone());
805                let exec_result = exec_dfs.search_with_execution(root_goal, facts, kb);
806                // Aggregate explored goals
807                let mut final_result = exec_result;
808                final_result.goals_explored += cumulative_goals - final_result.goals_explored;
809                return final_result;
810            }
811        }
812
813        // Nothing proved up to max_depth
814        SearchResult::failure(cumulative_goals, self.max_depth)
815    }
816
817    /// Non-executing search using iterative deepening (probes only)
818    pub fn search(&mut self, root_goal: &mut Goal, facts: &Facts) -> SearchResult {
819        self.goals_explored = 0;
820        let mut cumulative_goals = 0usize;
821
822        for depth_limit in 0..=self.max_depth {
823            let mut probe_goal = root_goal.clone();
824            let probe_kb = self.kb.clone();
825            let mut probe_dfs = DepthFirstSearch::new(depth_limit, probe_kb);
826            let probe_result = probe_dfs.search(&mut probe_goal, facts);
827            cumulative_goals += probe_result.goals_explored;
828            if probe_result.success {
829                // Return the successful probe result (with aggregated goals explored)
830                let mut res = probe_result;
831                res.goals_explored = cumulative_goals;
832                return res;
833            }
834        }
835
836        SearchResult::failure(cumulative_goals, self.max_depth)
837    }
838}
839
840impl BreadthFirstSearch {
841    /// Create a new breadth-first search
842    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
843        Self {
844            max_depth,
845            goals_explored: 0,
846            executor: RuleExecutor::new_with_inserter(kb, None),
847            proof_graph: None,
848        }
849    }
850
851    /// Create BFS with optional engine for TMS integration
852    pub fn new_with_engine(
853        max_depth: usize,
854        kb: KnowledgeBase,
855        engine: Option<Arc<Mutex<IncrementalEngine>>>,
856    ) -> Self {
857        // Create shared proof graph for caching ONLY if engine is provided
858        let proof_graph = engine.as_ref().map(|_| super::proof_graph::new_shared());
859        let proof_graph_clone = proof_graph.clone();
860
861        // If engine provided, create inserter closure
862        let inserter = engine.map(|eng| {
863            let eng = eng.clone();
864            let pg = proof_graph_clone.clone();
865
866            std::sync::Arc::new(
867                move |fact_type: String,
868                      data: crate::rete::TypedFacts,
869                      rule_name: String,
870                      premises: Vec<String>| {
871                    if let Ok(mut e) = eng.lock() {
872                        let handles = e.resolve_premise_keys(premises.clone());
873
874                        let handle = e.insert_logical(
875                            fact_type.clone(),
876                            data.clone(),
877                            rule_name.clone(),
878                            handles.clone(),
879                        );
880
881                        // Update proof graph
882                        if let Some(ref graph) = pg {
883                            if let Ok(mut g) = graph.lock() {
884                                let pattern = format!("{}.{}", fact_type, "derived");
885                                let key = FactKey::from_pattern(&pattern);
886                                g.insert_proof(handle, key, rule_name, handles, premises);
887                            }
888                        }
889                    }
890                },
891            )
892                as std::sync::Arc<
893                    dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync,
894                >
895        });
896
897        Self {
898            max_depth,
899            goals_explored: 0,
900            executor: RuleExecutor::new_with_inserter(kb, inserter),
901            proof_graph,
902        }
903    }
904
905    /// Search for a proof of the goal using BFS WITH rule execution
906    pub fn search_with_execution(
907        &mut self,
908        root_goal: &mut Goal,
909        facts: &mut Facts,
910        kb: &KnowledgeBase,
911    ) -> SearchResult {
912        self.goals_explored = 0;
913        let mut queue = VecDeque::new();
914        let mut path = Vec::new();
915        let mut max_depth = 0;
916
917        queue.push_back((root_goal as *mut Goal, 0));
918
919        while let Some((goal_ptr, depth)) = queue.pop_front() {
920            // Safety: We maintain ownership properly
921            let goal = unsafe { &mut *goal_ptr };
922
923            self.goals_explored += 1;
924            max_depth = max_depth.max(depth);
925
926            if depth > self.max_depth {
927                continue;
928            }
929
930            goal.depth = depth;
931
932            // Check if goal already satisfied by facts
933            if self.check_goal_in_facts(goal, facts) {
934                goal.status = GoalStatus::Proven;
935                continue;
936            }
937
938            // Try each candidate rule
939            for rule_name in goal.candidate_rules.clone() {
940                path.push(rule_name.clone());
941
942                // Get the rule from KB
943                if let Some(rule) = kb.get_rule(&rule_name) {
944                    // ✅ FIX: Try to execute rule (checks conditions AND executes actions)
945                    match self.executor.try_execute_rule(&rule, facts) {
946                        Ok(true) => {
947                            // Rule executed successfully - derived new facts!
948                            // Now check if our goal is proven
949                            if self.check_goal_in_facts(goal, facts) {
950                                goal.status = GoalStatus::Proven;
951                                break;
952                            }
953                        }
954                        Ok(false) => {
955                            // Conditions not satisfied - continue to next rule
956                        }
957                        Err(_) => {
958                            // Execution error - continue to next rule
959                        }
960                    }
961                }
962            }
963
964            // Add sub-goals to queue
965            for sub_goal in &mut goal.sub_goals {
966                queue.push_back((sub_goal as *mut Goal, depth + 1));
967            }
968        }
969
970        let success = root_goal.is_proven();
971
972        SearchResult {
973            success,
974            path,
975            goals_explored: self.goals_explored,
976            max_depth_reached: max_depth,
977            bindings: root_goal.bindings.to_map(),
978            solutions: Vec::new(),
979        }
980    }
981
982    /// Check if goal is already satisfied by facts
983    ///
984    /// This method now reuses ConditionEvaluator for proper evaluation
985    /// and checks ProofGraph cache first for previously proven facts
986    fn check_goal_in_facts(&self, goal: &Goal, facts: &Facts) -> bool {
987        // First check ProofGraph cache if available
988        if let Some(ref graph_arc) = self.proof_graph {
989            if let Ok(mut graph) = graph_arc.lock() {
990                let key = FactKey::from_pattern(&goal.pattern);
991                if graph.is_proven(&key) {
992                    // Cache hit! Goal was previously proven
993                    return true;
994                }
995            }
996        }
997
998        // For negated goals, use the expression directly (parser strips NOT)
999        if goal.is_negated {
1000            if let Some(ref expr) = goal.expression {
1001                // Expression.evaluate() returns Value, need to convert to bool
1002                match expr.evaluate(facts) {
1003                    Ok(Value::Boolean(b)) => return b,
1004                    Ok(_) => return false, // Non-boolean values are false
1005                    Err(_) => return false,
1006                }
1007            }
1008            return false;
1009        }
1010
1011        // Parse goal pattern into a Condition and use ConditionEvaluator
1012        if let Some(condition) = self.parse_goal_pattern(&goal.pattern) {
1013            // Use RuleExecutor's evaluator (which delegates to ConditionEvaluator)
1014            self.executor
1015                .evaluate_condition(&condition, facts)
1016                .unwrap_or(false)
1017        } else {
1018            false
1019        }
1020    }
1021
1022    /// Parse goal pattern string into a Condition object
1023    ///
1024    /// Examples:
1025    /// - "User.Score >= 80" → Condition { field: "User.Score", operator: GreaterThanOrEqual, value: Number(80) }
1026    /// - "User.IsVIP == true" → Condition { field: "User.IsVIP", operator: Equal, value: Boolean(true) }
1027    fn parse_goal_pattern(&self, pattern: &str) -> Option<crate::engine::rule::Condition> {
1028        use crate::engine::rule::{Condition, ConditionExpression};
1029        use crate::types::Operator;
1030
1031        // Try parsing operators in order (longest first to avoid conflicts)
1032        let operators = [
1033            (">=", Operator::GreaterThanOrEqual),
1034            ("<=", Operator::LessThanOrEqual),
1035            ("==", Operator::Equal),
1036            ("!=", Operator::NotEqual),
1037            (" > ", Operator::GreaterThan),
1038            (" < ", Operator::LessThan),
1039            (" contains ", Operator::Contains),
1040            (" not_contains ", Operator::NotContains),
1041            (" starts_with ", Operator::StartsWith),
1042            (" startsWith ", Operator::StartsWith),
1043            (" ends_with ", Operator::EndsWith),
1044            (" endsWith ", Operator::EndsWith),
1045            (" matches ", Operator::Matches),
1046        ];
1047
1048        for (op_str, operator) in operators {
1049            if let Some(pos) = pattern.find(op_str) {
1050                let field = pattern[..pos].trim().to_string();
1051                let value_str = pattern[pos + op_str.len()..].trim();
1052
1053                // Parse value
1054                let value = self.parse_value_string(value_str);
1055
1056                return Some(Condition {
1057                    field: field.clone(),
1058                    expression: ConditionExpression::Field(field),
1059                    operator,
1060                    value,
1061                });
1062            }
1063        }
1064
1065        None
1066    }
1067
1068    /// Parse value string into a Value
1069    fn parse_value_string(&self, s: &str) -> Value {
1070        let s = s.trim();
1071
1072        // Boolean
1073        if s == "true" {
1074            return Value::Boolean(true);
1075        }
1076        if s == "false" {
1077            return Value::Boolean(false);
1078        }
1079
1080        // Null
1081        if s == "null" {
1082            return Value::Null;
1083        }
1084
1085        // String (quoted)
1086        if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
1087            return Value::String(s[1..s.len() - 1].to_string());
1088        }
1089
1090        // Number (float)
1091        if let Ok(n) = s.parse::<f64>() {
1092            return Value::Number(n);
1093        }
1094
1095        // Integer
1096        if let Ok(i) = s.parse::<i64>() {
1097            return Value::Integer(i);
1098        }
1099
1100        // Default to string
1101        Value::String(s.to_string())
1102    }
1103
1104    /// Search for a proof of the goal using BFS (old method, kept for compatibility)
1105    pub fn search(&mut self, root_goal: &mut Goal, _facts: &Facts) -> SearchResult {
1106        self.goals_explored = 0;
1107        let mut queue = VecDeque::new();
1108        let mut path = Vec::new();
1109        let mut max_depth = 0;
1110
1111        queue.push_back((root_goal as *mut Goal, 0));
1112
1113        while let Some((goal_ptr, depth)) = queue.pop_front() {
1114            // Safety: We maintain ownership properly
1115            let goal = unsafe { &mut *goal_ptr };
1116
1117            self.goals_explored += 1;
1118            max_depth = max_depth.max(depth);
1119
1120            if depth > self.max_depth {
1121                continue;
1122            }
1123
1124            goal.depth = depth;
1125
1126            // Process candidate rules (collect names without cloning the Vec)
1127            for rule_name in &goal.candidate_rules {
1128                path.push(rule_name.clone());
1129            }
1130
1131            // Add sub-goals to queue
1132            for sub_goal in &mut goal.sub_goals {
1133                queue.push_back((sub_goal as *mut Goal, depth + 1));
1134            }
1135
1136            // Check if goal can be proven
1137            if !goal.candidate_rules.is_empty() || goal.all_subgoals_proven() {
1138                goal.status = GoalStatus::Proven;
1139            }
1140        }
1141
1142        let success = root_goal.is_proven();
1143
1144        SearchResult {
1145            success,
1146            path,
1147            goals_explored: self.goals_explored,
1148            max_depth_reached: max_depth,
1149            bindings: root_goal.bindings.to_map(),
1150            solutions: Vec::new(),
1151        }
1152    }
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157    use super::*;
1158    use std::collections::HashMap;
1159
1160    #[test]
1161    fn test_search_strategies() {
1162        assert_eq!(SearchStrategy::DepthFirst, SearchStrategy::DepthFirst);
1163        assert_ne!(SearchStrategy::DepthFirst, SearchStrategy::BreadthFirst);
1164    }
1165
1166    #[test]
1167    fn test_search_result_creation() {
1168        let success = SearchResult::success(vec!["Rule1".to_string()], 5, 3);
1169        assert!(success.success);
1170        assert_eq!(success.path.len(), 1);
1171        assert_eq!(success.goals_explored, 5);
1172
1173        let failure = SearchResult::failure(10, 5);
1174        assert!(!failure.success);
1175        assert!(failure.path.is_empty());
1176    }
1177
1178    #[test]
1179    fn test_depth_first_search_creation() {
1180        let kb = KnowledgeBase::new("test");
1181        let dfs = DepthFirstSearch::new(10, kb);
1182        assert_eq!(dfs.max_depth, 10);
1183        assert_eq!(dfs.goals_explored, 0);
1184    }
1185
1186    #[test]
1187    fn test_depth_first_search_simple() {
1188        let kb = KnowledgeBase::new("test");
1189        let mut dfs = DepthFirstSearch::new(10, kb);
1190        let facts = Facts::new();
1191
1192        let mut goal = Goal::new("test".to_string());
1193        goal.add_candidate_rule("TestRule".to_string());
1194
1195        let result = dfs.search(&mut goal, &facts);
1196
1197        assert!(result.success);
1198        assert!(goal.is_proven());
1199        assert!(result.goals_explored > 0);
1200    }
1201
1202    #[test]
1203    fn test_breadth_first_search() {
1204        let kb = KnowledgeBase::new("test");
1205        let mut bfs = BreadthFirstSearch::new(10, kb);
1206        let facts = Facts::new();
1207
1208        let mut goal = Goal::new("test".to_string());
1209        goal.add_candidate_rule("TestRule".to_string());
1210
1211        let result = bfs.search(&mut goal, &facts);
1212
1213        assert!(result.success);
1214        assert_eq!(result.goals_explored, 1);
1215    }
1216
1217    #[test]
1218    fn test_iterative_deepening_search_success() {
1219        let kb = KnowledgeBase::new("test");
1220        let mut ids = IterativeDeepeningSearch::new(5, kb.clone());
1221        let mut root = Goal::new("test".to_string());
1222        root.add_candidate_rule("TestRule".to_string());
1223
1224        // Facts empty; DFS probe should succeed because candidate rules mark provable
1225        let facts = Facts::new();
1226        let res = ids.search(&mut root, &facts);
1227        assert!(res.success);
1228    }
1229
1230    #[test]
1231    fn test_iterative_deepening_search_depth_limit() {
1232        let kb = KnowledgeBase::new("test");
1233        // Set max_depth to 0 so even shallow proofs that require depth >0 fail
1234        let mut ids = IterativeDeepeningSearch::new(0, kb.clone());
1235        let mut root = Goal::new("test".to_string());
1236        // Add a subgoal to force depth > 0
1237        let mut sub = Goal::new("sub".to_string());
1238        sub.add_candidate_rule("SubRule".to_string());
1239        root.sub_goals.push(sub);
1240
1241        let facts = Facts::new();
1242        let res = ids.search(&mut root, &facts);
1243        assert!(!res.success);
1244    }
1245
1246    #[test]
1247    fn test_depth_first_search_max_depth_exceeded() {
1248        let kb = KnowledgeBase::new("test");
1249        let mut dfs = DepthFirstSearch::new(2, kb);
1250        let facts = Facts::new();
1251
1252        // Create nested goals exceeding max depth
1253        let mut root = Goal::new("level0".to_string());
1254        root.depth = 0;
1255        root.add_candidate_rule("Rule0".to_string());
1256
1257        let mut level1 = Goal::new("level1".to_string());
1258        level1.depth = 1;
1259        level1.add_candidate_rule("Rule1".to_string());
1260
1261        let mut level2 = Goal::new("level2".to_string());
1262        level2.depth = 2;
1263        level2.add_candidate_rule("Rule2".to_string());
1264
1265        let mut level3 = Goal::new("level3".to_string());
1266        level3.depth = 3; // Exceeds max_depth of 2
1267        level3.add_candidate_rule("Rule3".to_string());
1268
1269        level2.add_subgoal(level3);
1270        level1.add_subgoal(level2);
1271        root.add_subgoal(level1);
1272
1273        let result = dfs.search(&mut root, &facts);
1274
1275        // Verify search completed (max_depth_reached is set)
1276        assert!(result.max_depth_reached <= 3);
1277    }
1278
1279    #[test]
1280    fn test_breadth_first_search_multiple_candidates() {
1281        let kb = KnowledgeBase::new("test");
1282        let mut bfs = BreadthFirstSearch::new(10, kb);
1283        let facts = Facts::new();
1284
1285        let mut goal = Goal::new("multi_rule_goal".to_string());
1286        goal.add_candidate_rule("Rule1".to_string());
1287        goal.add_candidate_rule("Rule2".to_string());
1288        goal.add_candidate_rule("Rule3".to_string());
1289
1290        let result = bfs.search(&mut goal, &facts);
1291
1292        assert!(result.success);
1293        assert_eq!(goal.candidate_rules.len(), 3);
1294    }
1295
1296    #[test]
1297    fn test_depth_first_search_empty_goal() {
1298        let kb = KnowledgeBase::new("test");
1299        let mut dfs = DepthFirstSearch::new(10, kb);
1300        let facts = Facts::new();
1301
1302        let mut goal = Goal::new("".to_string());
1303        // No candidate rules, no subgoals
1304
1305        let result = dfs.search(&mut goal, &facts);
1306
1307        // Should fail - no way to prove empty goal
1308        assert!(!result.success);
1309    }
1310
1311    #[test]
1312    fn test_search_result_with_bindings() {
1313        use crate::types::Value;
1314        let mut bindings = HashMap::new();
1315        bindings.insert("X".to_string(), Value::String("test".to_string()));
1316        bindings.insert("Y".to_string(), Value::Number(42.0));
1317
1318        let result = SearchResult {
1319            success: true,
1320            path: vec!["Rule1".to_string()],
1321            goals_explored: 5,
1322            max_depth_reached: 3,
1323            bindings: bindings.clone(),
1324            solutions: Vec::new(),
1325        };
1326
1327        assert_eq!(result.bindings.len(), 2);
1328        assert_eq!(
1329            result.bindings.get("X"),
1330            Some(&Value::String("test".to_string()))
1331        );
1332    }
1333
1334    #[test]
1335    fn test_breadth_first_search_with_subgoals() {
1336        let kb = KnowledgeBase::new("test");
1337        let mut bfs = BreadthFirstSearch::new(10, kb);
1338        let facts = Facts::new();
1339
1340        let mut root = Goal::new("root".to_string());
1341        root.add_candidate_rule("RootRule".to_string());
1342
1343        let mut sub1 = Goal::new("sub1".to_string());
1344        sub1.add_candidate_rule("Sub1Rule".to_string());
1345
1346        let mut sub2 = Goal::new("sub2".to_string());
1347        sub2.add_candidate_rule("Sub2Rule".to_string());
1348
1349        root.add_subgoal(sub1);
1350        root.add_subgoal(sub2);
1351
1352        let result = bfs.search(&mut root, &facts);
1353
1354        assert!(result.success);
1355        assert!(result.goals_explored >= 3); // root + 2 subgoals
1356    }
1357
1358    #[test]
1359    fn test_iterative_deepening_search_no_candidates() {
1360        let kb = KnowledgeBase::new("test");
1361        let mut ids = IterativeDeepeningSearch::new(5, kb);
1362        let mut root = Goal::new("no_rules".to_string());
1363        // No candidate rules added
1364
1365        let facts = Facts::new();
1366        let result = ids.search(&mut root, &facts);
1367
1368        assert!(!result.success);
1369        assert!(result.path.is_empty());
1370    }
1371
1372    #[test]
1373    fn test_search_strategy_equality() {
1374        assert_eq!(SearchStrategy::BreadthFirst, SearchStrategy::BreadthFirst);
1375        assert_eq!(SearchStrategy::Iterative, SearchStrategy::Iterative);
1376        assert_ne!(SearchStrategy::BreadthFirst, SearchStrategy::Iterative);
1377    }
1378
1379    #[test]
1380    fn test_depth_first_search_goals_explored_count() {
1381        let kb = KnowledgeBase::new("test");
1382        let mut dfs = DepthFirstSearch::new(10, kb);
1383        let facts = Facts::new();
1384
1385        let mut root = Goal::new("root".to_string());
1386        root.add_candidate_rule("RootRule".to_string());
1387
1388        let mut sub = Goal::new("sub".to_string());
1389        sub.add_candidate_rule("SubRule".to_string());
1390
1391        root.add_subgoal(sub);
1392
1393        let result = dfs.search(&mut root, &facts);
1394
1395        // Search succeeded with candidate rules
1396        assert!(result.success);
1397        // Goals explored count is tracked (always >= 0 since it's usize)
1398        assert!(result.goals_explored > 0);
1399    }
1400}