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