use std::fmt;
use std::io::{self, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TheoryStepId(pub u32);
impl fmt::Display for TheoryStepId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "t{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProofTerm(pub String);
impl fmt::Display for ProofTerm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<S: Into<String>> From<S> for ProofTerm {
fn from(s: S) -> Self {
ProofTerm(s.into())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TheoryRule {
Refl,
Symm,
Trans,
Cong,
FuncEq,
Distinct,
LaGeneric,
LaMult,
LaAdd,
LaTighten,
LaDiv,
LaTotality,
LaDiseq,
BvBlastEq,
BvExtract,
BvConcat,
BvZeroExtend,
BvSignExtend,
BvBitwise,
BvArith,
BvCompare,
BvOverflow,
ArrReadWrite1,
ArrReadWrite2,
ArrExt,
ArrConst,
ForallElim,
ExistsIntro,
Skolemize,
QuantInst,
AlphaEquiv,
TheoryConflict,
TheoryProp,
IteElim,
BoolSimp,
Custom(String),
}
impl fmt::Display for TheoryRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Refl => write!(f, "refl"),
Self::Symm => write!(f, "symm"),
Self::Trans => write!(f, "trans"),
Self::Cong => write!(f, "cong"),
Self::FuncEq => write!(f, "func_eq"),
Self::Distinct => write!(f, "distinct"),
Self::LaGeneric => write!(f, "la_generic"),
Self::LaMult => write!(f, "la_mult"),
Self::LaAdd => write!(f, "la_add"),
Self::LaTighten => write!(f, "la_tighten"),
Self::LaDiv => write!(f, "la_div"),
Self::LaTotality => write!(f, "la_totality"),
Self::LaDiseq => write!(f, "la_diseq"),
Self::BvBlastEq => write!(f, "bv_blast_eq"),
Self::BvExtract => write!(f, "bv_extract"),
Self::BvConcat => write!(f, "bv_concat"),
Self::BvZeroExtend => write!(f, "bv_zero_extend"),
Self::BvSignExtend => write!(f, "bv_sign_extend"),
Self::BvBitwise => write!(f, "bv_bitwise"),
Self::BvArith => write!(f, "bv_arith"),
Self::BvCompare => write!(f, "bv_compare"),
Self::BvOverflow => write!(f, "bv_overflow"),
Self::ArrReadWrite1 => write!(f, "arr_read_write_1"),
Self::ArrReadWrite2 => write!(f, "arr_read_write_2"),
Self::ArrExt => write!(f, "arr_ext"),
Self::ArrConst => write!(f, "arr_const"),
Self::ForallElim => write!(f, "forall_elim"),
Self::ExistsIntro => write!(f, "exists_intro"),
Self::Skolemize => write!(f, "skolemize"),
Self::QuantInst => write!(f, "quant_inst"),
Self::AlphaEquiv => write!(f, "alpha_equiv"),
Self::TheoryConflict => write!(f, "theory_conflict"),
Self::TheoryProp => write!(f, "theory_prop"),
Self::IteElim => write!(f, "ite_elim"),
Self::BoolSimp => write!(f, "bool_simp"),
Self::Custom(name) => write!(f, "{}", name),
}
}
}
#[derive(Debug, Clone)]
pub struct TheoryStep {
pub id: TheoryStepId,
pub rule: TheoryRule,
pub premises: Vec<TheoryStepId>,
pub args: Vec<ProofTerm>,
pub conclusion: ProofTerm,
}
impl fmt::Display for TheoryStep {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {} [", self.id, self.conclusion)?;
if !self.premises.is_empty() {
for (i, premise) in self.premises.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", premise)?;
}
write!(f, " | ")?;
}
write!(f, "{}", self.rule)?;
for arg in &self.args {
write!(f, " {}", arg)?;
}
write!(f, "]")
}
}
#[derive(Debug, Default)]
pub struct TheoryProof {
steps: Vec<TheoryStep>,
next_id: u32,
}
impl TheoryProof {
#[must_use]
pub fn new() -> Self {
Self {
steps: Vec::new(),
next_id: 0,
}
}
fn alloc_id(&mut self) -> TheoryStepId {
let id = TheoryStepId(self.next_id);
self.next_id += 1;
id
}
pub fn add_axiom(
&mut self,
rule: TheoryRule,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
let id = self.alloc_id();
self.steps.push(TheoryStep {
id,
rule,
premises: Vec::new(),
args: Vec::new(),
conclusion: conclusion.into(),
});
id
}
pub fn add_step(
&mut self,
rule: TheoryRule,
premises: Vec<TheoryStepId>,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
let id = self.alloc_id();
self.steps.push(TheoryStep {
id,
rule,
premises,
args: Vec::new(),
conclusion: conclusion.into(),
});
id
}
pub fn add_step_with_args(
&mut self,
rule: TheoryRule,
premises: Vec<TheoryStepId>,
args: Vec<ProofTerm>,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
let id = self.alloc_id();
self.steps.push(TheoryStep {
id,
rule,
premises,
args,
conclusion: conclusion.into(),
});
id
}
pub fn refl(&mut self, term: impl Into<ProofTerm>) -> TheoryStepId {
let t = term.into();
let conclusion = format!("(= {} {})", t.0, t.0);
self.add_axiom(TheoryRule::Refl, conclusion)
}
pub fn symm(
&mut self,
premise: TheoryStepId,
t1: impl Into<ProofTerm>,
t2: impl Into<ProofTerm>,
) -> TheoryStepId {
let conclusion = format!("(= {} {})", t2.into().0, t1.into().0);
self.add_step(TheoryRule::Symm, vec![premise], conclusion)
}
pub fn trans(
&mut self,
p1: TheoryStepId,
p2: TheoryStepId,
t1: impl Into<ProofTerm>,
t3: impl Into<ProofTerm>,
) -> TheoryStepId {
let conclusion = format!("(= {} {})", t1.into().0, t3.into().0);
self.add_step(TheoryRule::Trans, vec![p1, p2], conclusion)
}
pub fn cong(
&mut self,
premises: Vec<TheoryStepId>,
func: impl Into<ProofTerm>,
args1: &[ProofTerm],
args2: &[ProofTerm],
) -> TheoryStepId {
let f = func.into();
let lhs = if args1.is_empty() {
f.0.clone()
} else {
format!(
"({} {})",
f.0,
args1
.iter()
.map(|a| a.0.as_str())
.collect::<Vec<_>>()
.join(" ")
)
};
let rhs = if args2.is_empty() {
f.0.clone()
} else {
format!(
"({} {})",
f.0,
args2
.iter()
.map(|a| a.0.as_str())
.collect::<Vec<_>>()
.join(" ")
)
};
let conclusion = format!("(= {} {})", lhs, rhs);
self.add_step(TheoryRule::Cong, premises, conclusion)
}
pub fn farkas(
&mut self,
premises: Vec<TheoryStepId>,
coefficients: &[ProofTerm],
) -> TheoryStepId {
let conclusion = "false".to_string();
self.add_step_with_args(
TheoryRule::LaGeneric,
premises,
coefficients.to_vec(),
conclusion,
)
}
pub fn la_add(
&mut self,
p1: TheoryStepId,
p2: TheoryStepId,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
self.add_step(TheoryRule::LaAdd, vec![p1, p2], conclusion)
}
pub fn la_mult(
&mut self,
premise: TheoryStepId,
coefficient: impl Into<ProofTerm>,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
self.add_step_with_args(
TheoryRule::LaMult,
vec![premise],
vec![coefficient.into()],
conclusion,
)
}
pub fn read_write_same(
&mut self,
array: impl Into<ProofTerm>,
index: impl Into<ProofTerm>,
value: impl Into<ProofTerm>,
) -> TheoryStepId {
let a = array.into();
let i = index.into();
let v = value.into();
let conclusion = format!(
"(= (select (store {} {} {}) {}) {})",
a.0, i.0, v.0, i.0, v.0
);
self.add_axiom(TheoryRule::ArrReadWrite1, conclusion)
}
pub fn read_write_diff(
&mut self,
premise: TheoryStepId,
array: impl Into<ProofTerm>,
i: impl Into<ProofTerm>,
j: impl Into<ProofTerm>,
v: impl Into<ProofTerm>,
) -> TheoryStepId {
let a = array.into();
let idx_i = i.into();
let idx_j = j.into();
let val = v.into();
let conclusion = format!(
"(= (select (store {} {} {}) {}) (select {} {}))",
a.0, idx_i.0, val.0, idx_j.0, a.0, idx_j.0
);
self.add_step(TheoryRule::ArrReadWrite2, vec![premise], conclusion)
}
pub fn bv_blast_eq(
&mut self,
premises: Vec<TheoryStepId>,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
self.add_step(TheoryRule::BvBlastEq, premises, conclusion)
}
pub fn forall_elim(
&mut self,
premise: TheoryStepId,
variable: impl Into<ProofTerm>,
instantiation: impl Into<ProofTerm>,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
self.add_step_with_args(
TheoryRule::ForallElim,
vec![premise],
vec![variable.into(), instantiation.into()],
conclusion,
)
}
pub fn exists_intro(
&mut self,
premise: TheoryStepId,
witness: impl Into<ProofTerm>,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
self.add_step_with_args(
TheoryRule::ExistsIntro,
vec![premise],
vec![witness.into()],
conclusion,
)
}
pub fn skolemize(
&mut self,
premise: TheoryStepId,
skolem_constant: impl Into<ProofTerm>,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
self.add_step_with_args(
TheoryRule::Skolemize,
vec![premise],
vec![skolem_constant.into()],
conclusion,
)
}
pub fn quant_inst(
&mut self,
quantified_formula: TheoryStepId,
pattern: impl Into<ProofTerm>,
instantiations: Vec<ProofTerm>,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
let mut args = vec![pattern.into()];
args.extend(instantiations);
self.add_step_with_args(
TheoryRule::QuantInst,
vec![quantified_formula],
args,
conclusion,
)
}
#[must_use]
pub fn len(&self) -> usize {
self.steps.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
#[must_use]
pub fn steps(&self) -> &[TheoryStep] {
&self.steps
}
#[must_use]
pub fn get_step(&self, id: TheoryStepId) -> Option<&TheoryStep> {
self.steps.get(id.0 as usize)
}
pub fn clear(&mut self) {
self.steps.clear();
self.next_id = 0;
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writeln!(writer, "; Theory proof generated by OxiZ")?;
writeln!(writer)?;
for step in &self.steps {
writeln!(writer, "{}", step)?;
}
Ok(())
}
#[must_use]
pub fn to_string_repr(&self) -> String {
let mut buf = Vec::new();
self.write(&mut buf)
.expect("writing to Vec should not fail");
String::from_utf8(buf).expect("output is UTF-8")
}
}
#[derive(Debug, Default)]
pub struct EufProofRecorder {
proof: TheoryProof,
equality_steps: rustc_hash::FxHashMap<(u32, u32), TheoryStepId>,
}
impl EufProofRecorder {
#[must_use]
pub fn new() -> Self {
Self {
proof: TheoryProof::new(),
equality_steps: rustc_hash::FxHashMap::default(),
}
}
pub fn record_equality(&mut self, a: u32, b: u32, term: impl Into<ProofTerm>) -> TheoryStepId {
let id = self
.proof
.add_axiom(TheoryRule::Custom("assert".to_string()), term);
self.equality_steps.insert((a.min(b), a.max(b)), id);
id
}
pub fn record_congruence(
&mut self,
func: impl Into<ProofTerm>,
arg_equalities: Vec<TheoryStepId>,
result: impl Into<ProofTerm>,
) -> TheoryStepId {
self.proof
.add_step_with_args(TheoryRule::Cong, arg_equalities, vec![func.into()], result)
}
pub fn record_transitivity(
&mut self,
steps: Vec<TheoryStepId>,
conclusion: impl Into<ProofTerm>,
) -> TheoryStepId {
self.proof.add_step(TheoryRule::Trans, steps, conclusion)
}
#[must_use]
pub fn proof(&self) -> &TheoryProof {
&self.proof
}
pub fn take_proof(&mut self) -> TheoryProof {
std::mem::take(&mut self.proof)
}
}
#[derive(Debug, Default)]
pub struct ArithProofRecorder {
proof: TheoryProof,
constraint_steps: Vec<TheoryStepId>,
}
impl ArithProofRecorder {
#[must_use]
pub fn new() -> Self {
Self {
proof: TheoryProof::new(),
constraint_steps: Vec::new(),
}
}
pub fn record_bound(&mut self, constraint: impl Into<ProofTerm>) -> TheoryStepId {
let id = self
.proof
.add_axiom(TheoryRule::Custom("bound".to_string()), constraint);
self.constraint_steps.push(id);
id
}
pub fn record_farkas_conflict(&mut self, reasons: &[u32]) -> TheoryStepId {
let premises: Vec<TheoryStepId> = reasons
.iter()
.filter_map(|&r| self.constraint_steps.get(r as usize).copied())
.collect();
self.proof
.add_step(TheoryRule::LaGeneric, premises, "false")
}
#[must_use]
pub fn proof(&self) -> &TheoryProof {
&self.proof
}
pub fn take_proof(&mut self) -> TheoryProof {
std::mem::take(&mut self.proof)
}
}
#[derive(Debug, Default)]
pub struct ArrayProofRecorder {
proof: TheoryProof,
}
impl ArrayProofRecorder {
#[must_use]
pub fn new() -> Self {
Self {
proof: TheoryProof::new(),
}
}
pub fn record_select_store_same(
&mut self,
array: impl Into<ProofTerm>,
index: impl Into<ProofTerm>,
value: impl Into<ProofTerm>,
) -> TheoryStepId {
self.proof.read_write_same(array, index, value)
}
pub fn record_select_store_diff(
&mut self,
neq_proof: TheoryStepId,
array: impl Into<ProofTerm>,
i: impl Into<ProofTerm>,
j: impl Into<ProofTerm>,
v: impl Into<ProofTerm>,
) -> TheoryStepId {
self.proof.read_write_diff(neq_proof, array, i, j, v)
}
#[must_use]
pub fn proof(&self) -> &TheoryProof {
&self.proof
}
pub fn take_proof(&mut self) -> TheoryProof {
std::mem::take(&mut self.proof)
}
}
pub trait TheoryProofProducer {
fn enable_proof(&mut self);
fn disable_proof(&mut self);
fn proof_enabled(&self) -> bool;
fn get_theory_proof(&self) -> Option<&TheoryProof>;
fn take_theory_proof(&mut self) -> Option<TheoryProof>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_theory_step_id_display() {
let id = TheoryStepId(42);
assert_eq!(format!("{}", id), "t42");
}
#[test]
fn test_theory_rule_display() {
assert_eq!(format!("{}", TheoryRule::Refl), "refl");
assert_eq!(format!("{}", TheoryRule::Trans), "trans");
assert_eq!(format!("{}", TheoryRule::LaGeneric), "la_generic");
assert_eq!(format!("{}", TheoryRule::ArrReadWrite1), "arr_read_write_1");
}
#[test]
fn test_theory_proof_reflexivity() {
let mut proof = TheoryProof::new();
let step = proof.refl("x");
assert_eq!(step.0, 0);
let s = proof.get_step(step).expect("test operation should succeed");
assert_eq!(s.conclusion.0, "(= x x)");
assert_eq!(s.rule, TheoryRule::Refl);
assert!(s.premises.is_empty());
}
#[test]
fn test_theory_proof_transitivity() {
let mut proof = TheoryProof::new();
let s1 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= a b)");
let s2 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= b c)");
let s3 = proof.trans(s1, s2, "a", "c");
let step = proof.get_step(s3).expect("test operation should succeed");
assert_eq!(step.conclusion.0, "(= a c)");
assert_eq!(step.rule, TheoryRule::Trans);
assert_eq!(step.premises.len(), 2);
}
#[test]
fn test_theory_proof_farkas() {
let mut proof = TheoryProof::new();
let s1 = proof.add_axiom(TheoryRule::Custom("bound".into()), "(>= x 10)");
let s2 = proof.add_axiom(TheoryRule::Custom("bound".into()), "(<= x 5)");
let s3 = proof.farkas(
vec![s1, s2],
&[ProofTerm("1".into()), ProofTerm("1".into())],
);
let step = proof.get_step(s3).expect("test operation should succeed");
assert_eq!(step.conclusion.0, "false");
assert_eq!(step.rule, TheoryRule::LaGeneric);
}
#[test]
fn test_theory_proof_array_read_write() {
let mut proof = TheoryProof::new();
let step = proof.read_write_same("a", "i", "v");
let s = proof.get_step(step).expect("test operation should succeed");
assert!(s.conclusion.0.contains("select"));
assert!(s.conclusion.0.contains("store"));
assert_eq!(s.rule, TheoryRule::ArrReadWrite1);
}
#[test]
fn test_euf_proof_recorder() {
let mut recorder = EufProofRecorder::new();
let s1 = recorder.record_equality(0, 1, "(= a b)");
let s2 = recorder.record_equality(1, 2, "(= b c)");
let _s3 = recorder.record_transitivity(vec![s1, s2], "(= a c)");
assert_eq!(recorder.proof().len(), 3);
}
#[test]
fn test_arith_proof_recorder() {
let mut recorder = ArithProofRecorder::new();
recorder.record_bound("(>= x 10)");
recorder.record_bound("(<= x 5)");
let conflict = recorder.record_farkas_conflict(&[0, 1]);
let step = recorder
.proof()
.get_step(conflict)
.expect("test operation should succeed");
assert_eq!(step.conclusion.0, "false");
}
#[test]
fn test_theory_proof_clear() {
let mut proof = TheoryProof::new();
proof.refl("x");
proof.refl("y");
assert_eq!(proof.len(), 2);
proof.clear();
assert!(proof.is_empty());
}
#[test]
fn test_theory_proof_write() {
let mut proof = TheoryProof::new();
proof.refl("x");
let output = proof.to_string_repr();
assert!(output.contains("Theory proof"));
assert!(output.contains("(= x x)"));
}
#[test]
fn test_quantifier_forall_elim() {
let mut proof = TheoryProof::new();
let forall_step = proof.add_axiom(TheoryRule::Custom("axiom".into()), "(forall x (P x))");
let inst_step = proof.forall_elim(forall_step, "x", "5", "(P 5)");
let step = proof
.get_step(inst_step)
.expect("test operation should succeed");
assert_eq!(step.rule, TheoryRule::ForallElim);
assert_eq!(step.premises.len(), 1);
assert_eq!(step.args.len(), 2);
}
#[test]
fn test_quantifier_exists_intro() {
let mut proof = TheoryProof::new();
let concrete = proof.add_axiom(TheoryRule::Custom("axiom".into()), "(P 5)");
let exists_step = proof.exists_intro(concrete, "5", "(exists x (P x))");
let step = proof
.get_step(exists_step)
.expect("test operation should succeed");
assert_eq!(step.rule, TheoryRule::ExistsIntro);
assert_eq!(step.args.len(), 1);
}
#[test]
fn test_quantifier_skolemization() {
let mut proof = TheoryProof::new();
let exists_step = proof.add_axiom(TheoryRule::Custom("axiom".into()), "(exists x (P x))");
let skolem_step = proof.skolemize(exists_step, "sk_1", "(P sk_1)");
let step = proof
.get_step(skolem_step)
.expect("test operation should succeed");
assert_eq!(step.rule, TheoryRule::Skolemize);
assert!(step.conclusion.0.contains("sk_1"));
}
#[test]
fn test_quantifier_inst_pattern() {
let mut proof = TheoryProof::new();
let forall_step = proof.add_axiom(
TheoryRule::Custom("axiom".into()),
"(forall x (=> (P x) (Q x)))",
);
let inst_step = proof.quant_inst(
forall_step,
"(P x)",
vec![ProofTerm("a".into())],
"(=> (P a) (Q a))",
);
let step = proof
.get_step(inst_step)
.expect("test operation should succeed");
assert_eq!(step.rule, TheoryRule::QuantInst);
assert!(!step.args.is_empty());
}
#[test]
fn test_quantifier_rule_display() {
assert_eq!(format!("{}", TheoryRule::ForallElim), "forall_elim");
assert_eq!(format!("{}", TheoryRule::ExistsIntro), "exists_intro");
assert_eq!(format!("{}", TheoryRule::Skolemize), "skolemize");
assert_eq!(format!("{}", TheoryRule::QuantInst), "quant_inst");
}
}