Skip to main content

tensorlogic_ir/resolution/
prover.rs

1//! # Resolution Prover
2//!
3//! The [`ResolutionProver`] drives refutation-based theorem proving over a
4//! set of clauses. It supports multiple strategies: saturation, set-of-support,
5//! unit resolution, and linear resolution. First-order binary resolution with
6//! unification and standardizing-apart is also provided.
7
8use std::collections::VecDeque;
9
10use super::clause::Clause;
11use super::literal::Literal;
12use super::proof::{ProofResult, ProverStats, ResolutionStep, ResolutionStrategy};
13
14/// Resolution-based theorem prover.
15pub struct ResolutionProver {
16    /// Initial clause set
17    pub(super) clauses: Vec<Clause>,
18    /// Strategy to use
19    strategy: ResolutionStrategy,
20    /// Statistics
21    pub stats: ProverStats,
22}
23
24impl ResolutionProver {
25    /// Create a new resolution prover with default strategy.
26    pub fn new() -> Self {
27        ResolutionProver {
28            clauses: Vec::new(),
29            strategy: ResolutionStrategy::Saturation { max_clauses: 10000 },
30            stats: ProverStats::default(),
31        }
32    }
33
34    /// Create a prover with a specific strategy.
35    pub fn with_strategy(strategy: ResolutionStrategy) -> Self {
36        ResolutionProver {
37            clauses: Vec::new(),
38            strategy,
39            stats: ProverStats::default(),
40        }
41    }
42
43    /// Add a clause to the initial clause set.
44    pub fn add_clause(&mut self, clause: Clause) {
45        // Don't add tautologies
46        if !clause.is_tautology() {
47            self.clauses.push(clause);
48        } else {
49            self.stats.tautologies_removed += 1;
50        }
51    }
52
53    /// Add multiple clauses.
54    pub fn add_clauses(&mut self, clauses: Vec<Clause>) {
55        for clause in clauses {
56            self.add_clause(clause);
57        }
58    }
59
60    /// Reset the prover (clear clauses and stats).
61    pub fn reset(&mut self) {
62        self.clauses.clear();
63        self.stats = ProverStats::default();
64    }
65
66    /// Perform binary resolution on two clauses.
67    ///
68    /// Returns all possible resolvents.
69    ///
70    /// This is the ground resolution (no variables). For first-order resolution
71    /// with variables, use `resolve_first_order`.
72    fn resolve(&self, c1: &Clause, c2: &Clause) -> Vec<(Clause, Literal)> {
73        let mut resolvents = Vec::new();
74
75        // Try to resolve on each pair of complementary literals
76        for lit1 in &c1.literals {
77            for lit2 in &c2.literals {
78                if lit1.is_complementary(lit2) {
79                    // Build resolvent: (c1 - lit1) ∪ (c2 - lit2)
80                    let mut new_literals = Vec::new();
81
82                    // Add literals from c1 except lit1
83                    for lit in &c1.literals {
84                        if lit != lit1 {
85                            new_literals.push(lit.clone());
86                        }
87                    }
88
89                    // Add literals from c2 except lit2
90                    for lit in &c2.literals {
91                        if lit != lit2 {
92                            new_literals.push(lit.clone());
93                        }
94                    }
95
96                    let resolvent = Clause::from_literals(new_literals);
97                    resolvents.push((resolvent, lit1.clone()));
98                }
99            }
100        }
101
102        resolvents
103    }
104
105    /// Perform first-order binary resolution with unification.
106    ///
107    /// This supports resolution on clauses with variables by using unification.
108    /// Clauses are standardized apart before resolution to avoid variable conflicts.
109    ///
110    /// Returns all possible resolvents with their MGUs.
111    ///
112    /// # Example
113    ///
114    /// ```rust
115    /// use tensorlogic_ir::{TLExpr, Term, Literal, Clause, ResolutionProver};
116    ///
117    /// // {P(x)} and {¬P(a)} resolve to {} (empty clause) with MGU {x/a}
118    /// let p_x = Literal::positive(TLExpr::pred("P", vec![Term::var("x")]));
119    /// let not_p_a = Literal::negative(TLExpr::pred("P", vec![Term::constant("a")]));
120    ///
121    /// let c1 = Clause::unit(p_x);
122    /// let c2 = Clause::unit(not_p_a);
123    ///
124    /// let prover = ResolutionProver::new();
125    /// let resolvents = prover.resolve_first_order(&c1, &c2);
126    ///
127    /// assert_eq!(resolvents.len(), 1);
128    /// assert!(resolvents[0].0.is_empty()); // Empty clause derived
129    /// ```
130    pub fn resolve_first_order(&self, c1: &Clause, c2: &Clause) -> Vec<(Clause, Literal)> {
131        // Use a simple counter for standardizing apart
132        // In practice, this could use a global counter or timestamp
133        static mut RENAME_COUNTER: usize = 0;
134        let counter = unsafe {
135            RENAME_COUNTER += 1;
136            RENAME_COUNTER
137        };
138
139        // Standardize apart: rename variables to avoid conflicts
140        let c1_renamed = c1.rename_variables(&format!("_c1_{}", counter));
141        let c2_renamed = c2.rename_variables(&format!("_c2_{}", counter));
142
143        let mut resolvents = Vec::new();
144
145        // Try to unify each pair of opposite polarity literals
146        for lit1 in &c1_renamed.literals {
147            for lit2 in &c2_renamed.literals {
148                // Try to unify with first-order unification
149                if let Some(mgu) = lit1.try_unify(lit2) {
150                    // Build resolvent: apply MGU to (c1 - lit1) ∪ (c2 - lit2)
151                    let mut new_literals = Vec::new();
152
153                    // Add literals from c1 except lit1, with MGU applied
154                    for lit in &c1_renamed.literals {
155                        if lit != lit1 {
156                            new_literals.push(lit.apply_substitution(&mgu));
157                        }
158                    }
159
160                    // Add literals from c2 except lit2, with MGU applied
161                    for lit in &c2_renamed.literals {
162                        if lit != lit2 {
163                            new_literals.push(lit.apply_substitution(&mgu));
164                        }
165                    }
166
167                    let resolvent = Clause::from_literals(new_literals);
168                    // Return the original (non-renamed) literal for tracking
169                    let orig_lit = lit1.clone(); // Could map back to original if needed
170                    resolvents.push((resolvent, orig_lit));
171                }
172            }
173        }
174
175        resolvents
176    }
177
178    /// Check if a clause is subsumed by any clause in the set.
179    fn is_subsumed(&self, clause: &Clause, clause_set: &[Clause]) -> bool {
180        clause_set.iter().any(|c| c.subsumes(clause))
181    }
182
183    /// Attempt to prove the clause set unsatisfiable using resolution.
184    pub fn prove(&mut self) -> ProofResult {
185        match &self.strategy {
186            ResolutionStrategy::Saturation { max_clauses } => self.prove_saturation(*max_clauses),
187            ResolutionStrategy::SetOfSupport { max_steps } => self.prove_set_of_support(*max_steps),
188            ResolutionStrategy::UnitResolution { max_steps } => {
189                self.prove_unit_resolution(*max_steps)
190            }
191            ResolutionStrategy::Linear { max_depth } => self.prove_linear(*max_depth),
192        }
193    }
194
195    /// Saturation-based proof: generate all resolvents.
196    fn prove_saturation(&mut self, max_clauses: usize) -> ProofResult {
197        let mut clause_set: Vec<Clause> = self.clauses.clone();
198        let mut derivation = Vec::new();
199        let mut steps = 0;
200
201        // Check if empty clause is in initial set
202        if clause_set.iter().any(|c| c.is_empty()) {
203            self.stats.empty_clause_found = true;
204            return ProofResult::Unsatisfiable {
205                steps: 0,
206                derivation: vec![],
207            };
208        }
209
210        loop {
211            let current_clauses: Vec<Clause> = clause_set.clone();
212            let mut new_clauses = Vec::new();
213
214            // Generate all resolvents
215            for i in 0..current_clauses.len() {
216                for j in (i + 1)..current_clauses.len() {
217                    let resolvents = self.resolve(&current_clauses[i], &current_clauses[j]);
218
219                    for (resolvent, resolved_lit) in resolvents {
220                        steps += 1;
221                        self.stats.resolution_steps += 1;
222
223                        // Skip tautologies
224                        if resolvent.is_tautology() {
225                            self.stats.tautologies_removed += 1;
226                            continue;
227                        }
228
229                        // Check for empty clause
230                        if resolvent.is_empty() {
231                            self.stats.empty_clause_found = true;
232                            derivation.push(ResolutionStep {
233                                parent1: current_clauses[i].clone(),
234                                parent2: current_clauses[j].clone(),
235                                resolvent: resolvent.clone(),
236                                resolved_literal: resolved_lit,
237                            });
238                            return ProofResult::Unsatisfiable { steps, derivation };
239                        }
240
241                        // Skip if subsumed
242                        if self.is_subsumed(&resolvent, &current_clauses) {
243                            self.stats.clauses_subsumed += 1;
244                            continue;
245                        }
246
247                        // Add new clause if not already present
248                        if !clause_set.contains(&resolvent) && !new_clauses.contains(&resolvent) {
249                            new_clauses.push(resolvent.clone());
250                            derivation.push(ResolutionStep {
251                                parent1: current_clauses[i].clone(),
252                                parent2: current_clauses[j].clone(),
253                                resolvent,
254                                resolved_literal: resolved_lit,
255                            });
256                        }
257                    }
258                }
259            }
260
261            // Check for saturation or limit
262            if new_clauses.is_empty() {
263                return ProofResult::Saturated {
264                    clauses_generated: clause_set.len(),
265                };
266            }
267
268            // Add new clauses to set
269            for clause in new_clauses {
270                clause_set.push(clause);
271                self.stats.clauses_generated += 1;
272
273                if clause_set.len() >= max_clauses {
274                    return ProofResult::ResourceLimitReached {
275                        steps_attempted: steps,
276                    };
277                }
278            }
279        }
280    }
281
282    /// Set-of-support proof strategy.
283    fn prove_set_of_support(&mut self, max_steps: usize) -> ProofResult {
284        // Simplified: treat last clause as support set
285        if self.clauses.is_empty() {
286            return ProofResult::Satisfiable;
287        }
288
289        let support = self
290            .clauses
291            .pop()
292            .expect("clauses must be non-empty before pop");
293        let mut sos: VecDeque<Clause> = VecDeque::new();
294        sos.push_back(support);
295
296        let usable: Vec<Clause> = self.clauses.clone();
297        let mut derivation = Vec::new();
298        let mut steps = 0;
299
300        while let Some(current) = sos.pop_front() {
301            if steps >= max_steps {
302                return ProofResult::ResourceLimitReached {
303                    steps_attempted: steps,
304                };
305            }
306
307            if current.is_empty() {
308                self.stats.empty_clause_found = true;
309                return ProofResult::Unsatisfiable { steps, derivation };
310            }
311
312            // Resolve with usable clauses
313            for usable_clause in &usable {
314                let resolvents = self.resolve(&current, usable_clause);
315
316                for (resolvent, resolved_lit) in resolvents {
317                    steps += 1;
318                    self.stats.resolution_steps += 1;
319
320                    if resolvent.is_tautology() {
321                        self.stats.tautologies_removed += 1;
322                        continue;
323                    }
324
325                    if resolvent.is_empty() {
326                        self.stats.empty_clause_found = true;
327                        derivation.push(ResolutionStep {
328                            parent1: current.clone(),
329                            parent2: usable_clause.clone(),
330                            resolvent: resolvent.clone(),
331                            resolved_literal: resolved_lit,
332                        });
333                        return ProofResult::Unsatisfiable { steps, derivation };
334                    }
335
336                    sos.push_back(resolvent.clone());
337                    self.stats.clauses_generated += 1;
338                    derivation.push(ResolutionStep {
339                        parent1: current.clone(),
340                        parent2: usable_clause.clone(),
341                        resolvent,
342                        resolved_literal: resolved_lit,
343                    });
344                }
345            }
346        }
347
348        ProofResult::Satisfiable
349    }
350
351    /// Unit resolution strategy (only resolve with unit clauses).
352    fn prove_unit_resolution(&mut self, max_steps: usize) -> ProofResult {
353        let mut clauses = self.clauses.clone();
354        let mut derivation = Vec::new();
355        let mut steps = 0;
356
357        loop {
358            if steps >= max_steps {
359                return ProofResult::ResourceLimitReached {
360                    steps_attempted: steps,
361                };
362            }
363
364            // Find unit clauses
365            let unit_clauses: Vec<Clause> =
366                clauses.iter().filter(|c| c.is_unit()).cloned().collect();
367
368            if unit_clauses.is_empty() {
369                return ProofResult::Satisfiable;
370            }
371
372            let mut new_clauses = Vec::new();
373            let mut found_new = false;
374
375            // Resolve each unit clause with all clauses
376            for unit in &unit_clauses {
377                for clause in &clauses {
378                    if clause.is_unit() && clause == unit {
379                        continue; // Skip self-resolution
380                    }
381
382                    let resolvents = self.resolve(unit, clause);
383
384                    for (resolvent, resolved_lit) in resolvents {
385                        steps += 1;
386                        self.stats.resolution_steps += 1;
387
388                        if resolvent.is_tautology() {
389                            self.stats.tautologies_removed += 1;
390                            continue;
391                        }
392
393                        if resolvent.is_empty() {
394                            self.stats.empty_clause_found = true;
395                            derivation.push(ResolutionStep {
396                                parent1: unit.clone(),
397                                parent2: clause.clone(),
398                                resolvent: resolvent.clone(),
399                                resolved_literal: resolved_lit,
400                            });
401                            return ProofResult::Unsatisfiable { steps, derivation };
402                        }
403
404                        if !clauses.contains(&resolvent) && !new_clauses.contains(&resolvent) {
405                            new_clauses.push(resolvent.clone());
406                            found_new = true;
407                            self.stats.clauses_generated += 1;
408                            derivation.push(ResolutionStep {
409                                parent1: unit.clone(),
410                                parent2: clause.clone(),
411                                resolvent,
412                                resolved_literal: resolved_lit,
413                            });
414                        }
415                    }
416                }
417            }
418
419            if !found_new {
420                return ProofResult::Satisfiable;
421            }
422
423            clauses.extend(new_clauses);
424        }
425    }
426
427    /// Linear resolution strategy.
428    fn prove_linear(&mut self, max_depth: usize) -> ProofResult {
429        // Simplified linear resolution from first clause
430        if self.clauses.is_empty() {
431            return ProofResult::Satisfiable;
432        }
433
434        let start = self.clauses[0].clone();
435        let mut current = start.clone();
436        let mut depth = 0;
437        let mut derivation = Vec::new();
438
439        while depth < max_depth {
440            if current.is_empty() {
441                self.stats.empty_clause_found = true;
442                return ProofResult::Unsatisfiable {
443                    steps: depth,
444                    derivation,
445                };
446            }
447
448            // Try to resolve with any other clause
449            let mut resolved = false;
450            for other in &self.clauses[1..] {
451                let resolvents = self.resolve(&current, other);
452
453                if let Some((resolvent, resolved_lit)) = resolvents.first() {
454                    if !resolvent.is_tautology() {
455                        current = resolvent.clone();
456                        depth += 1;
457                        self.stats.resolution_steps += 1;
458                        self.stats.clauses_generated += 1;
459                        derivation.push(ResolutionStep {
460                            parent1: current.clone(),
461                            parent2: other.clone(),
462                            resolvent: resolvent.clone(),
463                            resolved_literal: resolved_lit.clone(),
464                        });
465                        resolved = true;
466                        break;
467                    }
468                }
469            }
470
471            if !resolved {
472                return ProofResult::Satisfiable;
473            }
474        }
475
476        ProofResult::ResourceLimitReached {
477            steps_attempted: depth,
478        }
479    }
480}
481
482impl Default for ResolutionProver {
483    fn default() -> Self {
484        Self::new()
485    }
486}