Skip to main content

tensorlogic_ir/
resolution.rs

1//! # Resolution-Based Theorem Proving
2//!
3//! This module implements Robinson's resolution principle for automated theorem proving.
4//! Resolution is a refutation-based proof procedure: to prove `Γ ⊢ φ`, we show that
5//! `Γ ∪ {¬φ}` is unsatisfiable by deriving the empty clause (⊥).
6//!
7//! ## Overview
8//!
9//! **Resolution** is a complete inference rule for first-order logic:
10//! - Given clauses `C₁ ∨ L` and `C₂ ∨ ¬L`, derive resolvent `C₁ ∨ C₂`
11//! - The empty clause (∅) represents a contradiction
12//! - If ∅ is derived, the original clause set is unsatisfiable
13//!
14//! ## Key Components
15//!
16//! ### Literals
17//! A literal is an atom or its negation:
18//! - Positive literal: `P(x, y)`
19//! - Negative literal: `¬P(x, y)`
20//!
21//! ### Clauses
22//! A clause is a disjunction of literals:
23//! - `P(x) ∨ Q(x) ∨ ¬R(y)`
24//! - Empty clause: `∅` (contradiction)
25//! - Unit clause: single literal
26//!
27//! ### Resolution Rule
28//! From clauses `C₁ ∨ L` and `C₂ ∨ ¬L`, derive `C₁ ∨ C₂`:
29//! ```text
30//!     C₁ ∨ L    C₂ ∨ ¬L
31//!     ───────────────────
32//!         C₁ ∨ C₂
33//! ```
34//!
35//! ## Algorithms
36//!
37//! 1. **Saturation**: Generate all resolvents until empty clause or saturation
38//! 2. **Set-of-Support**: Focus resolution on specific clause set
39//! 3. **Linear Resolution**: Chain resolutions from initial clause
40//! 4. **Unit Resolution**: Only resolve with unit clauses (more efficient)
41//!
42//! ## Example
43//!
44//! ```rust
45//! use tensorlogic_ir::{TLExpr, Term, Clause, Literal, ResolutionProver};
46//!
47//! // Clauses: { P(a), ¬P(a) }
48//! // This is unsatisfiable (derives empty clause via direct resolution)
49//! let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
50//! let not_p_a = Literal::negative(TLExpr::pred("P", vec![Term::constant("a")]));
51//!
52//! let mut prover = ResolutionProver::new();
53//! prover.add_clause(Clause::from_literals(vec![p_a]));
54//! prover.add_clause(Clause::from_literals(vec![not_p_a]));
55//!
56//! let result = prover.prove();
57//! assert!(result.is_unsatisfiable());
58//! ```
59
60use crate::error::IrError;
61use crate::expr::TLExpr;
62use crate::term::Term;
63use crate::unification::{rename_vars, unify_term_list, Substitution};
64use serde::{Deserialize, Serialize};
65use std::collections::{HashSet, VecDeque};
66
67/// A literal is an atomic formula or its negation.
68#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
69pub struct Literal {
70    /// The underlying atomic formula
71    pub atom: TLExpr,
72    /// True if positive literal, false if negative
73    pub polarity: bool,
74}
75
76impl Literal {
77    /// Create a positive literal from an atomic formula.
78    pub fn positive(atom: TLExpr) -> Self {
79        Literal {
80            atom,
81            polarity: true,
82        }
83    }
84
85    /// Create a negative literal from an atomic formula.
86    pub fn negative(atom: TLExpr) -> Self {
87        Literal {
88            atom,
89            polarity: false,
90        }
91    }
92
93    /// Negate this literal.
94    pub fn negate(&self) -> Self {
95        Literal {
96            atom: self.atom.clone(),
97            polarity: !self.polarity,
98        }
99    }
100
101    /// Check if this literal is complementary to another (same atom, opposite polarity).
102    ///
103    /// For ground literals (no variables), this checks exact equality.
104    pub fn is_complementary(&self, other: &Literal) -> bool {
105        self.atom == other.atom && self.polarity != other.polarity
106    }
107
108    /// Attempt to unify this literal with another for resolution.
109    ///
110    /// Returns the most general unifier (MGU) if the atoms can be unified
111    /// and the polarities are opposite.
112    ///
113    /// This is used for first-order resolution with variables.
114    ///
115    /// # Examples
116    ///
117    /// ```rust
118    /// use tensorlogic_ir::{TLExpr, Term, Literal};
119    ///
120    /// // P(x) and ¬P(a) can be unified with {x/a}
121    /// let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
122    /// let not_p_a = Literal::negative(TLExpr::pred("P", vec![Term::constant("a")]));
123    ///
124    /// let mgu = p_x.try_unify(&not_p_a);
125    /// assert!(mgu.is_some());
126    /// ```
127    pub fn try_unify(&self, other: &Literal) -> Option<Substitution> {
128        // Must have opposite polarity
129        if self.polarity == other.polarity {
130            return None;
131        }
132
133        // Try to unify the atoms
134        self.try_unify_atoms(&other.atom)
135    }
136
137    /// Attempt to unify this literal's atom with another expression.
138    ///
139    /// This extracts terms from predicates and attempts unification.
140    fn try_unify_atoms(&self, other_atom: &TLExpr) -> Option<Substitution> {
141        match (&self.atom, other_atom) {
142            (
143                TLExpr::Pred {
144                    name: n1,
145                    args: args1,
146                },
147                TLExpr::Pred {
148                    name: n2,
149                    args: args2,
150                },
151            ) => {
152                // Predicate names must match
153                if n1 != n2 {
154                    return None;
155                }
156
157                // Arity must match
158                if args1.len() != args2.len() {
159                    return None;
160                }
161
162                // Unify argument lists
163                let pairs: Vec<(Term, Term)> = args1
164                    .iter()
165                    .zip(args2.iter())
166                    .map(|(t1, t2)| (t1.clone(), t2.clone()))
167                    .collect();
168
169                unify_term_list(&pairs).ok()
170            }
171            _ => None,
172        }
173    }
174
175    /// Apply a substitution to this literal.
176    ///
177    /// This creates a new literal with the substitution applied to all terms.
178    pub fn apply_substitution(&self, subst: &Substitution) -> Literal {
179        let new_atom = self.apply_subst_to_expr(&self.atom, subst);
180        Literal {
181            atom: new_atom,
182            polarity: self.polarity,
183        }
184    }
185
186    /// Apply substitution to an expression (helper for apply_substitution).
187    fn apply_subst_to_expr(&self, expr: &TLExpr, subst: &Substitution) -> TLExpr {
188        match expr {
189            TLExpr::Pred { name, args } => {
190                let new_args = args.iter().map(|term| subst.apply(term)).collect();
191                TLExpr::Pred {
192                    name: name.clone(),
193                    args: new_args,
194                }
195            }
196            // For other expression types, return as-is
197            _ => expr.clone(),
198        }
199    }
200
201    /// Check if this is a positive literal.
202    pub fn is_positive(&self) -> bool {
203        self.polarity
204    }
205
206    /// Check if this is a negative literal.
207    pub fn is_negative(&self) -> bool {
208        !self.polarity
209    }
210
211    /// Get the free variables in this literal.
212    pub fn free_vars(&self) -> HashSet<String> {
213        self.atom.free_vars()
214    }
215}
216
217/// A clause is a disjunction of literals: `L₁ ∨ L₂ ∨ ... ∨ Lₙ`.
218///
219/// Special cases:
220/// - Empty clause (∅): contradiction, no literals
221/// - Unit clause: single literal
222/// - Horn clause: at most one positive literal
223#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
224pub struct Clause {
225    /// The literals in this clause (disjunction)
226    pub literals: Vec<Literal>,
227}
228
229impl Clause {
230    /// Create a new clause from a list of literals.
231    pub fn from_literals(literals: Vec<Literal>) -> Self {
232        // Remove duplicates and sort for consistency
233        let mut unique_lits: Vec<Literal> = literals.into_iter().collect();
234        unique_lits.sort_by(|a, b| {
235            let a_str = format!("{:?}", a);
236            let b_str = format!("{:?}", b);
237            a_str.cmp(&b_str)
238        });
239        unique_lits.dedup();
240
241        Clause {
242            literals: unique_lits,
243        }
244    }
245
246    /// Create an empty clause (contradiction).
247    pub fn empty() -> Self {
248        Clause { literals: vec![] }
249    }
250
251    /// Create a unit clause (single literal).
252    pub fn unit(literal: Literal) -> Self {
253        Clause {
254            literals: vec![literal],
255        }
256    }
257
258    /// Check if this is the empty clause (contradiction).
259    pub fn is_empty(&self) -> bool {
260        self.literals.is_empty()
261    }
262
263    /// Check if this is a unit clause (single literal).
264    pub fn is_unit(&self) -> bool {
265        self.literals.len() == 1
266    }
267
268    /// Check if this is a Horn clause (at most one positive literal).
269    pub fn is_horn(&self) -> bool {
270        self.literals.iter().filter(|l| l.is_positive()).count() <= 1
271    }
272
273    /// Get the number of literals in this clause.
274    pub fn len(&self) -> usize {
275        self.literals.len()
276    }
277
278    /// Check if clause is empty (different from is_empty which checks for contradiction).
279    pub fn is_len_zero(&self) -> bool {
280        self.literals.is_empty()
281    }
282
283    /// Get all free variables in this clause.
284    pub fn free_vars(&self) -> HashSet<String> {
285        self.literals
286            .iter()
287            .flat_map(|lit| lit.free_vars())
288            .collect()
289    }
290
291    /// Check if this clause subsumes another (is more general).
292    ///
293    /// **Theta-Subsumption**: Clause C subsumes D (C ⪯ D) if there exists a
294    /// substitution θ such that Cθ ⊆ D.
295    ///
296    /// This means C is more general than D. For example:
297    /// - `{P(x)}` subsumes `{P(a), Q(a)}` with θ = {x/a}
298    /// - `{P(x), Q(x)}` subsumes `{P(a), Q(a), R(a)}` with θ = {x/a}
299    ///
300    /// # Implementation
301    ///
302    /// We try to find a substitution by:
303    /// 1. For each literal in C, try to unify it with some literal in D
304    /// 2. Check if all substitutions are consistent
305    /// 3. If successful, C subsumes D
306    ///
307    /// # Examples
308    ///
309    /// ```rust
310    /// use tensorlogic_ir::{TLExpr, Term, Literal, Clause};
311    ///
312    /// // {P(x)} subsumes {P(a)}
313    /// let c = Clause::unit(Literal::positive(TLExpr::pred("P", vec![Term::var("x")])));
314    /// let d = Clause::unit(Literal::positive(TLExpr::pred("P", vec![Term::constant("a")])));
315    /// assert!(c.subsumes(&d));
316    ///
317    /// // {P(a)} does not subsume {P(x)} (x is more general than a)
318    /// assert!(!d.subsumes(&c));
319    /// ```
320    pub fn subsumes(&self, other: &Clause) -> bool {
321        // Empty clause subsumes nothing (except itself)
322        if self.is_empty() {
323            return other.is_empty();
324        }
325
326        // Can't subsume if C has more literals than D
327        if self.literals.len() > other.literals.len() {
328            return false;
329        }
330
331        // Try to find a substitution that makes all of C's literals match some literal in D
332        self.try_subsumption_matching(other).is_some()
333    }
334
335    /// Attempt to find a substitution θ such that Cθ ⊆ D.
336    ///
337    /// Uses a backtracking search to find consistent literal matchings.
338    fn try_subsumption_matching(&self, other: &Clause) -> Option<Substitution> {
339        // Rename variables in self to avoid conflicts
340        static mut SUBSUME_COUNTER: usize = 0;
341        let counter = unsafe {
342            SUBSUME_COUNTER += 1;
343            SUBSUME_COUNTER
344        };
345
346        let renamed_self = self.rename_variables(&format!("_s{}", counter));
347        let renamed_vars: HashSet<String> = renamed_self.free_vars();
348
349        // Try to match each literal in renamed_self with literals in other
350        let mut subst = Substitution::empty();
351
352        for self_lit in &renamed_self.literals {
353            // Try to find a matching literal in other
354            let mut found_match = false;
355
356            for other_lit in &other.literals {
357                // Literals must have the same polarity
358                if self_lit.polarity != other_lit.polarity {
359                    continue;
360                }
361
362                // Try one-way matching: only variables from self can be bound
363                if let Some(lit_mgu) = self_lit.try_one_way_match(&other_lit.atom, &renamed_vars) {
364                    // Check if this unifier is consistent with existing substitution
365                    if let Ok(()) = subst.try_extend(&lit_mgu) {
366                        found_match = true;
367                        break;
368                    }
369                }
370            }
371
372            if !found_match {
373                return None; // Failed to match this literal
374            }
375        }
376
377        Some(subst)
378    }
379}
380
381impl Literal {
382    /// Try one-way matching for subsumption: only variables in `allowed_vars` can be bound.
383    ///
384    /// This is different from full unification - we only allow variables from the
385    /// subsuming clause to be instantiated, not variables from the subsumed clause.
386    fn try_one_way_match(
387        &self,
388        other_atom: &TLExpr,
389        allowed_vars: &HashSet<String>,
390    ) -> Option<Substitution> {
391        match (&self.atom, other_atom) {
392            (
393                TLExpr::Pred {
394                    name: n1,
395                    args: args1,
396                },
397                TLExpr::Pred {
398                    name: n2,
399                    args: args2,
400                },
401            ) => {
402                // Predicate names must match
403                if n1 != n2 {
404                    return None;
405                }
406
407                // Arity must match
408                if args1.len() != args2.len() {
409                    return None;
410                }
411
412                // Try one-way matching for each argument pair
413                let mut subst = Substitution::empty();
414
415                for (t1, t2) in args1.iter().zip(args2.iter()) {
416                    if !try_one_way_match_terms(t1, t2, allowed_vars, &mut subst) {
417                        return None;
418                    }
419                }
420
421                Some(subst)
422            }
423            _ => None,
424        }
425    }
426}
427
428/// One-way matching for terms: only variables in `allowed_vars` can be bound.
429fn try_one_way_match_terms(
430    t1: &Term,
431    t2: &Term,
432    allowed_vars: &HashSet<String>,
433    subst: &mut Substitution,
434) -> bool {
435    // Apply current substitution to t1
436    let t1_subst = subst.apply(t1);
437
438    match (&t1_subst, t2) {
439        // Same constant
440        (Term::Const(c1), Term::Const(c2)) => c1 == c2,
441
442        // Same variable
443        (Term::Var(v1), Term::Var(v2)) => v1 == v2,
444
445        // t1 is an allowed variable, bind it to t2
446        (Term::Var(v1), _) if allowed_vars.contains(v1) => {
447            // Check if already bound by trying to apply the substitution
448            let after_subst = subst.apply(&t1_subst);
449            if after_subst != t1_subst {
450                // Already bound, check if it matches t2
451                &after_subst == t2
452            } else {
453                // Not bound, bind it now
454                subst.bind(v1.clone(), t2.clone());
455                true
456            }
457        }
458
459        // t1 is a variable but not allowed to be bound
460        (Term::Var(_), _) => false,
461
462        // t2 is a variable but we can't bind it (one-way matching)
463        (_, Term::Var(_)) => false,
464
465        // Both typed with same type
466        (
467            Term::Typed {
468                value: inner1,
469                type_annotation: ty1,
470            },
471            Term::Typed {
472                value: inner2,
473                type_annotation: ty2,
474            },
475        ) => {
476            if ty1 != ty2 {
477                return false;
478            }
479            try_one_way_match_terms(inner1, inner2, allowed_vars, subst)
480        }
481
482        // One typed, one not
483        (Term::Typed { value, .. }, other) | (other, Term::Typed { value, .. }) => {
484            try_one_way_match_terms(value, other, allowed_vars, subst)
485        }
486    }
487}
488
489impl Clause {
490    /// Check if this clause is tautology (contains complementary literals).
491    pub fn is_tautology(&self) -> bool {
492        for i in 0..self.literals.len() {
493            for j in (i + 1)..self.literals.len() {
494                if self.literals[i].is_complementary(&self.literals[j]) {
495                    return true;
496                }
497            }
498        }
499        false
500    }
501
502    /// Apply a substitution to this clause.
503    ///
504    /// This creates a new clause with the substitution applied to all literals.
505    pub fn apply_substitution(&self, subst: &Substitution) -> Clause {
506        let new_literals = self
507            .literals
508            .iter()
509            .map(|lit| lit.apply_substitution(subst))
510            .collect();
511        Clause::from_literals(new_literals)
512    }
513
514    /// Rename all variables in this clause with a suffix.
515    ///
516    /// This is used for standardizing apart clauses before resolution
517    /// to avoid variable name conflicts.
518    ///
519    /// # Example
520    ///
521    /// ```rust
522    /// use tensorlogic_ir::{TLExpr, Term, Literal, Clause};
523    ///
524    /// // P(x) ∨ Q(x)
525    /// let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
526    /// let q_x = Literal::positive(TLExpr::pred("Q", vec![Term::var("x")]));
527    /// let clause = Clause::from_literals(vec![p_x, q_x]);
528    ///
529    /// // Rename to P(x_1) ∨ Q(x_1)
530    /// let renamed = clause.rename_variables("1");
531    /// ```
532    pub fn rename_variables(&self, suffix: &str) -> Clause {
533        let renamed_literals = self
534            .literals
535            .iter()
536            .map(|lit| self.rename_literal(lit, suffix))
537            .collect();
538        Clause::from_literals(renamed_literals)
539    }
540
541    /// Rename variables in a literal (helper for rename_variables).
542    fn rename_literal(&self, lit: &Literal, suffix: &str) -> Literal {
543        match &lit.atom {
544            TLExpr::Pred { name, args } => {
545                let renamed_args = args.iter().map(|term| rename_vars(term, suffix)).collect();
546                Literal {
547                    atom: TLExpr::Pred {
548                        name: name.clone(),
549                        args: renamed_args,
550                    },
551                    polarity: lit.polarity,
552                }
553            }
554            _ => lit.clone(),
555        }
556    }
557}
558
559/// Result of a resolution proof attempt.
560#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
561pub enum ProofResult {
562    /// The clause set is unsatisfiable (empty clause derived)
563    Unsatisfiable {
564        /// Steps taken to derive empty clause
565        steps: usize,
566        /// Derivation path (sequence of resolution steps)
567        derivation: Vec<ResolutionStep>,
568    },
569    /// The clause set is satisfiable (no contradiction found)
570    Satisfiable,
571    /// Proof attempt reached saturation without finding empty clause
572    Saturated {
573        /// Number of clauses generated
574        clauses_generated: usize,
575    },
576    /// Proof search reached resource limit
577    ResourceLimitReached {
578        /// Number of steps attempted
579        steps_attempted: usize,
580    },
581}
582
583impl ProofResult {
584    /// Check if the result proves unsatisfiability.
585    pub fn is_unsatisfiable(&self) -> bool {
586        matches!(self, ProofResult::Unsatisfiable { .. })
587    }
588
589    /// Check if the result proves satisfiability.
590    pub fn is_satisfiable(&self) -> bool {
591        matches!(self, ProofResult::Satisfiable)
592    }
593
594    /// Get the number of steps taken.
595    pub fn steps(&self) -> usize {
596        match self {
597            ProofResult::Unsatisfiable { steps, .. } => *steps,
598            ProofResult::ResourceLimitReached { steps_attempted } => *steps_attempted,
599            ProofResult::Saturated { clauses_generated } => *clauses_generated,
600            ProofResult::Satisfiable => 0,
601        }
602    }
603}
604
605/// A single resolution step in a proof.
606#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
607pub struct ResolutionStep {
608    /// First parent clause
609    pub parent1: Clause,
610    /// Second parent clause
611    pub parent2: Clause,
612    /// Resulting resolvent clause
613    pub resolvent: Clause,
614    /// Literal that was resolved on (from parent1)
615    pub resolved_literal: Literal,
616}
617
618/// Resolution proof strategy.
619#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
620pub enum ResolutionStrategy {
621    /// Generate all possible resolvents until empty clause or saturation
622    Saturation {
623        /// Maximum number of clauses to generate
624        max_clauses: usize,
625    },
626    /// Focus resolution on specific set of clauses
627    SetOfSupport {
628        /// Maximum resolution steps
629        max_steps: usize,
630    },
631    /// Only resolve with unit clauses (more efficient)
632    UnitResolution {
633        /// Maximum resolution steps
634        max_steps: usize,
635    },
636    /// Linear resolution: chain from initial clause
637    Linear {
638        /// Maximum chain length
639        max_depth: usize,
640    },
641}
642
643/// Resolution-based theorem prover.
644pub struct ResolutionProver {
645    /// Initial clause set
646    clauses: Vec<Clause>,
647    /// Strategy to use
648    strategy: ResolutionStrategy,
649    /// Statistics
650    pub stats: ProverStats,
651}
652
653/// Statistics for resolution proof search.
654#[derive(Clone, Debug, Default, Serialize, Deserialize)]
655pub struct ProverStats {
656    /// Total clauses generated
657    pub clauses_generated: usize,
658    /// Resolution steps performed
659    pub resolution_steps: usize,
660    /// Tautologies removed
661    pub tautologies_removed: usize,
662    /// Clauses subsumed
663    pub clauses_subsumed: usize,
664    /// Empty clause found
665    pub empty_clause_found: bool,
666}
667
668impl ResolutionProver {
669    /// Create a new resolution prover with default strategy.
670    pub fn new() -> Self {
671        ResolutionProver {
672            clauses: Vec::new(),
673            strategy: ResolutionStrategy::Saturation { max_clauses: 10000 },
674            stats: ProverStats::default(),
675        }
676    }
677
678    /// Create a prover with a specific strategy.
679    pub fn with_strategy(strategy: ResolutionStrategy) -> Self {
680        ResolutionProver {
681            clauses: Vec::new(),
682            strategy,
683            stats: ProverStats::default(),
684        }
685    }
686
687    /// Add a clause to the initial clause set.
688    pub fn add_clause(&mut self, clause: Clause) {
689        // Don't add tautologies
690        if !clause.is_tautology() {
691            self.clauses.push(clause);
692        } else {
693            self.stats.tautologies_removed += 1;
694        }
695    }
696
697    /// Add multiple clauses.
698    pub fn add_clauses(&mut self, clauses: Vec<Clause>) {
699        for clause in clauses {
700            self.add_clause(clause);
701        }
702    }
703
704    /// Reset the prover (clear clauses and stats).
705    pub fn reset(&mut self) {
706        self.clauses.clear();
707        self.stats = ProverStats::default();
708    }
709
710    /// Perform binary resolution on two clauses.
711    ///
712    /// Returns all possible resolvents.
713    ///
714    /// This is the ground resolution (no variables). For first-order resolution
715    /// with variables, use `resolve_first_order`.
716    fn resolve(&self, c1: &Clause, c2: &Clause) -> Vec<(Clause, Literal)> {
717        let mut resolvents = Vec::new();
718
719        // Try to resolve on each pair of complementary literals
720        for lit1 in &c1.literals {
721            for lit2 in &c2.literals {
722                if lit1.is_complementary(lit2) {
723                    // Build resolvent: (c1 - lit1) ∪ (c2 - lit2)
724                    let mut new_literals = Vec::new();
725
726                    // Add literals from c1 except lit1
727                    for lit in &c1.literals {
728                        if lit != lit1 {
729                            new_literals.push(lit.clone());
730                        }
731                    }
732
733                    // Add literals from c2 except lit2
734                    for lit in &c2.literals {
735                        if lit != lit2 {
736                            new_literals.push(lit.clone());
737                        }
738                    }
739
740                    let resolvent = Clause::from_literals(new_literals);
741                    resolvents.push((resolvent, lit1.clone()));
742                }
743            }
744        }
745
746        resolvents
747    }
748
749    /// Perform first-order binary resolution with unification.
750    ///
751    /// This supports resolution on clauses with variables by using unification.
752    /// Clauses are standardized apart before resolution to avoid variable conflicts.
753    ///
754    /// Returns all possible resolvents with their MGUs.
755    ///
756    /// # Example
757    ///
758    /// ```rust
759    /// use tensorlogic_ir::{TLExpr, Term, Literal, Clause, ResolutionProver};
760    ///
761    /// // {P(x)} and {¬P(a)} resolve to {} (empty clause) with MGU {x/a}
762    /// let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
763    /// let not_p_a = Literal::negative(TLExpr::pred("P", vec![Term::constant("a")]));
764    ///
765    /// let c1 = Clause::unit(p_x);
766    /// let c2 = Clause::unit(not_p_a);
767    ///
768    /// let prover = ResolutionProver::new();
769    /// let resolvents = prover.resolve_first_order(&c1, &c2);
770    ///
771    /// assert_eq!(resolvents.len(), 1);
772    /// assert!(resolvents[0].0.is_empty()); // Empty clause derived
773    /// ```
774    pub fn resolve_first_order(&self, c1: &Clause, c2: &Clause) -> Vec<(Clause, Literal)> {
775        // Use a simple counter for standardizing apart
776        // In practice, this could use a global counter or timestamp
777        static mut RENAME_COUNTER: usize = 0;
778        let counter = unsafe {
779            RENAME_COUNTER += 1;
780            RENAME_COUNTER
781        };
782
783        // Standardize apart: rename variables to avoid conflicts
784        let c1_renamed = c1.rename_variables(&format!("_c1_{}", counter));
785        let c2_renamed = c2.rename_variables(&format!("_c2_{}", counter));
786
787        let mut resolvents = Vec::new();
788
789        // Try to unify each pair of opposite polarity literals
790        for lit1 in &c1_renamed.literals {
791            for lit2 in &c2_renamed.literals {
792                // Try to unify with first-order unification
793                if let Some(mgu) = lit1.try_unify(lit2) {
794                    // Build resolvent: apply MGU to (c1 - lit1) ∪ (c2 - lit2)
795                    let mut new_literals = Vec::new();
796
797                    // Add literals from c1 except lit1, with MGU applied
798                    for lit in &c1_renamed.literals {
799                        if lit != lit1 {
800                            new_literals.push(lit.apply_substitution(&mgu));
801                        }
802                    }
803
804                    // Add literals from c2 except lit2, with MGU applied
805                    for lit in &c2_renamed.literals {
806                        if lit != lit2 {
807                            new_literals.push(lit.apply_substitution(&mgu));
808                        }
809                    }
810
811                    let resolvent = Clause::from_literals(new_literals);
812                    // Return the original (non-renamed) literal for tracking
813                    let orig_lit = lit1.clone(); // Could map back to original if needed
814                    resolvents.push((resolvent, orig_lit));
815                }
816            }
817        }
818
819        resolvents
820    }
821
822    /// Check if a clause is subsumed by any clause in the set.
823    fn is_subsumed(&self, clause: &Clause, clause_set: &[Clause]) -> bool {
824        clause_set.iter().any(|c| c.subsumes(clause))
825    }
826
827    /// Attempt to prove the clause set unsatisfiable using resolution.
828    pub fn prove(&mut self) -> ProofResult {
829        match &self.strategy {
830            ResolutionStrategy::Saturation { max_clauses } => self.prove_saturation(*max_clauses),
831            ResolutionStrategy::SetOfSupport { max_steps } => self.prove_set_of_support(*max_steps),
832            ResolutionStrategy::UnitResolution { max_steps } => {
833                self.prove_unit_resolution(*max_steps)
834            }
835            ResolutionStrategy::Linear { max_depth } => self.prove_linear(*max_depth),
836        }
837    }
838
839    /// Saturation-based proof: generate all resolvents.
840    fn prove_saturation(&mut self, max_clauses: usize) -> ProofResult {
841        let mut clause_set: Vec<Clause> = self.clauses.clone();
842        let mut derivation = Vec::new();
843        let mut steps = 0;
844
845        // Check if empty clause is in initial set
846        if clause_set.iter().any(|c| c.is_empty()) {
847            self.stats.empty_clause_found = true;
848            return ProofResult::Unsatisfiable {
849                steps: 0,
850                derivation: vec![],
851            };
852        }
853
854        loop {
855            let current_clauses: Vec<Clause> = clause_set.clone();
856            let mut new_clauses = Vec::new();
857
858            // Generate all resolvents
859            for i in 0..current_clauses.len() {
860                for j in (i + 1)..current_clauses.len() {
861                    let resolvents = self.resolve(&current_clauses[i], &current_clauses[j]);
862
863                    for (resolvent, resolved_lit) in resolvents {
864                        steps += 1;
865                        self.stats.resolution_steps += 1;
866
867                        // Skip tautologies
868                        if resolvent.is_tautology() {
869                            self.stats.tautologies_removed += 1;
870                            continue;
871                        }
872
873                        // Check for empty clause
874                        if resolvent.is_empty() {
875                            self.stats.empty_clause_found = true;
876                            derivation.push(ResolutionStep {
877                                parent1: current_clauses[i].clone(),
878                                parent2: current_clauses[j].clone(),
879                                resolvent: resolvent.clone(),
880                                resolved_literal: resolved_lit,
881                            });
882                            return ProofResult::Unsatisfiable { steps, derivation };
883                        }
884
885                        // Skip if subsumed
886                        if self.is_subsumed(&resolvent, &current_clauses) {
887                            self.stats.clauses_subsumed += 1;
888                            continue;
889                        }
890
891                        // Add new clause if not already present
892                        if !clause_set.contains(&resolvent) && !new_clauses.contains(&resolvent) {
893                            new_clauses.push(resolvent.clone());
894                            derivation.push(ResolutionStep {
895                                parent1: current_clauses[i].clone(),
896                                parent2: current_clauses[j].clone(),
897                                resolvent,
898                                resolved_literal: resolved_lit,
899                            });
900                        }
901                    }
902                }
903            }
904
905            // Check for saturation or limit
906            if new_clauses.is_empty() {
907                return ProofResult::Saturated {
908                    clauses_generated: clause_set.len(),
909                };
910            }
911
912            // Add new clauses to set
913            for clause in new_clauses {
914                clause_set.push(clause);
915                self.stats.clauses_generated += 1;
916
917                if clause_set.len() >= max_clauses {
918                    return ProofResult::ResourceLimitReached {
919                        steps_attempted: steps,
920                    };
921                }
922            }
923        }
924    }
925
926    /// Set-of-support proof strategy.
927    fn prove_set_of_support(&mut self, max_steps: usize) -> ProofResult {
928        // Simplified: treat last clause as support set
929        if self.clauses.is_empty() {
930            return ProofResult::Satisfiable;
931        }
932
933        let support = self.clauses.pop().unwrap();
934        let mut sos: VecDeque<Clause> = VecDeque::new();
935        sos.push_back(support);
936
937        let usable: Vec<Clause> = self.clauses.clone();
938        let mut derivation = Vec::new();
939        let mut steps = 0;
940
941        while let Some(current) = sos.pop_front() {
942            if steps >= max_steps {
943                return ProofResult::ResourceLimitReached {
944                    steps_attempted: steps,
945                };
946            }
947
948            if current.is_empty() {
949                self.stats.empty_clause_found = true;
950                return ProofResult::Unsatisfiable { steps, derivation };
951            }
952
953            // Resolve with usable clauses
954            for usable_clause in &usable {
955                let resolvents = self.resolve(&current, usable_clause);
956
957                for (resolvent, resolved_lit) in resolvents {
958                    steps += 1;
959                    self.stats.resolution_steps += 1;
960
961                    if resolvent.is_tautology() {
962                        self.stats.tautologies_removed += 1;
963                        continue;
964                    }
965
966                    if resolvent.is_empty() {
967                        self.stats.empty_clause_found = true;
968                        derivation.push(ResolutionStep {
969                            parent1: current.clone(),
970                            parent2: usable_clause.clone(),
971                            resolvent: resolvent.clone(),
972                            resolved_literal: resolved_lit,
973                        });
974                        return ProofResult::Unsatisfiable { steps, derivation };
975                    }
976
977                    sos.push_back(resolvent.clone());
978                    self.stats.clauses_generated += 1;
979                    derivation.push(ResolutionStep {
980                        parent1: current.clone(),
981                        parent2: usable_clause.clone(),
982                        resolvent,
983                        resolved_literal: resolved_lit,
984                    });
985                }
986            }
987        }
988
989        ProofResult::Satisfiable
990    }
991
992    /// Unit resolution strategy (only resolve with unit clauses).
993    fn prove_unit_resolution(&mut self, max_steps: usize) -> ProofResult {
994        let mut clauses = self.clauses.clone();
995        let mut derivation = Vec::new();
996        let mut steps = 0;
997
998        loop {
999            if steps >= max_steps {
1000                return ProofResult::ResourceLimitReached {
1001                    steps_attempted: steps,
1002                };
1003            }
1004
1005            // Find unit clauses
1006            let unit_clauses: Vec<Clause> =
1007                clauses.iter().filter(|c| c.is_unit()).cloned().collect();
1008
1009            if unit_clauses.is_empty() {
1010                return ProofResult::Satisfiable;
1011            }
1012
1013            let mut new_clauses = Vec::new();
1014            let mut found_new = false;
1015
1016            // Resolve each unit clause with all clauses
1017            for unit in &unit_clauses {
1018                for clause in &clauses {
1019                    if clause.is_unit() && clause == unit {
1020                        continue; // Skip self-resolution
1021                    }
1022
1023                    let resolvents = self.resolve(unit, clause);
1024
1025                    for (resolvent, resolved_lit) in resolvents {
1026                        steps += 1;
1027                        self.stats.resolution_steps += 1;
1028
1029                        if resolvent.is_tautology() {
1030                            self.stats.tautologies_removed += 1;
1031                            continue;
1032                        }
1033
1034                        if resolvent.is_empty() {
1035                            self.stats.empty_clause_found = true;
1036                            derivation.push(ResolutionStep {
1037                                parent1: unit.clone(),
1038                                parent2: clause.clone(),
1039                                resolvent: resolvent.clone(),
1040                                resolved_literal: resolved_lit,
1041                            });
1042                            return ProofResult::Unsatisfiable { steps, derivation };
1043                        }
1044
1045                        if !clauses.contains(&resolvent) && !new_clauses.contains(&resolvent) {
1046                            new_clauses.push(resolvent.clone());
1047                            found_new = true;
1048                            self.stats.clauses_generated += 1;
1049                            derivation.push(ResolutionStep {
1050                                parent1: unit.clone(),
1051                                parent2: clause.clone(),
1052                                resolvent,
1053                                resolved_literal: resolved_lit,
1054                            });
1055                        }
1056                    }
1057                }
1058            }
1059
1060            if !found_new {
1061                return ProofResult::Satisfiable;
1062            }
1063
1064            clauses.extend(new_clauses);
1065        }
1066    }
1067
1068    /// Linear resolution strategy.
1069    fn prove_linear(&mut self, max_depth: usize) -> ProofResult {
1070        // Simplified linear resolution from first clause
1071        if self.clauses.is_empty() {
1072            return ProofResult::Satisfiable;
1073        }
1074
1075        let start = self.clauses[0].clone();
1076        let mut current = start.clone();
1077        let mut depth = 0;
1078        let mut derivation = Vec::new();
1079
1080        while depth < max_depth {
1081            if current.is_empty() {
1082                self.stats.empty_clause_found = true;
1083                return ProofResult::Unsatisfiable {
1084                    steps: depth,
1085                    derivation,
1086                };
1087            }
1088
1089            // Try to resolve with any other clause
1090            let mut resolved = false;
1091            for other in &self.clauses[1..] {
1092                let resolvents = self.resolve(&current, other);
1093
1094                if let Some((resolvent, resolved_lit)) = resolvents.first() {
1095                    if !resolvent.is_tautology() {
1096                        current = resolvent.clone();
1097                        depth += 1;
1098                        self.stats.resolution_steps += 1;
1099                        self.stats.clauses_generated += 1;
1100                        derivation.push(ResolutionStep {
1101                            parent1: current.clone(),
1102                            parent2: other.clone(),
1103                            resolvent: resolvent.clone(),
1104                            resolved_literal: resolved_lit.clone(),
1105                        });
1106                        resolved = true;
1107                        break;
1108                    }
1109                }
1110            }
1111
1112            if !resolved {
1113                return ProofResult::Satisfiable;
1114            }
1115        }
1116
1117        ProofResult::ResourceLimitReached {
1118            steps_attempted: depth,
1119        }
1120    }
1121}
1122
1123impl Default for ResolutionProver {
1124    fn default() -> Self {
1125        Self::new()
1126    }
1127}
1128
1129/// Convert a TLExpr to Clausal Normal Form (CNF).
1130///
1131/// This is a simplified conversion that handles basic logical operators.
1132/// Full CNF conversion would require skolemization and quantifier elimination.
1133pub fn to_cnf(expr: &TLExpr) -> Result<Vec<Clause>, IrError> {
1134    // Simplified CNF conversion
1135    // For full implementation, would need:
1136    // 1. Eliminate implications
1137    // 2. Move negations inward (De Morgan's laws)
1138    // 3. Distribute OR over AND
1139    // 4. Skolemize existential quantifiers
1140    // 5. Drop universal quantifiers
1141
1142    match expr {
1143        TLExpr::And(left, right) => {
1144            let mut clauses = to_cnf(left)?;
1145            clauses.extend(to_cnf(right)?);
1146            Ok(clauses)
1147        }
1148        TLExpr::Or(left, right) => {
1149            // Distribute OR over AND if needed
1150            let left_clauses = to_cnf(left)?;
1151            let right_clauses = to_cnf(right)?;
1152
1153            if left_clauses.len() == 1 && right_clauses.len() == 1 {
1154                // Simple case: combine literals
1155                let mut literals = left_clauses[0].literals.clone();
1156                literals.extend(right_clauses[0].literals.clone());
1157                Ok(vec![Clause::from_literals(literals)])
1158            } else {
1159                // Complex case: would need distribution
1160                // For now, treat as separate clauses (approximation)
1161                let mut result = left_clauses;
1162                result.extend(right_clauses);
1163                Ok(result)
1164            }
1165        }
1166        TLExpr::Not(inner) => {
1167            match &**inner {
1168                TLExpr::Pred { .. } => {
1169                    // Negative literal
1170                    Ok(vec![Clause::unit(Literal::negative((**inner).clone()))])
1171                }
1172                _ => {
1173                    // Would need to push negation inward
1174                    Err(IrError::ConstraintViolation {
1175                        message: "Complex negation not supported in simplified CNF conversion"
1176                            .to_string(),
1177                    })
1178                }
1179            }
1180        }
1181        TLExpr::Pred { .. } => {
1182            // Positive literal
1183            Ok(vec![Clause::unit(Literal::positive(expr.clone()))])
1184        }
1185        _ => Err(IrError::ConstraintViolation {
1186            message: format!(
1187                "Expression type not supported in CNF conversion: {:?}",
1188                expr
1189            ),
1190        }),
1191    }
1192}
1193
1194#[cfg(test)]
1195mod tests {
1196    use super::*;
1197
1198    fn p() -> TLExpr {
1199        TLExpr::pred("P", vec![])
1200    }
1201
1202    fn q() -> TLExpr {
1203        TLExpr::pred("Q", vec![])
1204    }
1205
1206    fn r() -> TLExpr {
1207        TLExpr::pred("R", vec![])
1208    }
1209
1210    #[test]
1211    fn test_literal_creation() {
1212        let lit_pos = Literal::positive(p());
1213        assert!(lit_pos.is_positive());
1214        assert!(!lit_pos.is_negative());
1215
1216        let lit_neg = Literal::negative(p());
1217        assert!(!lit_neg.is_positive());
1218        assert!(lit_neg.is_negative());
1219    }
1220
1221    #[test]
1222    fn test_literal_complementary() {
1223        let lit_pos = Literal::positive(p());
1224        let lit_neg = Literal::negative(p());
1225
1226        assert!(lit_pos.is_complementary(&lit_neg));
1227        assert!(lit_neg.is_complementary(&lit_pos));
1228        assert!(!lit_pos.is_complementary(&lit_pos));
1229    }
1230
1231    #[test]
1232    fn test_clause_empty() {
1233        let clause = Clause::empty();
1234        assert!(clause.is_empty());
1235        assert_eq!(clause.len(), 0);
1236    }
1237
1238    #[test]
1239    fn test_clause_unit() {
1240        let clause = Clause::unit(Literal::positive(p()));
1241        assert!(clause.is_unit());
1242        assert_eq!(clause.len(), 1);
1243    }
1244
1245    #[test]
1246    fn test_clause_tautology() {
1247        // P ∨ ¬P is a tautology
1248        let clause = Clause::from_literals(vec![Literal::positive(p()), Literal::negative(p())]);
1249        assert!(clause.is_tautology());
1250    }
1251
1252    #[test]
1253    fn test_resolution_basic() {
1254        // {P}, {¬P} ⊢ ∅
1255        let mut prover = ResolutionProver::new();
1256        prover.add_clause(Clause::unit(Literal::positive(p())));
1257        prover.add_clause(Clause::unit(Literal::negative(p())));
1258
1259        let result = prover.prove();
1260        assert!(result.is_unsatisfiable());
1261    }
1262
1263    #[test]
1264    fn test_resolution_modus_ponens() {
1265        // {P}, {P → Q} ≡ {P}, {¬P ∨ Q} ⊢ Q
1266        // Clauses: {P}, {¬P, Q}
1267        // Resolution: {Q}
1268        let mut prover = ResolutionProver::new();
1269        prover.add_clause(Clause::unit(Literal::positive(p())));
1270        prover.add_clause(Clause::from_literals(vec![
1271            Literal::negative(p()),
1272            Literal::positive(q()),
1273        ]));
1274        // To prove Q, add ¬Q and check for contradiction
1275        prover.add_clause(Clause::unit(Literal::negative(q())));
1276
1277        let result = prover.prove();
1278        assert!(result.is_unsatisfiable());
1279    }
1280
1281    #[test]
1282    fn test_resolution_satisfiable() {
1283        // {P}, {Q} is satisfiable (no complementary literals)
1284        let mut prover = ResolutionProver::new();
1285        prover.add_clause(Clause::unit(Literal::positive(p())));
1286        prover.add_clause(Clause::unit(Literal::positive(q())));
1287
1288        let result = prover.prove();
1289        // Should saturate or be satisfiable
1290        assert!(!result.is_unsatisfiable());
1291    }
1292
1293    #[test]
1294    fn test_cnf_conversion_and() {
1295        // P ∧ Q → clauses: {P}, {Q}
1296        let expr = TLExpr::and(p(), q());
1297        let clauses = to_cnf(&expr).unwrap();
1298
1299        assert_eq!(clauses.len(), 2);
1300        assert!(clauses.iter().all(|c| c.is_unit()));
1301    }
1302
1303    #[test]
1304    fn test_cnf_conversion_or() {
1305        // P ∨ Q → clause: {P, Q}
1306        let expr = TLExpr::or(p(), q());
1307        let clauses = to_cnf(&expr).unwrap();
1308
1309        assert_eq!(clauses.len(), 1);
1310        assert_eq!(clauses[0].len(), 2);
1311    }
1312
1313    #[test]
1314    fn test_resolution_strategy_unit() {
1315        // Test unit resolution strategy
1316        let mut prover =
1317            ResolutionProver::with_strategy(ResolutionStrategy::UnitResolution { max_steps: 100 });
1318
1319        prover.add_clause(Clause::unit(Literal::positive(p())));
1320        prover.add_clause(Clause::unit(Literal::negative(p())));
1321
1322        let result = prover.prove();
1323        assert!(result.is_unsatisfiable());
1324    }
1325
1326    #[test]
1327    fn test_resolution_three_clauses() {
1328        // {P ∨ Q}, {¬P ∨ R}, {¬Q}, {¬R} ⊢ ∅
1329        let mut prover = ResolutionProver::new();
1330
1331        prover.add_clause(Clause::from_literals(vec![
1332            Literal::positive(p()),
1333            Literal::positive(q()),
1334        ]));
1335        prover.add_clause(Clause::from_literals(vec![
1336            Literal::negative(p()),
1337            Literal::positive(r()),
1338        ]));
1339        prover.add_clause(Clause::unit(Literal::negative(q())));
1340        prover.add_clause(Clause::unit(Literal::negative(r())));
1341
1342        let result = prover.prove();
1343        assert!(result.is_unsatisfiable());
1344    }
1345
1346    #[test]
1347    fn test_horn_clause_detection() {
1348        // {¬P, ¬Q, R} is a Horn clause (exactly one positive)
1349        let clause = Clause::from_literals(vec![
1350            Literal::negative(p()),
1351            Literal::negative(q()),
1352            Literal::positive(r()),
1353        ]);
1354        assert!(clause.is_horn());
1355
1356        // {P, Q} is not a Horn clause (two positives)
1357        let non_horn = Clause::from_literals(vec![Literal::positive(p()), Literal::positive(q())]);
1358        assert!(!non_horn.is_horn());
1359    }
1360
1361    #[test]
1362    fn test_prover_stats() {
1363        let mut prover = ResolutionProver::new();
1364        prover.add_clause(Clause::unit(Literal::positive(p())));
1365        prover.add_clause(Clause::unit(Literal::negative(p())));
1366
1367        let result = prover.prove();
1368
1369        assert!(prover.stats.empty_clause_found);
1370        assert!(prover.stats.resolution_steps > 0);
1371        assert!(result.is_unsatisfiable());
1372    }
1373
1374    // === First-Order Resolution Tests ===
1375
1376    #[test]
1377    fn test_literal_unification_ground() {
1378        // P(a) and ¬P(a) should unify with empty substitution
1379        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1380        let not_p_a = Literal::negative(TLExpr::pred("P", vec![Term::constant("a")]));
1381
1382        let mgu = p_a.try_unify(&not_p_a);
1383        assert!(mgu.is_some());
1384        assert!(mgu.unwrap().is_empty());
1385    }
1386
1387    #[test]
1388    fn test_literal_unification_variable() {
1389        // P(x) and ¬P(a) should unify with {x/a}
1390        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1391        let not_p_a = Literal::negative(TLExpr::pred("P", vec![Term::constant("a")]));
1392
1393        let mgu = p_x.try_unify(&not_p_a);
1394        assert!(mgu.is_some());
1395
1396        let mgu = mgu.unwrap();
1397        assert_eq!(mgu.len(), 1);
1398        assert_eq!(mgu.apply(&Term::var("x")), Term::constant("a"));
1399    }
1400
1401    #[test]
1402    fn test_literal_unification_fails_diff_names() {
1403        // P(x) and ¬Q(x) should not unify (different predicate names)
1404        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1405        let not_q_x = Literal::negative(TLExpr::pred("Q", vec![Term::var("x")]));
1406
1407        let mgu = p_x.try_unify(&not_q_x);
1408        assert!(mgu.is_none());
1409    }
1410
1411    #[test]
1412    fn test_literal_unification_fails_same_polarity() {
1413        // P(x) and P(a) should not unify (same polarity)
1414        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1415        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1416
1417        let mgu = p_x.try_unify(&p_a);
1418        assert!(mgu.is_none());
1419    }
1420
1421    #[test]
1422    fn test_literal_apply_substitution() {
1423        // P(x) with {x/a} should become P(a)
1424        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1425        let mut subst = Substitution::empty();
1426        subst.bind("x".to_string(), Term::constant("a"));
1427
1428        let p_a = p_x.apply_substitution(&subst);
1429        let expected = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1430
1431        assert_eq!(p_a.atom, expected.atom);
1432        assert_eq!(p_a.polarity, expected.polarity);
1433    }
1434
1435    #[test]
1436    fn test_clause_rename_variables() {
1437        // P(x) ∨ Q(x) renamed with "1" should become P(x_1) ∨ Q(x_1)
1438        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1439        let q_x = Literal::positive(TLExpr::pred("Q", vec![Term::var("x")]));
1440        let clause = Clause::from_literals(vec![p_x, q_x]);
1441
1442        let renamed = clause.rename_variables("1");
1443
1444        // Check that variables were renamed
1445        let vars = renamed.free_vars();
1446        assert!(vars.contains("x_1"));
1447        assert!(!vars.contains("x"));
1448    }
1449
1450    #[test]
1451    fn test_clause_apply_substitution() {
1452        // {P(x), Q(y)} with {x/a, y/b} should become {P(a), Q(b)}
1453        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1454        let q_y = Literal::positive(TLExpr::pred("Q", vec![Term::var("y")]));
1455        let clause = Clause::from_literals(vec![p_x, q_y]);
1456
1457        let mut subst = Substitution::empty();
1458        subst.bind("x".to_string(), Term::constant("a"));
1459        subst.bind("y".to_string(), Term::constant("b"));
1460
1461        let result = clause.apply_substitution(&subst);
1462
1463        // Should have no free variables (all substituted)
1464        assert!(result.free_vars().is_empty());
1465    }
1466
1467    #[test]
1468    fn test_first_order_resolution_basic() {
1469        // {P(x)} and {¬P(a)} should resolve to {} with {x/a}
1470        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1471        let not_p_a = Literal::negative(TLExpr::pred("P", vec![Term::constant("a")]));
1472
1473        let c1 = Clause::unit(p_x);
1474        let c2 = Clause::unit(not_p_a);
1475
1476        let prover = ResolutionProver::new();
1477        let resolvents = prover.resolve_first_order(&c1, &c2);
1478
1479        assert_eq!(resolvents.len(), 1);
1480        assert!(resolvents[0].0.is_empty()); // Empty clause derived
1481    }
1482
1483    #[test]
1484    fn test_first_order_resolution_complex() {
1485        // {P(x), Q(x)} and {¬P(a), R(a)} should resolve to {Q(a), R(a)}
1486        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1487        let q_x = Literal::positive(TLExpr::pred("Q", vec![Term::var("x")]));
1488        let c1 = Clause::from_literals(vec![p_x, q_x]);
1489
1490        let not_p_a = Literal::negative(TLExpr::pred("P", vec![Term::constant("a")]));
1491        let r_a = Literal::positive(TLExpr::pred("R", vec![Term::constant("a")]));
1492        let c2 = Clause::from_literals(vec![not_p_a, r_a]);
1493
1494        let prover = ResolutionProver::new();
1495        let resolvents = prover.resolve_first_order(&c1, &c2);
1496
1497        assert_eq!(resolvents.len(), 1);
1498        let resolvent = &resolvents[0].0;
1499
1500        // Should have 2 literals: Q(a) and R(a)
1501        assert_eq!(resolvent.len(), 2);
1502
1503        // Should have no free variables (all unified)
1504        assert!(resolvent.free_vars().is_empty());
1505    }
1506
1507    #[test]
1508    fn test_first_order_resolution_multiple_vars() {
1509        // {P(x, y)} and {¬P(a, b)} should resolve to {} with {x/a, y/b}
1510        let p_xy = Literal::positive(TLExpr::pred("P", vec![Term::var("x"), Term::var("y")]));
1511        let not_p_ab = Literal::negative(TLExpr::pred(
1512            "P",
1513            vec![Term::constant("a"), Term::constant("b")],
1514        ));
1515
1516        let c1 = Clause::unit(p_xy);
1517        let c2 = Clause::unit(not_p_ab);
1518
1519        let prover = ResolutionProver::new();
1520        let resolvents = prover.resolve_first_order(&c1, &c2);
1521
1522        assert_eq!(resolvents.len(), 1);
1523        assert!(resolvents[0].0.is_empty());
1524    }
1525
1526    #[test]
1527    fn test_first_order_resolution_standardizing_apart() {
1528        // {P(x)} and {¬P(x)} should be standardized apart before resolution
1529        // After standardization: {P(x_c1_N)} and {¬P(x_c2_N)}
1530        // These should resolve to {} with {x_c1_N/x_c2_N} or similar
1531        let p_x1 = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1532        let not_p_x2 = Literal::negative(TLExpr::pred("P", vec![Term::var("x")]));
1533
1534        let c1 = Clause::unit(p_x1);
1535        let c2 = Clause::unit(not_p_x2);
1536
1537        let prover = ResolutionProver::new();
1538        let resolvents = prover.resolve_first_order(&c1, &c2);
1539
1540        // Should successfully resolve despite both using variable "x"
1541        assert_eq!(resolvents.len(), 1);
1542        assert!(resolvents[0].0.is_empty());
1543    }
1544
1545    #[test]
1546    fn test_first_order_resolution_no_unifier() {
1547        // {P(a)} and {¬P(b)} should not resolve (no unifier for a and b)
1548        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1549        let not_p_b = Literal::negative(TLExpr::pred("P", vec![Term::constant("b")]));
1550
1551        let c1 = Clause::unit(p_a);
1552        let c2 = Clause::unit(not_p_b);
1553
1554        let prover = ResolutionProver::new();
1555        let resolvents = prover.resolve_first_order(&c1, &c2);
1556
1557        // Should find no resolvents
1558        assert_eq!(resolvents.len(), 0);
1559    }
1560
1561    // === Theta-Subsumption Tests ===
1562
1563    #[test]
1564    fn test_subsumption_identical() {
1565        // {P(a)} subsumes {P(a)} (same clause)
1566        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1567        let c = Clause::unit(p_a);
1568
1569        assert!(c.subsumes(&c));
1570    }
1571
1572    #[test]
1573    fn test_subsumption_variable_to_constant() {
1574        // {P(x)} subsumes {P(a)} with θ = {x/a}
1575        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1576        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1577
1578        let c_general = Clause::unit(p_x);
1579        let c_specific = Clause::unit(p_a);
1580
1581        assert!(c_general.subsumes(&c_specific));
1582    }
1583
1584    #[test]
1585    fn test_subsumption_not_reverse() {
1586        // {P(a)} does NOT subsume {P(x)} (constant is less general than variable)
1587        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1588        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1589
1590        let c_general = Clause::unit(p_x);
1591        let c_specific = Clause::unit(p_a);
1592
1593        assert!(!c_specific.subsumes(&c_general));
1594    }
1595
1596    #[test]
1597    fn test_subsumption_smaller_clause() {
1598        // {P(x)} subsumes {P(a), Q(a)} with θ = {x/a}
1599        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1600        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1601        let q_a = Literal::positive(TLExpr::pred("Q", vec![Term::constant("a")]));
1602
1603        let c1 = Clause::unit(p_x);
1604        let c2 = Clause::from_literals(vec![p_a, q_a]);
1605
1606        assert!(c1.subsumes(&c2));
1607    }
1608
1609    #[test]
1610    fn test_subsumption_multiple_literals() {
1611        // {P(x), Q(x)} subsumes {P(a), Q(a), R(a)} with θ = {x/a}
1612        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1613        let q_x = Literal::positive(TLExpr::pred("Q", vec![Term::var("x")]));
1614        let c1 = Clause::from_literals(vec![p_x, q_x]);
1615
1616        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1617        let q_a = Literal::positive(TLExpr::pred("Q", vec![Term::constant("a")]));
1618        let r_a = Literal::positive(TLExpr::pred("R", vec![Term::constant("a")]));
1619        let c2 = Clause::from_literals(vec![p_a, q_a, r_a]);
1620
1621        assert!(c1.subsumes(&c2));
1622    }
1623
1624    #[test]
1625    fn test_subsumption_fails_different_pred() {
1626        // {P(x)} does not subsume {Q(a)} (different predicate names)
1627        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1628        let q_a = Literal::positive(TLExpr::pred("Q", vec![Term::constant("a")]));
1629
1630        let c1 = Clause::unit(p_x);
1631        let c2 = Clause::unit(q_a);
1632
1633        assert!(!c1.subsumes(&c2));
1634    }
1635
1636    #[test]
1637    fn test_subsumption_fails_too_many_literals() {
1638        // {P(x), Q(x), R(x)} does not subsume {P(a), Q(a)} (c1 has more literals)
1639        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1640        let q_x = Literal::positive(TLExpr::pred("Q", vec![Term::var("x")]));
1641        let r_x = Literal::positive(TLExpr::pred("R", vec![Term::var("x")]));
1642        let c1 = Clause::from_literals(vec![p_x, q_x, r_x]);
1643
1644        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1645        let q_a = Literal::positive(TLExpr::pred("Q", vec![Term::constant("a")]));
1646        let c2 = Clause::from_literals(vec![p_a, q_a]);
1647
1648        assert!(!c1.subsumes(&c2));
1649    }
1650
1651    #[test]
1652    fn test_subsumption_empty_clause() {
1653        // Empty clause only subsumes itself
1654        let empty = Clause::empty();
1655        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1656        let non_empty = Clause::unit(p_a);
1657
1658        assert!(empty.subsumes(&empty));
1659        assert!(!empty.subsumes(&non_empty));
1660        assert!(!non_empty.subsumes(&empty));
1661    }
1662
1663    #[test]
1664    fn test_subsumption_polarity_matters() {
1665        // {P(x)} does not subsume {¬P(a)} (different polarity)
1666        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1667        let not_p_a = Literal::negative(TLExpr::pred("P", vec![Term::constant("a")]));
1668
1669        let c1 = Clause::unit(p_x);
1670        let c2 = Clause::unit(not_p_a);
1671
1672        assert!(!c1.subsumes(&c2));
1673    }
1674
1675    #[test]
1676    fn test_subsumption_two_variables() {
1677        // {P(x, y)} subsumes {P(a, b)} with θ = {x/a, y/b}
1678        let p_xy = Literal::positive(TLExpr::pred("P", vec![Term::var("x"), Term::var("y")]));
1679        let p_ab = Literal::positive(TLExpr::pred(
1680            "P",
1681            vec![Term::constant("a"), Term::constant("b")],
1682        ));
1683
1684        let c1 = Clause::unit(p_xy);
1685        let c2 = Clause::unit(p_ab);
1686
1687        assert!(c1.subsumes(&c2));
1688    }
1689
1690    #[test]
1691    fn test_subsumption_in_prover() {
1692        // Test that subsumption is actually used in the prover to reduce search space
1693        let mut prover = ResolutionProver::new();
1694
1695        // Add {P(x), Q(x)} - more general
1696        let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
1697        let q_x = Literal::positive(TLExpr::pred("Q", vec![Term::var("x")]));
1698        prover.add_clause(Clause::from_literals(vec![p_x, q_x]));
1699
1700        // This clause would be subsumed by the first
1701        let p_a = Literal::positive(TLExpr::pred("P", vec![Term::constant("a")]));
1702        let q_a = Literal::positive(TLExpr::pred("Q", vec![Term::constant("a")]));
1703        let r_a = Literal::positive(TLExpr::pred("R", vec![Term::constant("a")]));
1704        let subsumed_clause = Clause::from_literals(vec![p_a, q_a, r_a]);
1705
1706        // Check if subsumption works
1707        assert!(prover.clauses[0].subsumes(&subsumed_clause));
1708    }
1709}