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            crate::types::Operator::In => "in",
646        };
647
648        // Convert value to string format that matches goal patterns
649        let value_str = match &condition.value {
650            Value::Boolean(b) => b.to_string(),
651            Value::Number(n) => n.to_string(),
652            Value::Integer(i) => i.to_string(),
653            Value::String(s) => format!("\"{}\"", s),
654            Value::Null => "null".to_string(),
655            _ => format!("{:?}", condition.value),
656        };
657
658        format!("{} {} {}", field, op_str, value_str)
659    }
660
661    /// Check if a rule could prove a specific goal pattern
662    fn rule_could_prove_pattern(&self, rule: &Rule, pattern: &str) -> bool {
663        // Simple heuristic: check if pattern mentions fields that this rule sets
664        for action in &rule.actions {
665            match action {
666                crate::types::ActionType::Set { field, .. } => {
667                    if pattern.contains(field) {
668                        return true;
669                    }
670                }
671                crate::types::ActionType::MethodCall { object, method, .. } => {
672                    if pattern.contains(object) || pattern.contains(method) {
673                        return true;
674                    }
675                }
676                _ => {}
677            }
678        }
679        false
680    }
681
682    /// OLD: Recursive search without execution
683    fn search_recursive(&mut self, goal: &mut Goal, depth: usize) -> bool {
684        self.goals_explored += 1;
685
686        // Check depth limit
687        if depth > self.max_depth {
688            goal.status = GoalStatus::Unprovable;
689            return false;
690        }
691
692        // Check for cycles (goal already in progress)
693        if goal.status == GoalStatus::InProgress {
694            goal.status = GoalStatus::Unprovable;
695            return false;
696        }
697
698        // Mark as in progress to detect cycles
699        goal.status = GoalStatus::InProgress;
700        goal.depth = depth;
701
702        // Try each candidate rule
703        if let Some(rule_name) = goal.candidate_rules.clone().into_iter().next() {
704            self.path.push(rule_name.clone());
705
706            // Get the rule from knowledge base (via goal's stored rules)
707            // In a full implementation with KB access:
708            // 1. Get rule conditions
709            // 2. Check if conditions match current facts
710            // 3. If match, execute rule actions to derive new facts
711            // 4. Mark goal as proven
712
713            // For backward chaining, we check:
714            // - Can this rule's conclusion prove our goal?
715            // - Are all rule conditions satisfied (recursively)?
716
717            // Since we found a candidate rule, assume it can prove the goal
718            // The rule was added as candidate because its conclusion matches the goal
719            goal.status = GoalStatus::Proven;
720            return true;
721        }
722
723        // Try to prove sub-goals
724        for sub_goal in &mut goal.sub_goals {
725            if !self.search_recursive(sub_goal, depth + 1) {
726                goal.status = GoalStatus::Unprovable;
727                return false;
728            }
729        }
730
731        // If we have no sub-goals and no candidate rules, unprovable
732        if goal.sub_goals.is_empty() && goal.candidate_rules.is_empty() {
733            goal.status = GoalStatus::Unprovable;
734            return false;
735        }
736
737        goal.status = GoalStatus::Proven;
738        true
739    }
740}
741
742/// Breadth-first search implementation
743pub struct BreadthFirstSearch {
744    max_depth: usize,
745    goals_explored: usize,
746    executor: RuleExecutor,
747    proof_graph: Option<SharedProofGraph>,
748}
749
750/// Iterative deepening search implementation
751pub struct IterativeDeepeningSearch {
752    max_depth: usize,
753    goals_explored: usize,
754    kb: KnowledgeBase,
755    engine: Option<Arc<Mutex<IncrementalEngine>>>,
756}
757
758impl IterativeDeepeningSearch {
759    /// Create a new iterative deepening search
760    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
761        Self {
762            max_depth,
763            goals_explored: 0,
764            kb,
765            engine: None,
766        }
767    }
768
769    /// Create with optional IncrementalEngine for TMS integration
770    pub fn new_with_engine(
771        max_depth: usize,
772        kb: KnowledgeBase,
773        engine: Option<Arc<Mutex<IncrementalEngine>>>,
774    ) -> Self {
775        // Store the engine so we can pass it to DFS instances
776        Self {
777            max_depth,
778            goals_explored: 0,
779            kb,
780            engine,
781        }
782    }
783
784    /// Search with execution: probe with increasing depth using non-executing DFS,
785    /// then run a final executing DFS at the discovered depth to mutate facts.
786    pub fn search_with_execution(
787        &mut self,
788        root_goal: &mut Goal,
789        facts: &mut Facts,
790        kb: &KnowledgeBase,
791    ) -> SearchResult {
792        self.goals_explored = 0;
793        let mut cumulative_goals = 0usize;
794
795        // Try increasing depth limits
796        for depth_limit in 0..=self.max_depth {
797            // Probe using a non-executing depth-first search on a cloned goal
798            let mut probe_goal = root_goal.clone();
799            let probe_kb = self.kb.clone();
800            let mut probe_dfs = DepthFirstSearch::new(depth_limit, probe_kb);
801            let probe_result = probe_dfs.search(&mut probe_goal, facts);
802            cumulative_goals += probe_result.goals_explored;
803
804            if probe_result.success {
805                // Found a depth where a proof exists; execute for real at this depth
806                let exec_kb = self.kb.clone();
807                let mut exec_dfs =
808                    DepthFirstSearch::new_with_engine(depth_limit, exec_kb, self.engine.clone());
809                let exec_result = exec_dfs.search_with_execution(root_goal, facts, kb);
810                // Aggregate explored goals
811                let mut final_result = exec_result;
812                final_result.goals_explored += cumulative_goals - final_result.goals_explored;
813                return final_result;
814            }
815        }
816
817        // Nothing proved up to max_depth
818        SearchResult::failure(cumulative_goals, self.max_depth)
819    }
820
821    /// Non-executing search using iterative deepening (probes only)
822    pub fn search(&mut self, root_goal: &mut Goal, facts: &Facts) -> SearchResult {
823        self.goals_explored = 0;
824        let mut cumulative_goals = 0usize;
825
826        for depth_limit in 0..=self.max_depth {
827            let mut probe_goal = root_goal.clone();
828            let probe_kb = self.kb.clone();
829            let mut probe_dfs = DepthFirstSearch::new(depth_limit, probe_kb);
830            let probe_result = probe_dfs.search(&mut probe_goal, facts);
831            cumulative_goals += probe_result.goals_explored;
832            if probe_result.success {
833                // Return the successful probe result (with aggregated goals explored)
834                let mut res = probe_result;
835                res.goals_explored = cumulative_goals;
836                return res;
837            }
838        }
839
840        SearchResult::failure(cumulative_goals, self.max_depth)
841    }
842}
843
844impl BreadthFirstSearch {
845    /// Create a new breadth-first search
846    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
847        Self {
848            max_depth,
849            goals_explored: 0,
850            executor: RuleExecutor::new_with_inserter(kb, None),
851            proof_graph: None,
852        }
853    }
854
855    /// Create BFS with optional engine for TMS integration
856    pub fn new_with_engine(
857        max_depth: usize,
858        kb: KnowledgeBase,
859        engine: Option<Arc<Mutex<IncrementalEngine>>>,
860    ) -> Self {
861        // Create shared proof graph for caching ONLY if engine is provided
862        let proof_graph = engine.as_ref().map(|_| super::proof_graph::new_shared());
863        let proof_graph_clone = proof_graph.clone();
864
865        // If engine provided, create inserter closure
866        let inserter = engine.map(|eng| {
867            let eng = eng.clone();
868            let pg = proof_graph_clone.clone();
869
870            std::sync::Arc::new(
871                move |fact_type: String,
872                      data: crate::rete::TypedFacts,
873                      rule_name: String,
874                      premises: Vec<String>| {
875                    if let Ok(mut e) = eng.lock() {
876                        let handles = e.resolve_premise_keys(premises.clone());
877
878                        let handle = e.insert_logical(
879                            fact_type.clone(),
880                            data.clone(),
881                            rule_name.clone(),
882                            handles.clone(),
883                        );
884
885                        // Update proof graph
886                        if let Some(ref graph) = pg {
887                            if let Ok(mut g) = graph.lock() {
888                                let pattern = format!("{}.{}", fact_type, "derived");
889                                let key = FactKey::from_pattern(&pattern);
890                                g.insert_proof(handle, key, rule_name, handles, premises);
891                            }
892                        }
893                    }
894                },
895            )
896                as std::sync::Arc<
897                    dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync,
898                >
899        });
900
901        Self {
902            max_depth,
903            goals_explored: 0,
904            executor: RuleExecutor::new_with_inserter(kb, inserter),
905            proof_graph,
906        }
907    }
908
909    /// Search for a proof of the goal using BFS WITH rule execution
910    pub fn search_with_execution(
911        &mut self,
912        root_goal: &mut Goal,
913        facts: &mut Facts,
914        kb: &KnowledgeBase,
915    ) -> SearchResult {
916        self.goals_explored = 0;
917        let mut queue = VecDeque::new();
918        let mut path = Vec::new();
919        let mut max_depth = 0;
920
921        queue.push_back((root_goal as *mut Goal, 0));
922
923        while let Some((goal_ptr, depth)) = queue.pop_front() {
924            // Safety: We maintain ownership properly
925            let goal = unsafe { &mut *goal_ptr };
926
927            self.goals_explored += 1;
928            max_depth = max_depth.max(depth);
929
930            if depth > self.max_depth {
931                continue;
932            }
933
934            goal.depth = depth;
935
936            // Check if goal already satisfied by facts
937            if self.check_goal_in_facts(goal, facts) {
938                goal.status = GoalStatus::Proven;
939                continue;
940            }
941
942            // Try each candidate rule
943            for rule_name in goal.candidate_rules.clone() {
944                path.push(rule_name.clone());
945
946                // Get the rule from KB
947                if let Some(rule) = kb.get_rule(&rule_name) {
948                    // ✅ FIX: Try to execute rule (checks conditions AND executes actions)
949                    match self.executor.try_execute_rule(&rule, facts) {
950                        Ok(true) => {
951                            // Rule executed successfully - derived new facts!
952                            // Now check if our goal is proven
953                            if self.check_goal_in_facts(goal, facts) {
954                                goal.status = GoalStatus::Proven;
955                                break;
956                            }
957                        }
958                        Ok(false) => {
959                            // Conditions not satisfied - continue to next rule
960                        }
961                        Err(_) => {
962                            // Execution error - continue to next rule
963                        }
964                    }
965                }
966            }
967
968            // Add sub-goals to queue
969            for sub_goal in &mut goal.sub_goals {
970                queue.push_back((sub_goal as *mut Goal, depth + 1));
971            }
972        }
973
974        let success = root_goal.is_proven();
975
976        SearchResult {
977            success,
978            path,
979            goals_explored: self.goals_explored,
980            max_depth_reached: max_depth,
981            bindings: root_goal.bindings.to_map(),
982            solutions: Vec::new(),
983        }
984    }
985
986    /// Check if goal is already satisfied by facts
987    ///
988    /// This method now reuses ConditionEvaluator for proper evaluation
989    /// and checks ProofGraph cache first for previously proven facts
990    fn check_goal_in_facts(&self, goal: &Goal, facts: &Facts) -> bool {
991        // First check ProofGraph cache if available
992        if let Some(ref graph_arc) = self.proof_graph {
993            if let Ok(mut graph) = graph_arc.lock() {
994                let key = FactKey::from_pattern(&goal.pattern);
995                if graph.is_proven(&key) {
996                    // Cache hit! Goal was previously proven
997                    return true;
998                }
999            }
1000        }
1001
1002        // For negated goals, use the expression directly (parser strips NOT)
1003        if goal.is_negated {
1004            if let Some(ref expr) = goal.expression {
1005                // Expression.evaluate() returns Value, need to convert to bool
1006                match expr.evaluate(facts) {
1007                    Ok(Value::Boolean(b)) => return b,
1008                    Ok(_) => return false, // Non-boolean values are false
1009                    Err(_) => return false,
1010                }
1011            }
1012            return false;
1013        }
1014
1015        // Parse goal pattern into a Condition and use ConditionEvaluator
1016        if let Some(condition) = self.parse_goal_pattern(&goal.pattern) {
1017            // Use RuleExecutor's evaluator (which delegates to ConditionEvaluator)
1018            self.executor
1019                .evaluate_condition(&condition, facts)
1020                .unwrap_or(false)
1021        } else {
1022            false
1023        }
1024    }
1025
1026    /// Parse goal pattern string into a Condition object
1027    ///
1028    /// Examples:
1029    /// - "User.Score >= 80" → Condition { field: "User.Score", operator: GreaterThanOrEqual, value: Number(80) }
1030    /// - "User.IsVIP == true" → Condition { field: "User.IsVIP", operator: Equal, value: Boolean(true) }
1031    fn parse_goal_pattern(&self, pattern: &str) -> Option<crate::engine::rule::Condition> {
1032        use crate::engine::rule::{Condition, ConditionExpression};
1033        use crate::types::Operator;
1034
1035        // Try parsing operators in order (longest first to avoid conflicts)
1036        let operators = [
1037            (">=", Operator::GreaterThanOrEqual),
1038            ("<=", Operator::LessThanOrEqual),
1039            ("==", Operator::Equal),
1040            ("!=", Operator::NotEqual),
1041            (" > ", Operator::GreaterThan),
1042            (" < ", Operator::LessThan),
1043            (" contains ", Operator::Contains),
1044            (" not_contains ", Operator::NotContains),
1045            (" starts_with ", Operator::StartsWith),
1046            (" startsWith ", Operator::StartsWith),
1047            (" ends_with ", Operator::EndsWith),
1048            (" endsWith ", Operator::EndsWith),
1049            (" matches ", Operator::Matches),
1050        ];
1051
1052        for (op_str, operator) in operators {
1053            if let Some(pos) = pattern.find(op_str) {
1054                let field = pattern[..pos].trim().to_string();
1055                let value_str = pattern[pos + op_str.len()..].trim();
1056
1057                // Parse value
1058                let value = self.parse_value_string(value_str);
1059
1060                return Some(Condition {
1061                    field: field.clone(),
1062                    expression: ConditionExpression::Field(field),
1063                    operator,
1064                    value,
1065                });
1066            }
1067        }
1068
1069        None
1070    }
1071
1072    /// Parse value string into a Value
1073    fn parse_value_string(&self, s: &str) -> Value {
1074        let s = s.trim();
1075
1076        // Boolean
1077        if s == "true" {
1078            return Value::Boolean(true);
1079        }
1080        if s == "false" {
1081            return Value::Boolean(false);
1082        }
1083
1084        // Null
1085        if s == "null" {
1086            return Value::Null;
1087        }
1088
1089        // String (quoted)
1090        if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
1091            return Value::String(s[1..s.len() - 1].to_string());
1092        }
1093
1094        // Number (float)
1095        if let Ok(n) = s.parse::<f64>() {
1096            return Value::Number(n);
1097        }
1098
1099        // Integer
1100        if let Ok(i) = s.parse::<i64>() {
1101            return Value::Integer(i);
1102        }
1103
1104        // Default to string
1105        Value::String(s.to_string())
1106    }
1107
1108    /// Search for a proof of the goal using BFS (old method, kept for compatibility)
1109    pub fn search(&mut self, root_goal: &mut Goal, _facts: &Facts) -> SearchResult {
1110        self.goals_explored = 0;
1111        let mut queue = VecDeque::new();
1112        let mut path = Vec::new();
1113        let mut max_depth = 0;
1114
1115        queue.push_back((root_goal as *mut Goal, 0));
1116
1117        while let Some((goal_ptr, depth)) = queue.pop_front() {
1118            // Safety: We maintain ownership properly
1119            let goal = unsafe { &mut *goal_ptr };
1120
1121            self.goals_explored += 1;
1122            max_depth = max_depth.max(depth);
1123
1124            if depth > self.max_depth {
1125                continue;
1126            }
1127
1128            goal.depth = depth;
1129
1130            // Process candidate rules (collect names without cloning the Vec)
1131            for rule_name in &goal.candidate_rules {
1132                path.push(rule_name.clone());
1133            }
1134
1135            // Add sub-goals to queue
1136            for sub_goal in &mut goal.sub_goals {
1137                queue.push_back((sub_goal as *mut Goal, depth + 1));
1138            }
1139
1140            // Check if goal can be proven
1141            if !goal.candidate_rules.is_empty() || goal.all_subgoals_proven() {
1142                goal.status = GoalStatus::Proven;
1143            }
1144        }
1145
1146        let success = root_goal.is_proven();
1147
1148        SearchResult {
1149            success,
1150            path,
1151            goals_explored: self.goals_explored,
1152            max_depth_reached: max_depth,
1153            bindings: root_goal.bindings.to_map(),
1154            solutions: Vec::new(),
1155        }
1156    }
1157}
1158
1159#[cfg(test)]
1160mod tests {
1161    use super::*;
1162    use std::collections::HashMap;
1163
1164    #[test]
1165    fn test_search_strategies() {
1166        assert_eq!(SearchStrategy::DepthFirst, SearchStrategy::DepthFirst);
1167        assert_ne!(SearchStrategy::DepthFirst, SearchStrategy::BreadthFirst);
1168    }
1169
1170    #[test]
1171    fn test_search_result_creation() {
1172        let success = SearchResult::success(vec!["Rule1".to_string()], 5, 3);
1173        assert!(success.success);
1174        assert_eq!(success.path.len(), 1);
1175        assert_eq!(success.goals_explored, 5);
1176
1177        let failure = SearchResult::failure(10, 5);
1178        assert!(!failure.success);
1179        assert!(failure.path.is_empty());
1180    }
1181
1182    #[test]
1183    fn test_depth_first_search_creation() {
1184        let kb = KnowledgeBase::new("test");
1185        let dfs = DepthFirstSearch::new(10, kb);
1186        assert_eq!(dfs.max_depth, 10);
1187        assert_eq!(dfs.goals_explored, 0);
1188    }
1189
1190    #[test]
1191    fn test_depth_first_search_simple() {
1192        let kb = KnowledgeBase::new("test");
1193        let mut dfs = DepthFirstSearch::new(10, kb);
1194        let facts = Facts::new();
1195
1196        let mut goal = Goal::new("test".to_string());
1197        goal.add_candidate_rule("TestRule".to_string());
1198
1199        let result = dfs.search(&mut goal, &facts);
1200
1201        assert!(result.success);
1202        assert!(goal.is_proven());
1203        assert!(result.goals_explored > 0);
1204    }
1205
1206    #[test]
1207    fn test_breadth_first_search() {
1208        let kb = KnowledgeBase::new("test");
1209        let mut bfs = BreadthFirstSearch::new(10, kb);
1210        let facts = Facts::new();
1211
1212        let mut goal = Goal::new("test".to_string());
1213        goal.add_candidate_rule("TestRule".to_string());
1214
1215        let result = bfs.search(&mut goal, &facts);
1216
1217        assert!(result.success);
1218        assert_eq!(result.goals_explored, 1);
1219    }
1220
1221    #[test]
1222    fn test_iterative_deepening_search_success() {
1223        let kb = KnowledgeBase::new("test");
1224        let mut ids = IterativeDeepeningSearch::new(5, kb.clone());
1225        let mut root = Goal::new("test".to_string());
1226        root.add_candidate_rule("TestRule".to_string());
1227
1228        // Facts empty; DFS probe should succeed because candidate rules mark provable
1229        let facts = Facts::new();
1230        let res = ids.search(&mut root, &facts);
1231        assert!(res.success);
1232    }
1233
1234    #[test]
1235    fn test_iterative_deepening_search_depth_limit() {
1236        let kb = KnowledgeBase::new("test");
1237        // Set max_depth to 0 so even shallow proofs that require depth >0 fail
1238        let mut ids = IterativeDeepeningSearch::new(0, kb.clone());
1239        let mut root = Goal::new("test".to_string());
1240        // Add a subgoal to force depth > 0
1241        let mut sub = Goal::new("sub".to_string());
1242        sub.add_candidate_rule("SubRule".to_string());
1243        root.sub_goals.push(sub);
1244
1245        let facts = Facts::new();
1246        let res = ids.search(&mut root, &facts);
1247        assert!(!res.success);
1248    }
1249
1250    #[test]
1251    fn test_depth_first_search_max_depth_exceeded() {
1252        let kb = KnowledgeBase::new("test");
1253        let mut dfs = DepthFirstSearch::new(2, kb);
1254        let facts = Facts::new();
1255
1256        // Create nested goals exceeding max depth
1257        let mut root = Goal::new("level0".to_string());
1258        root.depth = 0;
1259        root.add_candidate_rule("Rule0".to_string());
1260
1261        let mut level1 = Goal::new("level1".to_string());
1262        level1.depth = 1;
1263        level1.add_candidate_rule("Rule1".to_string());
1264
1265        let mut level2 = Goal::new("level2".to_string());
1266        level2.depth = 2;
1267        level2.add_candidate_rule("Rule2".to_string());
1268
1269        let mut level3 = Goal::new("level3".to_string());
1270        level3.depth = 3; // Exceeds max_depth of 2
1271        level3.add_candidate_rule("Rule3".to_string());
1272
1273        level2.add_subgoal(level3);
1274        level1.add_subgoal(level2);
1275        root.add_subgoal(level1);
1276
1277        let result = dfs.search(&mut root, &facts);
1278
1279        // Verify search completed (max_depth_reached is set)
1280        assert!(result.max_depth_reached <= 3);
1281    }
1282
1283    #[test]
1284    fn test_breadth_first_search_multiple_candidates() {
1285        let kb = KnowledgeBase::new("test");
1286        let mut bfs = BreadthFirstSearch::new(10, kb);
1287        let facts = Facts::new();
1288
1289        let mut goal = Goal::new("multi_rule_goal".to_string());
1290        goal.add_candidate_rule("Rule1".to_string());
1291        goal.add_candidate_rule("Rule2".to_string());
1292        goal.add_candidate_rule("Rule3".to_string());
1293
1294        let result = bfs.search(&mut goal, &facts);
1295
1296        assert!(result.success);
1297        assert_eq!(goal.candidate_rules.len(), 3);
1298    }
1299
1300    #[test]
1301    fn test_depth_first_search_empty_goal() {
1302        let kb = KnowledgeBase::new("test");
1303        let mut dfs = DepthFirstSearch::new(10, kb);
1304        let facts = Facts::new();
1305
1306        let mut goal = Goal::new("".to_string());
1307        // No candidate rules, no subgoals
1308
1309        let result = dfs.search(&mut goal, &facts);
1310
1311        // Should fail - no way to prove empty goal
1312        assert!(!result.success);
1313    }
1314
1315    #[test]
1316    fn test_search_result_with_bindings() {
1317        use crate::types::Value;
1318        let mut bindings = HashMap::new();
1319        bindings.insert("X".to_string(), Value::String("test".to_string()));
1320        bindings.insert("Y".to_string(), Value::Number(42.0));
1321
1322        let result = SearchResult {
1323            success: true,
1324            path: vec!["Rule1".to_string()],
1325            goals_explored: 5,
1326            max_depth_reached: 3,
1327            bindings: bindings.clone(),
1328            solutions: Vec::new(),
1329        };
1330
1331        assert_eq!(result.bindings.len(), 2);
1332        assert_eq!(
1333            result.bindings.get("X"),
1334            Some(&Value::String("test".to_string()))
1335        );
1336    }
1337
1338    #[test]
1339    fn test_breadth_first_search_with_subgoals() {
1340        let kb = KnowledgeBase::new("test");
1341        let mut bfs = BreadthFirstSearch::new(10, kb);
1342        let facts = Facts::new();
1343
1344        let mut root = Goal::new("root".to_string());
1345        root.add_candidate_rule("RootRule".to_string());
1346
1347        let mut sub1 = Goal::new("sub1".to_string());
1348        sub1.add_candidate_rule("Sub1Rule".to_string());
1349
1350        let mut sub2 = Goal::new("sub2".to_string());
1351        sub2.add_candidate_rule("Sub2Rule".to_string());
1352
1353        root.add_subgoal(sub1);
1354        root.add_subgoal(sub2);
1355
1356        let result = bfs.search(&mut root, &facts);
1357
1358        assert!(result.success);
1359        assert!(result.goals_explored >= 3); // root + 2 subgoals
1360    }
1361
1362    #[test]
1363    fn test_iterative_deepening_search_no_candidates() {
1364        let kb = KnowledgeBase::new("test");
1365        let mut ids = IterativeDeepeningSearch::new(5, kb);
1366        let mut root = Goal::new("no_rules".to_string());
1367        // No candidate rules added
1368
1369        let facts = Facts::new();
1370        let result = ids.search(&mut root, &facts);
1371
1372        assert!(!result.success);
1373        assert!(result.path.is_empty());
1374    }
1375
1376    #[test]
1377    fn test_search_strategy_equality() {
1378        assert_eq!(SearchStrategy::BreadthFirst, SearchStrategy::BreadthFirst);
1379        assert_eq!(SearchStrategy::Iterative, SearchStrategy::Iterative);
1380        assert_ne!(SearchStrategy::BreadthFirst, SearchStrategy::Iterative);
1381    }
1382
1383    #[test]
1384    fn test_depth_first_search_goals_explored_count() {
1385        let kb = KnowledgeBase::new("test");
1386        let mut dfs = DepthFirstSearch::new(10, kb);
1387        let facts = Facts::new();
1388
1389        let mut root = Goal::new("root".to_string());
1390        root.add_candidate_rule("RootRule".to_string());
1391
1392        let mut sub = Goal::new("sub".to_string());
1393        sub.add_candidate_rule("SubRule".to_string());
1394
1395        root.add_subgoal(sub);
1396
1397        let result = dfs.search(&mut root, &facts);
1398
1399        // Search succeeded with candidate rules
1400        assert!(result.success);
1401        // Goals explored count is tracked (always >= 0 since it's usize)
1402        assert!(result.goals_explored > 0);
1403    }
1404}