Skip to main content

oxiz_proof/
theory.rs

1//! Theory proof generation for SMT solvers.
2//!
3//! This module provides infrastructure for generating machine-checkable proofs
4//! from theory solver inferences. It bridges the gap between theory-specific
5//! reasoning and proof formats like Alethe and LFSC.
6//!
7//! ## Supported Theories
8//!
9//! - **EUF**: Equality with Uninterpreted Functions (congruence, transitivity)
10//! - **LRA/LIA**: Linear Real/Integer Arithmetic (Farkas lemmas)
11//! - **BV**: BitVectors (bit-level reasoning)
12//! - **Arrays**: Read-over-write axioms
13//!
14//! ## Proof Structure
15//!
16//! Theory proofs consist of:
17//! - **Axiom applications**: Theory axioms (e.g., reflexivity, array extensionality)
18//! - **Inference rules**: Theory-specific deduction rules
19//! - **Lemma steps**: Derived facts used in the proof
20
21use std::fmt;
22use std::io::{self, Write};
23
24/// Unique identifier for a theory proof step
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct TheoryStepId(pub u32);
27
28impl fmt::Display for TheoryStepId {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        write!(f, "t{}", self.0)
31    }
32}
33
34/// A term reference (opaque identifier for formula/term)
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct ProofTerm(pub String);
37
38impl fmt::Display for ProofTerm {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "{}", self.0)
41    }
42}
43
44impl<S: Into<String>> From<S> for ProofTerm {
45    fn from(s: S) -> Self {
46        ProofTerm(s.into())
47    }
48}
49
50/// Theory-specific proof rules
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum TheoryRule {
53    // === EUF Rules ===
54    /// Reflexivity: ⊢ t = t
55    Refl,
56    /// Symmetry: t1 = t2 ⊢ t2 = t1
57    Symm,
58    /// Transitivity: t1 = t2, t2 = t3 ⊢ t1 = t3
59    Trans,
60    /// Congruence: f(a1,...,an) = f(b1,...,bn) when ai = bi
61    Cong,
62    /// Function application equality
63    FuncEq,
64    /// Distinctness axiom: distinct(t1,...,tn) ⊢ ti ≠ tj for i ≠ j
65    Distinct,
66
67    // === Linear Arithmetic Rules ===
68    /// Linear combination of inequalities (Farkas lemma)
69    LaGeneric,
70    /// Multiplication of inequality by positive constant
71    LaMult,
72    /// Addition of two inequalities
73    LaAdd,
74    /// Tightening of integer bounds: x < c ⊢ x ≤ c-1
75    LaTighten,
76    /// Division by constant
77    LaDiv,
78    /// Integer totality: x ≤ c ∨ x ≥ c+1
79    LaTotality,
80    /// Disequality split: x ≠ c ⊢ x < c ∨ x > c
81    LaDiseq,
82
83    // === BitVector Rules ===
84    /// Bit-blasting equality
85    BvBlastEq,
86    /// Bit extraction
87    BvExtract,
88    /// Concatenation
89    BvConcat,
90    /// Zero extension
91    BvZeroExtend,
92    /// Sign extension
93    BvSignExtend,
94    /// Bitwise operations (AND, OR, XOR, NOT)
95    BvBitwise,
96    /// Arithmetic operations
97    BvArith,
98    /// Comparison operations
99    BvCompare,
100    /// Overflow detection
101    BvOverflow,
102
103    // === Array Rules ===
104    /// Read-over-write (same index): (select (store a i v) i) = v
105    ArrReadWrite1,
106    /// Read-over-write (different index): i ≠ j → (select (store a i v) j) = (select a j)
107    ArrReadWrite2,
108    /// Extensionality: (∀i. (select a i) = (select b i)) → a = b
109    ArrExt,
110    /// Const array: (select (const v) i) = v
111    ArrConst,
112
113    // === Quantifier Rules ===
114    /// Forall elimination: ∀x. φ(x) ⊢ φ(t) for any term t
115    ForallElim,
116    /// Exists introduction: φ(t) ⊢ ∃x. φ(x) for any term t
117    ExistsIntro,
118    /// Skolemization: ∃x. φ(x) ⊢ φ(sk) where sk is fresh
119    Skolemize,
120    /// Quantifier instantiation with pattern matching
121    QuantInst,
122    /// α-equivalence (renaming bound variables)
123    AlphaEquiv,
124
125    // === General SMT Rules ===
126    /// Theory conflict (unsat core from theory)
127    TheoryConflict,
128    /// Theory propagation
129    TheoryProp,
130    /// ITE elimination
131    IteElim,
132    /// Boolean simplification
133    BoolSimp,
134    /// Custom rule with name
135    Custom(String),
136}
137
138impl fmt::Display for TheoryRule {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        match self {
141            Self::Refl => write!(f, "refl"),
142            Self::Symm => write!(f, "symm"),
143            Self::Trans => write!(f, "trans"),
144            Self::Cong => write!(f, "cong"),
145            Self::FuncEq => write!(f, "func_eq"),
146            Self::Distinct => write!(f, "distinct"),
147            Self::LaGeneric => write!(f, "la_generic"),
148            Self::LaMult => write!(f, "la_mult"),
149            Self::LaAdd => write!(f, "la_add"),
150            Self::LaTighten => write!(f, "la_tighten"),
151            Self::LaDiv => write!(f, "la_div"),
152            Self::LaTotality => write!(f, "la_totality"),
153            Self::LaDiseq => write!(f, "la_diseq"),
154            Self::BvBlastEq => write!(f, "bv_blast_eq"),
155            Self::BvExtract => write!(f, "bv_extract"),
156            Self::BvConcat => write!(f, "bv_concat"),
157            Self::BvZeroExtend => write!(f, "bv_zero_extend"),
158            Self::BvSignExtend => write!(f, "bv_sign_extend"),
159            Self::BvBitwise => write!(f, "bv_bitwise"),
160            Self::BvArith => write!(f, "bv_arith"),
161            Self::BvCompare => write!(f, "bv_compare"),
162            Self::BvOverflow => write!(f, "bv_overflow"),
163            Self::ArrReadWrite1 => write!(f, "arr_read_write_1"),
164            Self::ArrReadWrite2 => write!(f, "arr_read_write_2"),
165            Self::ArrExt => write!(f, "arr_ext"),
166            Self::ArrConst => write!(f, "arr_const"),
167            Self::ForallElim => write!(f, "forall_elim"),
168            Self::ExistsIntro => write!(f, "exists_intro"),
169            Self::Skolemize => write!(f, "skolemize"),
170            Self::QuantInst => write!(f, "quant_inst"),
171            Self::AlphaEquiv => write!(f, "alpha_equiv"),
172            Self::TheoryConflict => write!(f, "theory_conflict"),
173            Self::TheoryProp => write!(f, "theory_prop"),
174            Self::IteElim => write!(f, "ite_elim"),
175            Self::BoolSimp => write!(f, "bool_simp"),
176            Self::Custom(name) => write!(f, "{}", name),
177        }
178    }
179}
180
181/// A single step in a theory proof
182#[derive(Debug, Clone)]
183pub struct TheoryStep {
184    /// Unique identifier for this step
185    pub id: TheoryStepId,
186    /// The rule applied
187    pub rule: TheoryRule,
188    /// Premises (indices of previous steps)
189    pub premises: Vec<TheoryStepId>,
190    /// Arguments to the rule (theory-specific)
191    pub args: Vec<ProofTerm>,
192    /// The conclusion (what this step proves)
193    pub conclusion: ProofTerm,
194}
195
196impl fmt::Display for TheoryStep {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        write!(f, "{}: {} [", self.id, self.conclusion)?;
199        if !self.premises.is_empty() {
200            for (i, premise) in self.premises.iter().enumerate() {
201                if i > 0 {
202                    write!(f, ", ")?;
203                }
204                write!(f, "{}", premise)?;
205            }
206            write!(f, " | ")?;
207        }
208        write!(f, "{}", self.rule)?;
209        for arg in &self.args {
210            write!(f, " {}", arg)?;
211        }
212        write!(f, "]")
213    }
214}
215
216/// Theory proof builder
217#[derive(Debug, Default)]
218pub struct TheoryProof {
219    /// Steps in the proof
220    steps: Vec<TheoryStep>,
221    /// Next step ID
222    next_id: u32,
223}
224
225impl TheoryProof {
226    /// Create a new empty theory proof
227    #[must_use]
228    pub fn new() -> Self {
229        Self {
230            steps: Vec::new(),
231            next_id: 0,
232        }
233    }
234
235    /// Allocate a new step ID
236    fn alloc_id(&mut self) -> TheoryStepId {
237        let id = TheoryStepId(self.next_id);
238        self.next_id += 1;
239        id
240    }
241
242    /// Add an axiom step (no premises)
243    pub fn add_axiom(
244        &mut self,
245        rule: TheoryRule,
246        conclusion: impl Into<ProofTerm>,
247    ) -> TheoryStepId {
248        let id = self.alloc_id();
249        self.steps.push(TheoryStep {
250            id,
251            rule,
252            premises: Vec::new(),
253            args: Vec::new(),
254            conclusion: conclusion.into(),
255        });
256        id
257    }
258
259    /// Add an inference step with premises
260    pub fn add_step(
261        &mut self,
262        rule: TheoryRule,
263        premises: Vec<TheoryStepId>,
264        conclusion: impl Into<ProofTerm>,
265    ) -> TheoryStepId {
266        let id = self.alloc_id();
267        self.steps.push(TheoryStep {
268            id,
269            rule,
270            premises,
271            args: Vec::new(),
272            conclusion: conclusion.into(),
273        });
274        id
275    }
276
277    /// Add an inference step with premises and arguments
278    pub fn add_step_with_args(
279        &mut self,
280        rule: TheoryRule,
281        premises: Vec<TheoryStepId>,
282        args: Vec<ProofTerm>,
283        conclusion: impl Into<ProofTerm>,
284    ) -> TheoryStepId {
285        let id = self.alloc_id();
286        self.steps.push(TheoryStep {
287            id,
288            rule,
289            premises,
290            args,
291            conclusion: conclusion.into(),
292        });
293        id
294    }
295
296    // === EUF Proof Helpers ===
297
298    /// Add reflexivity step: ⊢ t = t
299    pub fn refl(&mut self, term: impl Into<ProofTerm>) -> TheoryStepId {
300        let t = term.into();
301        let conclusion = format!("(= {} {})", t.0, t.0);
302        self.add_axiom(TheoryRule::Refl, conclusion)
303    }
304
305    /// Add symmetry step: t1 = t2 ⊢ t2 = t1
306    pub fn symm(
307        &mut self,
308        premise: TheoryStepId,
309        t1: impl Into<ProofTerm>,
310        t2: impl Into<ProofTerm>,
311    ) -> TheoryStepId {
312        let conclusion = format!("(= {} {})", t2.into().0, t1.into().0);
313        self.add_step(TheoryRule::Symm, vec![premise], conclusion)
314    }
315
316    /// Add transitivity step: t1 = t2, t2 = t3 ⊢ t1 = t3
317    pub fn trans(
318        &mut self,
319        p1: TheoryStepId,
320        p2: TheoryStepId,
321        t1: impl Into<ProofTerm>,
322        t3: impl Into<ProofTerm>,
323    ) -> TheoryStepId {
324        let conclusion = format!("(= {} {})", t1.into().0, t3.into().0);
325        self.add_step(TheoryRule::Trans, vec![p1, p2], conclusion)
326    }
327
328    /// Add congruence step
329    pub fn cong(
330        &mut self,
331        premises: Vec<TheoryStepId>,
332        func: impl Into<ProofTerm>,
333        args1: &[ProofTerm],
334        args2: &[ProofTerm],
335    ) -> TheoryStepId {
336        let f = func.into();
337        let lhs = if args1.is_empty() {
338            f.0.clone()
339        } else {
340            format!(
341                "({} {})",
342                f.0,
343                args1
344                    .iter()
345                    .map(|a| a.0.as_str())
346                    .collect::<Vec<_>>()
347                    .join(" ")
348            )
349        };
350        let rhs = if args2.is_empty() {
351            f.0.clone()
352        } else {
353            format!(
354                "({} {})",
355                f.0,
356                args2
357                    .iter()
358                    .map(|a| a.0.as_str())
359                    .collect::<Vec<_>>()
360                    .join(" ")
361            )
362        };
363        let conclusion = format!("(= {} {})", lhs, rhs);
364        self.add_step(TheoryRule::Cong, premises, conclusion)
365    }
366
367    // === Arithmetic Proof Helpers ===
368
369    /// Add a Farkas lemma step (linear combination proving unsatisfiability)
370    pub fn farkas(
371        &mut self,
372        premises: Vec<TheoryStepId>,
373        coefficients: &[ProofTerm],
374    ) -> TheoryStepId {
375        let conclusion = "false".to_string();
376        self.add_step_with_args(
377            TheoryRule::LaGeneric,
378            premises,
379            coefficients.to_vec(),
380            conclusion,
381        )
382    }
383
384    /// Add linear arithmetic addition step
385    pub fn la_add(
386        &mut self,
387        p1: TheoryStepId,
388        p2: TheoryStepId,
389        conclusion: impl Into<ProofTerm>,
390    ) -> TheoryStepId {
391        self.add_step(TheoryRule::LaAdd, vec![p1, p2], conclusion)
392    }
393
394    /// Add linear arithmetic multiplication step
395    pub fn la_mult(
396        &mut self,
397        premise: TheoryStepId,
398        coefficient: impl Into<ProofTerm>,
399        conclusion: impl Into<ProofTerm>,
400    ) -> TheoryStepId {
401        self.add_step_with_args(
402            TheoryRule::LaMult,
403            vec![premise],
404            vec![coefficient.into()],
405            conclusion,
406        )
407    }
408
409    // === Array Proof Helpers ===
410
411    /// Add read-over-write-same step
412    pub fn read_write_same(
413        &mut self,
414        array: impl Into<ProofTerm>,
415        index: impl Into<ProofTerm>,
416        value: impl Into<ProofTerm>,
417    ) -> TheoryStepId {
418        let a = array.into();
419        let i = index.into();
420        let v = value.into();
421        let conclusion = format!(
422            "(= (select (store {} {} {}) {}) {})",
423            a.0, i.0, v.0, i.0, v.0
424        );
425        self.add_axiom(TheoryRule::ArrReadWrite1, conclusion)
426    }
427
428    /// Add read-over-write-different step
429    pub fn read_write_diff(
430        &mut self,
431        premise: TheoryStepId,
432        array: impl Into<ProofTerm>,
433        i: impl Into<ProofTerm>,
434        j: impl Into<ProofTerm>,
435        v: impl Into<ProofTerm>,
436    ) -> TheoryStepId {
437        let a = array.into();
438        let idx_i = i.into();
439        let idx_j = j.into();
440        let val = v.into();
441        let conclusion = format!(
442            "(= (select (store {} {} {}) {}) (select {} {}))",
443            a.0, idx_i.0, val.0, idx_j.0, a.0, idx_j.0
444        );
445        self.add_step(TheoryRule::ArrReadWrite2, vec![premise], conclusion)
446    }
447
448    // === BitVector Proof Helpers ===
449
450    /// Add bit-blasting equality step
451    pub fn bv_blast_eq(
452        &mut self,
453        premises: Vec<TheoryStepId>,
454        conclusion: impl Into<ProofTerm>,
455    ) -> TheoryStepId {
456        self.add_step(TheoryRule::BvBlastEq, premises, conclusion)
457    }
458
459    // === Quantifier Proof Helpers ===
460
461    /// Add forall elimination step: ∀x. φ(x) ⊢ φ(t)
462    pub fn forall_elim(
463        &mut self,
464        premise: TheoryStepId,
465        variable: impl Into<ProofTerm>,
466        instantiation: impl Into<ProofTerm>,
467        conclusion: impl Into<ProofTerm>,
468    ) -> TheoryStepId {
469        self.add_step_with_args(
470            TheoryRule::ForallElim,
471            vec![premise],
472            vec![variable.into(), instantiation.into()],
473            conclusion,
474        )
475    }
476
477    /// Add exists introduction step: φ(t) ⊢ ∃x. φ(x)
478    pub fn exists_intro(
479        &mut self,
480        premise: TheoryStepId,
481        witness: impl Into<ProofTerm>,
482        conclusion: impl Into<ProofTerm>,
483    ) -> TheoryStepId {
484        self.add_step_with_args(
485            TheoryRule::ExistsIntro,
486            vec![premise],
487            vec![witness.into()],
488            conclusion,
489        )
490    }
491
492    /// Add skolemization step: ∃x. φ(x) ⊢ φ(sk)
493    pub fn skolemize(
494        &mut self,
495        premise: TheoryStepId,
496        skolem_constant: impl Into<ProofTerm>,
497        conclusion: impl Into<ProofTerm>,
498    ) -> TheoryStepId {
499        self.add_step_with_args(
500            TheoryRule::Skolemize,
501            vec![premise],
502            vec![skolem_constant.into()],
503            conclusion,
504        )
505    }
506
507    /// Add quantifier instantiation with pattern matching
508    pub fn quant_inst(
509        &mut self,
510        quantified_formula: TheoryStepId,
511        pattern: impl Into<ProofTerm>,
512        instantiations: Vec<ProofTerm>,
513        conclusion: impl Into<ProofTerm>,
514    ) -> TheoryStepId {
515        let mut args = vec![pattern.into()];
516        args.extend(instantiations);
517        self.add_step_with_args(
518            TheoryRule::QuantInst,
519            vec![quantified_formula],
520            args,
521            conclusion,
522        )
523    }
524
525    // === General Methods ===
526
527    /// Get the number of steps
528    #[must_use]
529    pub fn len(&self) -> usize {
530        self.steps.len()
531    }
532
533    /// Check if the proof is empty
534    #[must_use]
535    pub fn is_empty(&self) -> bool {
536        self.steps.is_empty()
537    }
538
539    /// Get all steps
540    #[must_use]
541    pub fn steps(&self) -> &[TheoryStep] {
542        &self.steps
543    }
544
545    /// Get a step by ID
546    #[must_use]
547    pub fn get_step(&self, id: TheoryStepId) -> Option<&TheoryStep> {
548        self.steps.get(id.0 as usize)
549    }
550
551    /// Clear the proof
552    pub fn clear(&mut self) {
553        self.steps.clear();
554        self.next_id = 0;
555    }
556
557    /// Write the proof in human-readable format
558    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
559        writeln!(writer, "; Theory proof generated by OxiZ")?;
560        writeln!(writer)?;
561
562        for step in &self.steps {
563            writeln!(writer, "{}", step)?;
564        }
565
566        Ok(())
567    }
568
569    /// Convert to string
570    #[must_use]
571    pub fn to_string_repr(&self) -> String {
572        let mut buf = Vec::new();
573        self.write(&mut buf)
574            .expect("writing to Vec should not fail");
575        String::from_utf8(buf).expect("output is UTF-8")
576    }
577}
578
579/// EUF-specific proof recorder
580#[derive(Debug, Default)]
581pub struct EufProofRecorder {
582    /// Underlying proof
583    proof: TheoryProof,
584    /// Mapping from equality pairs to step IDs
585    equality_steps: rustc_hash::FxHashMap<(u32, u32), TheoryStepId>,
586}
587
588impl EufProofRecorder {
589    /// Create a new EUF proof recorder
590    #[must_use]
591    pub fn new() -> Self {
592        Self {
593            proof: TheoryProof::new(),
594            equality_steps: rustc_hash::FxHashMap::default(),
595        }
596    }
597
598    /// Record an equality assertion
599    pub fn record_equality(&mut self, a: u32, b: u32, term: impl Into<ProofTerm>) -> TheoryStepId {
600        let id = self
601            .proof
602            .add_axiom(TheoryRule::Custom("assert".to_string()), term);
603        self.equality_steps.insert((a.min(b), a.max(b)), id);
604        id
605    }
606
607    /// Record a congruence step
608    pub fn record_congruence(
609        &mut self,
610        func: impl Into<ProofTerm>,
611        arg_equalities: Vec<TheoryStepId>,
612        result: impl Into<ProofTerm>,
613    ) -> TheoryStepId {
614        self.proof
615            .add_step_with_args(TheoryRule::Cong, arg_equalities, vec![func.into()], result)
616    }
617
618    /// Record a transitivity chain
619    pub fn record_transitivity(
620        &mut self,
621        steps: Vec<TheoryStepId>,
622        conclusion: impl Into<ProofTerm>,
623    ) -> TheoryStepId {
624        self.proof.add_step(TheoryRule::Trans, steps, conclusion)
625    }
626
627    /// Get the recorded proof
628    #[must_use]
629    pub fn proof(&self) -> &TheoryProof {
630        &self.proof
631    }
632
633    /// Take the proof
634    pub fn take_proof(&mut self) -> TheoryProof {
635        std::mem::take(&mut self.proof)
636    }
637}
638
639/// Arithmetic-specific proof recorder
640#[derive(Debug, Default)]
641pub struct ArithProofRecorder {
642    /// Underlying proof
643    proof: TheoryProof,
644    /// Constraint step IDs
645    constraint_steps: Vec<TheoryStepId>,
646}
647
648impl ArithProofRecorder {
649    /// Create a new arithmetic proof recorder
650    #[must_use]
651    pub fn new() -> Self {
652        Self {
653            proof: TheoryProof::new(),
654            constraint_steps: Vec::new(),
655        }
656    }
657
658    /// Record a bound constraint
659    pub fn record_bound(&mut self, constraint: impl Into<ProofTerm>) -> TheoryStepId {
660        let id = self
661            .proof
662            .add_axiom(TheoryRule::Custom("bound".to_string()), constraint);
663        self.constraint_steps.push(id);
664        id
665    }
666
667    /// Record a Farkas conflict
668    pub fn record_farkas_conflict(&mut self, reasons: &[u32]) -> TheoryStepId {
669        let premises: Vec<TheoryStepId> = reasons
670            .iter()
671            .filter_map(|&r| self.constraint_steps.get(r as usize).copied())
672            .collect();
673        self.proof
674            .add_step(TheoryRule::LaGeneric, premises, "false")
675    }
676
677    /// Get the recorded proof
678    #[must_use]
679    pub fn proof(&self) -> &TheoryProof {
680        &self.proof
681    }
682
683    /// Take the proof
684    pub fn take_proof(&mut self) -> TheoryProof {
685        std::mem::take(&mut self.proof)
686    }
687}
688
689/// Array-specific proof recorder
690#[derive(Debug, Default)]
691pub struct ArrayProofRecorder {
692    /// Underlying proof
693    proof: TheoryProof,
694}
695
696impl ArrayProofRecorder {
697    /// Create a new array proof recorder
698    #[must_use]
699    pub fn new() -> Self {
700        Self {
701            proof: TheoryProof::new(),
702        }
703    }
704
705    /// Record a select-store axiom (same index)
706    pub fn record_select_store_same(
707        &mut self,
708        array: impl Into<ProofTerm>,
709        index: impl Into<ProofTerm>,
710        value: impl Into<ProofTerm>,
711    ) -> TheoryStepId {
712        self.proof.read_write_same(array, index, value)
713    }
714
715    /// Record a select-store axiom (different index)
716    pub fn record_select_store_diff(
717        &mut self,
718        neq_proof: TheoryStepId,
719        array: impl Into<ProofTerm>,
720        i: impl Into<ProofTerm>,
721        j: impl Into<ProofTerm>,
722        v: impl Into<ProofTerm>,
723    ) -> TheoryStepId {
724        self.proof.read_write_diff(neq_proof, array, i, j, v)
725    }
726
727    /// Get the recorded proof
728    #[must_use]
729    pub fn proof(&self) -> &TheoryProof {
730        &self.proof
731    }
732
733    /// Take the proof
734    pub fn take_proof(&mut self) -> TheoryProof {
735        std::mem::take(&mut self.proof)
736    }
737}
738
739/// Trait for theory solvers that support proof generation
740pub trait TheoryProofProducer {
741    /// Enable proof recording
742    fn enable_proof(&mut self);
743
744    /// Disable proof recording
745    fn disable_proof(&mut self);
746
747    /// Check if proof recording is enabled
748    fn proof_enabled(&self) -> bool;
749
750    /// Get the theory proof (if available)
751    fn get_theory_proof(&self) -> Option<&TheoryProof>;
752
753    /// Take the theory proof
754    fn take_theory_proof(&mut self) -> Option<TheoryProof>;
755}
756
757#[cfg(test)]
758mod tests {
759    use super::*;
760
761    #[test]
762    fn test_theory_step_id_display() {
763        let id = TheoryStepId(42);
764        assert_eq!(format!("{}", id), "t42");
765    }
766
767    #[test]
768    fn test_theory_rule_display() {
769        assert_eq!(format!("{}", TheoryRule::Refl), "refl");
770        assert_eq!(format!("{}", TheoryRule::Trans), "trans");
771        assert_eq!(format!("{}", TheoryRule::LaGeneric), "la_generic");
772        assert_eq!(format!("{}", TheoryRule::ArrReadWrite1), "arr_read_write_1");
773    }
774
775    #[test]
776    fn test_theory_proof_reflexivity() {
777        let mut proof = TheoryProof::new();
778
779        let step = proof.refl("x");
780        assert_eq!(step.0, 0);
781
782        let s = proof.get_step(step).expect("test operation should succeed");
783        assert_eq!(s.conclusion.0, "(= x x)");
784        assert_eq!(s.rule, TheoryRule::Refl);
785        assert!(s.premises.is_empty());
786    }
787
788    #[test]
789    fn test_theory_proof_transitivity() {
790        let mut proof = TheoryProof::new();
791
792        // Step 1: a = b (axiom)
793        let s1 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= a b)");
794
795        // Step 2: b = c (axiom)
796        let s2 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= b c)");
797
798        // Step 3: a = c (by transitivity)
799        let s3 = proof.trans(s1, s2, "a", "c");
800
801        let step = proof.get_step(s3).expect("test operation should succeed");
802        assert_eq!(step.conclusion.0, "(= a c)");
803        assert_eq!(step.rule, TheoryRule::Trans);
804        assert_eq!(step.premises.len(), 2);
805    }
806
807    #[test]
808    fn test_theory_proof_farkas() {
809        let mut proof = TheoryProof::new();
810
811        // x >= 10
812        let s1 = proof.add_axiom(TheoryRule::Custom("bound".into()), "(>= x 10)");
813
814        // x <= 5
815        let s2 = proof.add_axiom(TheoryRule::Custom("bound".into()), "(<= x 5)");
816
817        // Conflict by Farkas lemma with coefficients [1, 1]
818        let s3 = proof.farkas(
819            vec![s1, s2],
820            &[ProofTerm("1".into()), ProofTerm("1".into())],
821        );
822
823        let step = proof.get_step(s3).expect("test operation should succeed");
824        assert_eq!(step.conclusion.0, "false");
825        assert_eq!(step.rule, TheoryRule::LaGeneric);
826    }
827
828    #[test]
829    fn test_theory_proof_array_read_write() {
830        let mut proof = TheoryProof::new();
831
832        let step = proof.read_write_same("a", "i", "v");
833
834        let s = proof.get_step(step).expect("test operation should succeed");
835        assert!(s.conclusion.0.contains("select"));
836        assert!(s.conclusion.0.contains("store"));
837        assert_eq!(s.rule, TheoryRule::ArrReadWrite1);
838    }
839
840    #[test]
841    fn test_euf_proof_recorder() {
842        let mut recorder = EufProofRecorder::new();
843
844        let s1 = recorder.record_equality(0, 1, "(= a b)");
845        let s2 = recorder.record_equality(1, 2, "(= b c)");
846        let _s3 = recorder.record_transitivity(vec![s1, s2], "(= a c)");
847
848        assert_eq!(recorder.proof().len(), 3);
849    }
850
851    #[test]
852    fn test_arith_proof_recorder() {
853        let mut recorder = ArithProofRecorder::new();
854
855        recorder.record_bound("(>= x 10)");
856        recorder.record_bound("(<= x 5)");
857        let conflict = recorder.record_farkas_conflict(&[0, 1]);
858
859        let step = recorder
860            .proof()
861            .get_step(conflict)
862            .expect("test operation should succeed");
863        assert_eq!(step.conclusion.0, "false");
864    }
865
866    #[test]
867    fn test_theory_proof_clear() {
868        let mut proof = TheoryProof::new();
869        proof.refl("x");
870        proof.refl("y");
871
872        assert_eq!(proof.len(), 2);
873        proof.clear();
874        assert!(proof.is_empty());
875    }
876
877    #[test]
878    fn test_theory_proof_write() {
879        let mut proof = TheoryProof::new();
880        proof.refl("x");
881
882        let output = proof.to_string_repr();
883        assert!(output.contains("Theory proof"));
884        assert!(output.contains("(= x x)"));
885    }
886
887    #[test]
888    fn test_quantifier_forall_elim() {
889        let mut proof = TheoryProof::new();
890
891        // ∀x. P(x)
892        let forall_step = proof.add_axiom(TheoryRule::Custom("axiom".into()), "(forall x (P x))");
893
894        // ∀x. P(x) ⊢ P(5)
895        let inst_step = proof.forall_elim(forall_step, "x", "5", "(P 5)");
896
897        let step = proof
898            .get_step(inst_step)
899            .expect("test operation should succeed");
900        assert_eq!(step.rule, TheoryRule::ForallElim);
901        assert_eq!(step.premises.len(), 1);
902        assert_eq!(step.args.len(), 2);
903    }
904
905    #[test]
906    fn test_quantifier_exists_intro() {
907        let mut proof = TheoryProof::new();
908
909        // P(5)
910        let concrete = proof.add_axiom(TheoryRule::Custom("axiom".into()), "(P 5)");
911
912        // P(5) ⊢ ∃x. P(x)
913        let exists_step = proof.exists_intro(concrete, "5", "(exists x (P x))");
914
915        let step = proof
916            .get_step(exists_step)
917            .expect("test operation should succeed");
918        assert_eq!(step.rule, TheoryRule::ExistsIntro);
919        assert_eq!(step.args.len(), 1);
920    }
921
922    #[test]
923    fn test_quantifier_skolemization() {
924        let mut proof = TheoryProof::new();
925
926        // ∃x. P(x)
927        let exists_step = proof.add_axiom(TheoryRule::Custom("axiom".into()), "(exists x (P x))");
928
929        // ∃x. P(x) ⊢ P(sk_1)
930        let skolem_step = proof.skolemize(exists_step, "sk_1", "(P sk_1)");
931
932        let step = proof
933            .get_step(skolem_step)
934            .expect("test operation should succeed");
935        assert_eq!(step.rule, TheoryRule::Skolemize);
936        assert!(step.conclusion.0.contains("sk_1"));
937    }
938
939    #[test]
940    fn test_quantifier_inst_pattern() {
941        let mut proof = TheoryProof::new();
942
943        // ∀x. (P x) => (Q x)
944        let forall_step = proof.add_axiom(
945            TheoryRule::Custom("axiom".into()),
946            "(forall x (=> (P x) (Q x)))",
947        );
948
949        // Instantiate with pattern matching
950        let inst_step = proof.quant_inst(
951            forall_step,
952            "(P x)",
953            vec![ProofTerm("a".into())],
954            "(=> (P a) (Q a))",
955        );
956
957        let step = proof
958            .get_step(inst_step)
959            .expect("test operation should succeed");
960        assert_eq!(step.rule, TheoryRule::QuantInst);
961        assert!(!step.args.is_empty());
962    }
963
964    #[test]
965    fn test_quantifier_rule_display() {
966        assert_eq!(format!("{}", TheoryRule::ForallElim), "forall_elim");
967        assert_eq!(format!("{}", TheoryRule::ExistsIntro), "exists_intro");
968        assert_eq!(format!("{}", TheoryRule::Skolemize), "skolemize");
969        assert_eq!(format!("{}", TheoryRule::QuantInst), "quant_inst");
970    }
971}