ipfrs_tensorlogic/
reasoning.rs

1//! Distributed reasoning and inference engine
2//!
3//! Implements backward chaining inference with unification for TensorLogic.
4//! Features:
5//! - Backward chaining with SLD-resolution
6//! - Cycle detection to prevent infinite loops
7//! - Memoization for query caching
8//! - Goal decomposition tracking
9//! - Distributed reasoning support
10
11use crate::cache::{CacheManager, QueryKey};
12use crate::ir::{KnowledgeBase, Predicate, Rule, Term};
13use ipfrs_core::error::Result;
14use serde::{Deserialize, Serialize};
15use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17
18/// Variable substitution (bindings)
19pub type Substitution = HashMap<String, Term>;
20
21/// Proof tree representing the derivation of a goal
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct Proof {
24    /// The proven goal
25    pub goal: Predicate,
26    /// The rule or fact used
27    pub rule: Option<ProofRule>,
28    /// Sub-proofs for the rule body
29    pub subproofs: Vec<Proof>,
30}
31
32/// Rule used in a proof
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ProofRule {
35    /// Head of the rule
36    pub head: Predicate,
37    /// Body of the rule
38    pub body: Vec<Predicate>,
39    /// Whether this was a fact (empty body)
40    pub is_fact: bool,
41}
42
43impl Proof {
44    /// Create a proof for a fact
45    pub fn fact(goal: Predicate) -> Self {
46        Self {
47            rule: Some(ProofRule {
48                head: goal.clone(),
49                body: Vec::new(),
50                is_fact: true,
51            }),
52            goal,
53            subproofs: Vec::new(),
54        }
55    }
56
57    /// Create a proof from a rule and subproofs
58    pub fn from_rule(goal: Predicate, rule: &Rule, subproofs: Vec<Proof>) -> Self {
59        Self {
60            goal,
61            rule: Some(ProofRule {
62                head: rule.head.clone(),
63                body: rule.body.clone(),
64                is_fact: false,
65            }),
66            subproofs,
67        }
68    }
69
70    /// Get the depth of the proof tree
71    pub fn depth(&self) -> usize {
72        if self.subproofs.is_empty() {
73            1
74        } else {
75            1 + self.subproofs.iter().map(|p| p.depth()).max().unwrap_or(0)
76        }
77    }
78
79    /// Get the number of nodes in the proof tree
80    #[inline]
81    pub fn size(&self) -> usize {
82        1 + self.subproofs.iter().map(|p| p.size()).sum::<usize>()
83    }
84
85    /// Check if this proof is for a fact (no subproofs)
86    #[inline]
87    pub fn is_fact(&self) -> bool {
88        self.subproofs.is_empty()
89    }
90
91    /// Get all goals in the proof tree (flattened)
92    pub fn all_goals(&self) -> Vec<&Predicate> {
93        let mut goals = vec![&self.goal];
94        for subproof in &self.subproofs {
95            goals.extend(subproof.all_goals());
96        }
97        goals
98    }
99}
100
101/// Goal decomposition tracking for distributed reasoning
102#[derive(Debug, Clone)]
103pub struct GoalDecomposition {
104    /// The original goal
105    pub goal: Predicate,
106    /// Subgoals after decomposition
107    pub subgoals: Vec<Predicate>,
108    /// The rule used for decomposition (if any)
109    pub rule_applied: Option<Rule>,
110    /// Whether each subgoal was solved locally
111    pub local_solutions: Vec<bool>,
112    /// Depth in the decomposition tree
113    pub depth: usize,
114}
115
116impl GoalDecomposition {
117    /// Create a new decomposition for a goal
118    pub fn new(goal: Predicate, depth: usize) -> Self {
119        Self {
120            goal,
121            subgoals: Vec::new(),
122            rule_applied: None,
123            local_solutions: Vec::new(),
124            depth,
125        }
126    }
127
128    /// Apply a rule to decompose the goal
129    pub fn apply_rule(&mut self, rule: &Rule) {
130        self.rule_applied = Some(rule.clone());
131        self.subgoals = rule.body.clone();
132        self.local_solutions = vec![false; rule.body.len()];
133    }
134
135    /// Mark a subgoal as solved locally
136    pub fn mark_solved(&mut self, index: usize) {
137        if index < self.local_solutions.len() {
138            self.local_solutions[index] = true;
139        }
140    }
141
142    /// Check if all subgoals are solved
143    #[inline]
144    pub fn is_complete(&self) -> bool {
145        self.local_solutions.iter().all(|&solved| solved)
146    }
147
148    /// Get unsolved subgoals (for distributed forwarding)
149    pub fn unsolved_subgoals(&self) -> Vec<&Predicate> {
150        self.subgoals
151            .iter()
152            .zip(self.local_solutions.iter())
153            .filter(|(_, &solved)| !solved)
154            .map(|(sg, _)| sg)
155            .collect()
156    }
157}
158
159/// Cycle detection context for recursive queries
160#[derive(Debug, Clone, Default)]
161pub struct CycleDetector {
162    /// Stack of goals being solved
163    goal_stack: Vec<String>,
164    /// Set for O(1) lookup
165    goal_set: HashSet<String>,
166}
167
168impl CycleDetector {
169    /// Create a new cycle detector
170    pub fn new() -> Self {
171        Self::default()
172    }
173
174    /// Push a goal onto the stack, returns false if cycle detected
175    #[inline]
176    pub fn push(&mut self, goal: &Predicate) -> bool {
177        let key = goal_to_key(goal);
178        if self.goal_set.contains(&key) {
179            return false; // Cycle detected
180        }
181        self.goal_set.insert(key.clone());
182        self.goal_stack.push(key);
183        true
184    }
185
186    /// Pop a goal from the stack
187    #[inline]
188    pub fn pop(&mut self) {
189        if let Some(key) = self.goal_stack.pop() {
190            self.goal_set.remove(&key);
191        }
192    }
193
194    /// Check if a goal would create a cycle
195    #[inline]
196    pub fn would_cycle(&self, goal: &Predicate) -> bool {
197        let key = goal_to_key(goal);
198        self.goal_set.contains(&key)
199    }
200
201    /// Get the current depth
202    #[inline]
203    pub fn depth(&self) -> usize {
204        self.goal_stack.len()
205    }
206
207    /// Clear the detector
208    pub fn clear(&mut self) {
209        self.goal_stack.clear();
210        self.goal_set.clear();
211    }
212}
213
214/// Convert a goal predicate to a unique key for cycle detection
215fn goal_to_key(goal: &Predicate) -> String {
216    format!("{}({})", goal.name, goal.args.len())
217}
218
219/// Local inference engine with backward chaining
220#[derive(Default)]
221pub struct InferenceEngine {
222    /// Maximum proof depth to prevent infinite loops
223    max_depth: usize,
224    /// Maximum number of solutions to find
225    max_solutions: usize,
226    /// Enable cycle detection
227    cycle_detection: bool,
228}
229
230impl InferenceEngine {
231    /// Create a new inference engine with default settings
232    #[inline]
233    pub fn new() -> Self {
234        Self {
235            max_depth: 100,
236            max_solutions: 100,
237            cycle_detection: true,
238        }
239    }
240
241    /// Create an inference engine with custom limits
242    #[inline]
243    pub fn with_limits(max_depth: usize, max_solutions: usize) -> Self {
244        Self {
245            max_depth,
246            max_solutions,
247            cycle_detection: true,
248        }
249    }
250
251    /// Enable or disable cycle detection
252    #[inline]
253    pub fn with_cycle_detection(mut self, enabled: bool) -> Self {
254        self.cycle_detection = enabled;
255        self
256    }
257
258    /// Query the knowledge base for solutions to a goal
259    pub fn query(&self, goal: &Predicate, kb: &KnowledgeBase) -> Result<Vec<Substitution>> {
260        let mut solutions = Vec::new();
261        let initial_subst = Substitution::new();
262
263        self.solve_goal(goal, &initial_subst, kb, 0, &mut solutions)?;
264
265        Ok(solutions)
266    }
267
268    /// Prove a goal and return the proof tree
269    pub fn prove(&self, goal: &Predicate, kb: &KnowledgeBase) -> Result<Option<Proof>> {
270        let initial_subst = Substitution::new();
271        self.prove_goal(goal, &initial_subst, kb, 0)
272    }
273
274    /// Backward chaining resolution for a single goal
275    fn solve_goal(
276        &self,
277        goal: &Predicate,
278        subst: &Substitution,
279        kb: &KnowledgeBase,
280        depth: usize,
281        solutions: &mut Vec<Substitution>,
282    ) -> Result<()> {
283        // Check depth limit
284        if depth > self.max_depth {
285            return Ok(());
286        }
287
288        // Check solution limit
289        if solutions.len() >= self.max_solutions {
290            return Ok(());
291        }
292
293        // Apply current substitution to goal
294        let goal = apply_subst_predicate(goal, subst);
295
296        // Try to unify with facts
297        for fact in kb.get_predicates(&goal.name) {
298            if let Some(new_subst) = unify_predicates(&goal, fact, subst) {
299                solutions.push(new_subst);
300                if solutions.len() >= self.max_solutions {
301                    return Ok(());
302                }
303            }
304        }
305
306        // Try to unify with rule heads
307        for rule in kb.get_rules(&goal.name) {
308            // Rename variables in the rule to avoid conflicts
309            let renamed_rule = rename_rule_vars(rule, depth);
310
311            // Try to unify goal with rule head
312            if let Some(new_subst) = unify_predicates(&goal, &renamed_rule.head, subst) {
313                // Solve rule body
314                self.solve_conjunction(&renamed_rule.body, &new_subst, kb, depth + 1, solutions)?;
315            }
316        }
317
318        Ok(())
319    }
320
321    /// Solve a conjunction of goals (rule body)
322    fn solve_conjunction(
323        &self,
324        goals: &[Predicate],
325        subst: &Substitution,
326        kb: &KnowledgeBase,
327        depth: usize,
328        solutions: &mut Vec<Substitution>,
329    ) -> Result<()> {
330        if goals.is_empty() {
331            // All goals satisfied, add solution
332            solutions.push(subst.clone());
333            return Ok(());
334        }
335
336        // Solve first goal
337        let first_goal = &goals[0];
338        let rest_goals = &goals[1..];
339
340        let mut intermediate_solutions = Vec::new();
341        self.solve_goal(first_goal, subst, kb, depth, &mut intermediate_solutions)?;
342
343        // For each solution of the first goal, solve the rest
344        for intermediate_subst in intermediate_solutions {
345            if solutions.len() >= self.max_solutions {
346                return Ok(());
347            }
348            self.solve_conjunction(rest_goals, &intermediate_subst, kb, depth, solutions)?;
349        }
350
351        Ok(())
352    }
353
354    /// Prove a goal and build the proof tree
355    fn prove_goal(
356        &self,
357        goal: &Predicate,
358        subst: &Substitution,
359        kb: &KnowledgeBase,
360        depth: usize,
361    ) -> Result<Option<Proof>> {
362        // Check depth limit
363        if depth > self.max_depth {
364            return Ok(None);
365        }
366
367        // Apply current substitution to goal
368        let goal = apply_subst_predicate(goal, subst);
369
370        // Try to unify with facts
371        for fact in kb.get_predicates(&goal.name) {
372            if let Some(_new_subst) = unify_predicates(&goal, fact, subst) {
373                return Ok(Some(Proof::fact(goal)));
374            }
375        }
376
377        // Try to unify with rule heads
378        for rule in kb.get_rules(&goal.name) {
379            // Rename variables in the rule to avoid conflicts
380            let renamed_rule = rename_rule_vars(rule, depth);
381
382            // Try to unify goal with rule head
383            if let Some(new_subst) = unify_predicates(&goal, &renamed_rule.head, subst) {
384                // Prove rule body
385                if let Some(subproofs) =
386                    self.prove_conjunction(&renamed_rule.body, &new_subst, kb, depth + 1)?
387                {
388                    return Ok(Some(Proof::from_rule(goal, &renamed_rule, subproofs)));
389                }
390            }
391        }
392
393        Ok(None)
394    }
395
396    /// Prove a conjunction of goals
397    fn prove_conjunction(
398        &self,
399        goals: &[Predicate],
400        subst: &Substitution,
401        kb: &KnowledgeBase,
402        depth: usize,
403    ) -> Result<Option<Vec<Proof>>> {
404        if goals.is_empty() {
405            return Ok(Some(Vec::new()));
406        }
407
408        let first_goal = &goals[0];
409        let rest_goals = &goals[1..];
410
411        // Prove first goal
412        if let Some(first_proof) = self.prove_goal(first_goal, subst, kb, depth)? {
413            // Get the substitution from the proof (simplified - just use current subst)
414            if let Some(rest_proofs) = self.prove_conjunction(rest_goals, subst, kb, depth)? {
415                let mut all_proofs = vec![first_proof];
416                all_proofs.extend(rest_proofs);
417                return Ok(Some(all_proofs));
418            }
419        }
420
421        Ok(None)
422    }
423
424    /// Verify that a proof is valid against a knowledge base
425    pub fn verify(&self, proof: &Proof, kb: &KnowledgeBase) -> Result<bool> {
426        self.verify_proof_recursive(proof, kb, 0)
427    }
428
429    /// Recursively verify a proof tree
430    fn verify_proof_recursive(
431        &self,
432        proof: &Proof,
433        kb: &KnowledgeBase,
434        depth: usize,
435    ) -> Result<bool> {
436        // Check depth limit to prevent infinite recursion
437        if depth > self.max_depth {
438            return Ok(false);
439        }
440
441        // Check if proof has a rule
442        let Some(ref rule) = proof.rule else {
443            return Ok(false);
444        };
445
446        // Verify fact proof
447        if rule.is_fact {
448            // Check if the fact exists in the knowledge base
449            let facts = kb.get_predicates(&proof.goal.name);
450            for fact in facts {
451                if unify_predicates(&proof.goal, fact, &Substitution::new()).is_some() {
452                    return Ok(true);
453                }
454            }
455            return Ok(false);
456        }
457
458        // Verify rule proof
459        // 1. Check that the rule exists in KB
460        let rules = kb.get_rules(&proof.goal.name);
461        let mut rule_exists = false;
462        for kb_rule in rules {
463            // Check if rule heads match
464            if kb_rule.head.name == rule.head.name
465                && kb_rule.head.args.len() == rule.head.args.len()
466                && kb_rule.body.len() == rule.body.len()
467            {
468                // Check if body predicates match
469                let bodies_match = kb_rule
470                    .body
471                    .iter()
472                    .zip(rule.body.iter())
473                    .all(|(b1, b2)| b1.name == b2.name && b1.args.len() == b2.args.len());
474
475                if bodies_match {
476                    rule_exists = true;
477                    break;
478                }
479            }
480        }
481
482        if !rule_exists {
483            return Ok(false);
484        }
485
486        // 2. Verify that the number of subproofs matches the rule body
487        if proof.subproofs.len() != rule.body.len() {
488            return Ok(false);
489        }
490
491        // 3. Verify each subproof
492        for (i, subproof) in proof.subproofs.iter().enumerate() {
493            // Check that subproof goal matches the corresponding body predicate
494            let body_predicate = &rule.body[i];
495            if subproof.goal.name != body_predicate.name {
496                return Ok(false);
497            }
498
499            // Recursively verify the subproof
500            if !self.verify_proof_recursive(subproof, kb, depth + 1)? {
501                return Ok(false);
502            }
503        }
504
505        // All checks passed
506        Ok(true)
507    }
508}
509
510/// Unify two terms with a substitution
511pub fn unify(t1: &Term, t2: &Term, subst: &Substitution) -> Option<Substitution> {
512    let t1 = apply_subst_term(t1, subst);
513    let t2 = apply_subst_term(t2, subst);
514
515    match (&t1, &t2) {
516        // Two identical constants
517        (Term::Const(c1), Term::Const(c2)) if c1 == c2 => Some(subst.clone()),
518
519        // Variable unification
520        (Term::Var(v), t) | (t, Term::Var(v)) => {
521            if let Term::Var(v2) = t {
522                if v == v2 {
523                    return Some(subst.clone());
524                }
525            }
526            // Occurs check
527            if occurs_in(v, t) {
528                return None;
529            }
530            let mut new_subst = subst.clone();
531            new_subst.insert(v.clone(), t.clone());
532            Some(new_subst)
533        }
534
535        // Function unification
536        (Term::Fun(f1, args1), Term::Fun(f2, args2)) if f1 == f2 && args1.len() == args2.len() => {
537            let mut current_subst = subst.clone();
538            for (a1, a2) in args1.iter().zip(args2.iter()) {
539                match unify(a1, a2, &current_subst) {
540                    Some(new_subst) => current_subst = new_subst,
541                    None => return None,
542                }
543            }
544            Some(current_subst)
545        }
546
547        // References - assume equal if CIDs match
548        (Term::Ref(r1), Term::Ref(r2)) if r1.cid == r2.cid => Some(subst.clone()),
549
550        _ => None,
551    }
552}
553
554/// Unify two predicates
555pub fn unify_predicates(
556    p1: &Predicate,
557    p2: &Predicate,
558    subst: &Substitution,
559) -> Option<Substitution> {
560    if p1.name != p2.name || p1.args.len() != p2.args.len() {
561        return None;
562    }
563
564    let mut current_subst = subst.clone();
565    for (a1, a2) in p1.args.iter().zip(p2.args.iter()) {
566        match unify(a1, a2, &current_subst) {
567            Some(new_subst) => current_subst = new_subst,
568            None => return None,
569        }
570    }
571
572    Some(current_subst)
573}
574
575/// Check if a variable occurs in a term (for occurs check)
576fn occurs_in(var: &str, term: &Term) -> bool {
577    match term {
578        Term::Var(v) => v == var,
579        Term::Fun(_, args) => args.iter().any(|t| occurs_in(var, t)),
580        _ => false,
581    }
582}
583
584/// Apply substitution to a term
585pub fn apply_subst_term(term: &Term, subst: &Substitution) -> Term {
586    match term {
587        Term::Var(v) => subst.get(v).cloned().unwrap_or_else(|| term.clone()),
588        Term::Fun(f, args) => {
589            let new_args = args.iter().map(|t| apply_subst_term(t, subst)).collect();
590            Term::Fun(f.clone(), new_args)
591        }
592        _ => term.clone(),
593    }
594}
595
596/// Apply substitution to a predicate
597pub fn apply_subst_predicate(pred: &Predicate, subst: &Substitution) -> Predicate {
598    Predicate {
599        name: pred.name.clone(),
600        args: pred
601            .args
602            .iter()
603            .map(|t| apply_subst_term(t, subst))
604            .collect(),
605    }
606}
607
608/// Rename variables in a rule to avoid conflicts
609fn rename_rule_vars(rule: &Rule, suffix: usize) -> Rule {
610    let var_map: HashMap<String, String> = rule
611        .variables()
612        .into_iter()
613        .map(|v| (v.clone(), format!("{}_{}", v, suffix)))
614        .collect();
615
616    let rename_subst: Substitution = var_map
617        .into_iter()
618        .map(|(old, new)| (old, Term::Var(new)))
619        .collect();
620
621    Rule {
622        head: apply_subst_predicate(&rule.head, &rename_subst),
623        body: rule
624            .body
625            .iter()
626            .map(|p| apply_subst_predicate(p, &rename_subst))
627            .collect(),
628    }
629}
630
631/// Memoized inference engine with query caching
632pub struct MemoizedInferenceEngine {
633    /// Base inference engine
634    engine: InferenceEngine,
635    /// Cache manager
636    cache: Arc<CacheManager>,
637}
638
639impl MemoizedInferenceEngine {
640    /// Create a new memoized inference engine
641    pub fn new(cache: Arc<CacheManager>) -> Self {
642        Self {
643            engine: InferenceEngine::new(),
644            cache,
645        }
646    }
647
648    /// Create with custom inference limits
649    pub fn with_limits(max_depth: usize, max_solutions: usize, cache: Arc<CacheManager>) -> Self {
650        Self {
651            engine: InferenceEngine::with_limits(max_depth, max_solutions),
652            cache,
653        }
654    }
655
656    /// Query with memoization
657    pub fn query(&self, goal: &Predicate, kb: &KnowledgeBase) -> Result<Vec<Substitution>> {
658        // Create query key
659        let key = QueryKey::from_predicate(goal);
660
661        // Check cache first
662        if let Some(cached) = self.cache.query_cache.get(&key) {
663            return Ok(cached);
664        }
665
666        // Execute query
667        let solutions = self.engine.query(goal, kb)?;
668
669        // Cache results
670        if !solutions.is_empty() {
671            self.cache.query_cache.insert(key, solutions.clone());
672        }
673
674        Ok(solutions)
675    }
676
677    /// Prove with memoization
678    pub fn prove(&self, goal: &Predicate, kb: &KnowledgeBase) -> Result<Option<Proof>> {
679        // For proofs, we don't cache directly but could cache intermediate results
680        self.engine.prove(goal, kb)
681    }
682
683    /// Get cache statistics
684    #[inline]
685    pub fn cache_stats(&self) -> crate::cache::CombinedCacheStats {
686        self.cache.stats()
687    }
688
689    /// Clear the cache
690    pub fn clear_cache(&self) {
691        self.cache.query_cache.clear();
692    }
693}
694
695/// Distributed reasoning engine with caching support
696pub struct DistributedReasoner {
697    /// Local inference engine
698    engine: InferenceEngine,
699    /// Cache manager (optional)
700    cache: Option<Arc<CacheManager>>,
701    /// Goal decomposition tracker
702    decompositions: Vec<GoalDecomposition>,
703}
704
705impl DistributedReasoner {
706    /// Create a new distributed reasoner
707    pub fn new() -> Result<Self> {
708        Ok(Self {
709            engine: InferenceEngine::new(),
710            cache: None,
711            decompositions: Vec::new(),
712        })
713    }
714
715    /// Create with a cache manager
716    pub fn with_cache(cache: Arc<CacheManager>) -> Result<Self> {
717        Ok(Self {
718            engine: InferenceEngine::new(),
719            cache: Some(cache),
720            decompositions: Vec::new(),
721        })
722    }
723
724    /// Query locally with optional caching
725    pub async fn query(&self, goal: &Predicate, kb: &KnowledgeBase) -> Result<Vec<Substitution>> {
726        // Check query cache first
727        if let Some(cache) = &self.cache {
728            let key = QueryKey::from_predicate(goal);
729            if let Some(cached) = cache.query_cache.get(&key) {
730                return Ok(cached);
731            }
732
733            // Execute query
734            let solutions = self.engine.query(goal, kb)?;
735
736            // Cache results
737            if !solutions.is_empty() {
738                cache.query_cache.insert(key, solutions.clone());
739            }
740
741            Ok(solutions)
742        } else {
743            self.engine.query(goal, kb)
744        }
745    }
746
747    /// Prove a goal with goal decomposition tracking
748    pub async fn prove(&self, goal: &Predicate, kb: &KnowledgeBase) -> Result<Option<Proof>> {
749        self.engine.prove(goal, kb)
750    }
751
752    /// Prove with goal decomposition tracking
753    pub async fn prove_with_decomposition(
754        &mut self,
755        goal: &Predicate,
756        kb: &KnowledgeBase,
757    ) -> Result<(Option<Proof>, Vec<GoalDecomposition>)> {
758        self.decompositions.clear();
759        let proof = self.prove_tracking(goal, kb, 0)?;
760        let decomps = std::mem::take(&mut self.decompositions);
761        Ok((proof, decomps))
762    }
763
764    /// Internal prove with decomposition tracking
765    fn prove_tracking(
766        &mut self,
767        goal: &Predicate,
768        kb: &KnowledgeBase,
769        depth: usize,
770    ) -> Result<Option<Proof>> {
771        // Create decomposition record
772        let mut decomp = GoalDecomposition::new(goal.clone(), depth);
773
774        // Try facts first
775        for fact in kb.get_predicates(&goal.name) {
776            if unify_predicates(goal, fact, &Substitution::new()).is_some() {
777                self.decompositions.push(decomp);
778                return Ok(Some(Proof::fact(goal.clone())));
779            }
780        }
781
782        // Try rules
783        for rule in kb.get_rules(&goal.name) {
784            let renamed_rule = rename_rule_vars(rule, depth);
785
786            if let Some(subst) = unify_predicates(goal, &renamed_rule.head, &Substitution::new()) {
787                decomp.apply_rule(&renamed_rule);
788
789                // Prove subgoals
790                let mut subproofs = Vec::new();
791                let mut all_proved = true;
792
793                for (i, subgoal) in renamed_rule.body.iter().enumerate() {
794                    let subgoal = apply_subst_predicate(subgoal, &subst);
795                    if let Some(subproof) = self.prove_tracking(&subgoal, kb, depth + 1)? {
796                        subproofs.push(subproof);
797                        decomp.mark_solved(i);
798                    } else {
799                        all_proved = false;
800                        break;
801                    }
802                }
803
804                if all_proved {
805                    self.decompositions.push(decomp);
806                    return Ok(Some(Proof::from_rule(
807                        goal.clone(),
808                        &renamed_rule,
809                        subproofs,
810                    )));
811                }
812            }
813        }
814
815        self.decompositions.push(decomp);
816        Ok(None)
817    }
818
819    /// Get unsolved goals that could be forwarded to peers
820    pub fn get_unsolved_goals(&self) -> Vec<&Predicate> {
821        self.decompositions
822            .iter()
823            .flat_map(|d| d.unsolved_subgoals())
824            .collect()
825    }
826
827    /// Get cache statistics (if cache is available)
828    pub fn cache_stats(&self) -> Option<crate::cache::CombinedCacheStats> {
829        self.cache.as_ref().map(|c| c.stats())
830    }
831}
832
833impl Default for DistributedReasoner {
834    fn default() -> Self {
835        Self {
836            engine: InferenceEngine::new(),
837            cache: None,
838            decompositions: Vec::new(),
839        }
840    }
841}
842
843#[cfg(test)]
844mod tests {
845    use super::*;
846    use crate::ir::Constant;
847
848    #[test]
849    fn test_unify_constants() {
850        let t1 = Term::Const(Constant::String("Alice".to_string()));
851        let t2 = Term::Const(Constant::String("Alice".to_string()));
852        let subst = Substitution::new();
853
854        assert!(unify(&t1, &t2, &subst).is_some());
855    }
856
857    #[test]
858    fn test_unify_var_const() {
859        let t1 = Term::Var("X".to_string());
860        let t2 = Term::Const(Constant::String("Alice".to_string()));
861        let subst = Substitution::new();
862
863        let result = unify(&t1, &t2, &subst);
864        assert!(result.is_some());
865
866        let result_subst = result.unwrap();
867        assert_eq!(
868            result_subst.get("X"),
869            Some(&Term::Const(Constant::String("Alice".to_string())))
870        );
871    }
872
873    #[test]
874    fn test_unify_functions() {
875        let t1 = Term::Fun(
876            "f".to_string(),
877            vec![Term::Var("X".to_string()), Term::Const(Constant::Int(1))],
878        );
879        let t2 = Term::Fun(
880            "f".to_string(),
881            vec![
882                Term::Const(Constant::String("a".to_string())),
883                Term::Const(Constant::Int(1)),
884            ],
885        );
886        let subst = Substitution::new();
887
888        let result = unify(&t1, &t2, &subst);
889        assert!(result.is_some());
890    }
891
892    #[test]
893    fn test_occurs_check() {
894        let t1 = Term::Var("X".to_string());
895        let t2 = Term::Fun("f".to_string(), vec![Term::Var("X".to_string())]);
896        let subst = Substitution::new();
897
898        assert!(unify(&t1, &t2, &subst).is_none());
899    }
900
901    #[test]
902    fn test_inference_fact() {
903        let mut kb = KnowledgeBase::new();
904        kb.add_fact(Predicate::new(
905            "parent".to_string(),
906            vec![
907                Term::Const(Constant::String("Alice".to_string())),
908                Term::Const(Constant::String("Bob".to_string())),
909            ],
910        ));
911
912        let engine = InferenceEngine::new();
913        let goal = Predicate::new(
914            "parent".to_string(),
915            vec![
916                Term::Const(Constant::String("Alice".to_string())),
917                Term::Var("X".to_string()),
918            ],
919        );
920
921        let solutions = engine.query(&goal, &kb).unwrap();
922        assert_eq!(solutions.len(), 1);
923        assert_eq!(
924            solutions[0].get("X"),
925            Some(&Term::Const(Constant::String("Bob".to_string())))
926        );
927    }
928
929    #[test]
930    fn test_inference_rule() {
931        let mut kb = KnowledgeBase::new();
932
933        // Facts: parent(alice, bob), parent(bob, charlie)
934        kb.add_fact(Predicate::new(
935            "parent".to_string(),
936            vec![
937                Term::Const(Constant::String("alice".to_string())),
938                Term::Const(Constant::String("bob".to_string())),
939            ],
940        ));
941        kb.add_fact(Predicate::new(
942            "parent".to_string(),
943            vec![
944                Term::Const(Constant::String("bob".to_string())),
945                Term::Const(Constant::String("charlie".to_string())),
946            ],
947        ));
948
949        // Rule: grandparent(X, Z) :- parent(X, Y), parent(Y, Z)
950        let rule = Rule::new(
951            Predicate::new(
952                "grandparent".to_string(),
953                vec![Term::Var("X".to_string()), Term::Var("Z".to_string())],
954            ),
955            vec![
956                Predicate::new(
957                    "parent".to_string(),
958                    vec![Term::Var("X".to_string()), Term::Var("Y".to_string())],
959                ),
960                Predicate::new(
961                    "parent".to_string(),
962                    vec![Term::Var("Y".to_string()), Term::Var("Z".to_string())],
963                ),
964            ],
965        );
966        kb.add_rule(rule);
967
968        let engine = InferenceEngine::new();
969        let goal = Predicate::new(
970            "grandparent".to_string(),
971            vec![
972                Term::Const(Constant::String("alice".to_string())),
973                Term::Var("Z".to_string()),
974            ],
975        );
976
977        let solutions = engine.query(&goal, &kb).unwrap();
978        assert!(!solutions.is_empty());
979    }
980}