use crate::error::ProofResult;
use crate::{DerivationTree, InferenceRule, ProofExpr, ProofGoal, ProofTerm};
use crate::modal_translation::{contains_modal_constructs, WorldTranslation};
use logicaffeine_verify::ir::{VerifyExpr, VerifyOp, VerifyType};
use logicaffeine_verify::solver::VerificationSession;
use logicaffeine_verify::VerificationErrorKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SmtVerdict {
Entailed,
NotEntailed,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SmtConsistency {
Consistent,
Inconsistent,
Unknown,
}
#[derive(Debug, Clone, Default)]
pub struct SmtTheory {
pub cumulative_predicates: Vec<String>,
}
pub fn oracle_entails(premises: &[ProofExpr], goal: &ProofExpr) -> SmtVerdict {
oracle_entails_with_theory(premises, goal, &SmtTheory::default())
}
pub fn oracle_entails_with_theory(
premises: &[ProofExpr],
goal: &ProofExpr,
theory: &SmtTheory,
) -> SmtVerdict {
if contains_inductive_constructs(goal)
|| premises.iter().any(contains_inductive_constructs)
{
return SmtVerdict::Unknown;
}
let mut session = VerificationSession::new();
let goal_expr = match build_smt_problem(premises, Some(goal), &mut session, theory) {
Some(g) => g,
None => return SmtVerdict::Unknown,
};
match session.verify(&goal_expr) {
Ok(()) => SmtVerdict::Entailed,
Err(e) => match e.kind {
VerificationErrorKind::ContradictoryAssertion => SmtVerdict::NotEntailed,
_ => SmtVerdict::Unknown,
},
}
}
pub fn oracle_consistent(premises: &[ProofExpr]) -> SmtConsistency {
oracle_consistent_with_theory(premises, &SmtTheory::default())
}
pub fn oracle_consistent_with_theory(
premises: &[ProofExpr],
theory: &SmtTheory,
) -> SmtConsistency {
if premises.iter().any(contains_inductive_constructs) {
return SmtConsistency::Unknown;
}
let mut session = VerificationSession::new();
if build_smt_problem(premises, None, &mut session, theory).is_none() {
return SmtConsistency::Unknown;
}
match session.check_sat() {
Ok(true) => SmtConsistency::Consistent,
Ok(false) => SmtConsistency::Inconsistent,
Err(_) => SmtConsistency::Unknown,
}
}
fn contains_sum_term(expr: &ProofExpr) -> bool {
fn in_term(term: &ProofTerm) -> bool {
match term {
ProofTerm::Function(name, args) => {
name == "sum" || args.iter().any(in_term)
}
ProofTerm::Group(terms) => terms.len() > 1 || terms.iter().any(in_term),
_ => false,
}
}
fn walk(expr: &ProofExpr, found: &mut bool) {
if *found {
return;
}
match expr {
ProofExpr::Predicate { args, .. } => *found = args.iter().any(in_term),
ProofExpr::Identity(l, r) => *found = in_term(l) || in_term(r),
ProofExpr::NeoEvent { roles, .. } => {
*found = roles.iter().any(|(_, t)| in_term(t))
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
walk(l, found);
walk(r, found);
}
ProofExpr::Not(i) => walk(i, found),
ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
walk(body, found)
}
ProofExpr::Modal { body, .. } | ProofExpr::Temporal { body, .. } => {
walk(body, found)
}
ProofExpr::Counterfactual {
antecedent,
consequent,
} => {
walk(antecedent, found);
walk(consequent, found);
}
_ => {}
}
}
let mut found = false;
walk(expr, &mut found);
found
}
pub fn predicate_names(exprs: &[ProofExpr]) -> std::collections::BTreeSet<String> {
let mut out = std::collections::BTreeSet::new();
for expr in exprs {
collect_predicate_names(expr, &mut out);
}
out
}
fn collect_predicate_names(expr: &ProofExpr, out: &mut std::collections::BTreeSet<String>) {
match expr {
ProofExpr::Predicate { name, .. } => {
out.insert(name.clone());
}
ProofExpr::NeoEvent { verb, .. } => {
out.insert(verb.to_lowercase());
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
collect_predicate_names(l, out);
collect_predicate_names(r, out);
}
ProofExpr::Not(i) => collect_predicate_names(i, out),
ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
collect_predicate_names(body, out)
}
ProofExpr::Modal { body, .. } | ProofExpr::Temporal { body, .. } => {
collect_predicate_names(body, out)
}
ProofExpr::Counterfactual {
antecedent,
consequent,
} => {
collect_predicate_names(antecedent, out);
collect_predicate_names(consequent, out);
}
_ => {}
}
}
fn ground_sum_universe(
premises: &[ProofExpr],
goal: Option<&ProofExpr>,
theory: &SmtTheory,
) -> Vec<VerifyExpr> {
fn leaves(term: &ProofTerm, out: &mut Vec<ProofTerm>) {
match term {
ProofTerm::Function(_, args) | ProofTerm::Group(args) => {
for arg in args {
leaves(arg, out);
}
}
other => {
if !out.contains(other) {
out.push(other.clone());
}
}
}
}
fn walk(expr: &ProofExpr, theory: &SmtTheory, out: &mut Vec<ProofTerm>) {
match expr {
ProofExpr::Predicate { name, args, .. } => {
let relevant = theory.cumulative_predicates.contains(name)
|| args.iter().any(|t| {
matches!(t, ProofTerm::Function(n, _) if n == "sum")
|| matches!(t, ProofTerm::Group(g) if g.len() > 1)
});
if relevant {
for arg in args {
leaves(arg, out);
}
}
}
ProofExpr::Identity(l, r) => {
let relevant = [l, r].iter().any(|t| {
matches!(t, ProofTerm::Function(n, _) if n == "sum")
|| matches!(t, ProofTerm::Group(g) if g.len() > 1)
});
if relevant {
leaves(l, out);
leaves(r, out);
}
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
walk(l, theory, out);
walk(r, theory, out);
}
ProofExpr::Not(i) => walk(i, theory, out),
ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
walk(body, theory, out)
}
ProofExpr::Modal { body, .. } | ProofExpr::Temporal { body, .. } => {
walk(body, theory, out)
}
ProofExpr::Counterfactual {
antecedent,
consequent,
} => {
walk(antecedent, theory, out);
walk(consequent, theory, out);
}
ProofExpr::NeoEvent { roles, .. } => {
for (_, term) in roles {
if matches!(term, ProofTerm::Function(n, _) if n == "sum")
|| matches!(term, ProofTerm::Group(g) if g.len() > 1)
{
leaves(term, out);
}
}
}
_ => {}
}
}
let mut terms = Vec::new();
for premise in premises {
walk(premise, theory, &mut terms);
}
if let Some(g) = goal {
walk(g, theory, &mut terms);
}
terms.truncate(6);
terms
.iter()
.filter_map(proof_term_to_verify_expr)
.collect()
}
fn lattice_axioms(
premises: &[ProofExpr],
goal: Option<&ProofExpr>,
theory: &SmtTheory,
) -> Vec<VerifyExpr> {
let mut axioms = Vec::new();
let has_sum = premises.iter().any(contains_sum_term)
|| goal.map(contains_sum_term).unwrap_or(false);
let mut mentioned = std::collections::BTreeSet::new();
for premise in premises {
collect_predicate_names(premise, &mut mentioned);
}
if let Some(g) = goal {
collect_predicate_names(g, &mut mentioned);
}
if has_sum {
let universe = ground_sum_universe(premises, goal, theory);
let sum_of = |a: &VerifyExpr, b: &VerifyExpr| {
VerifyExpr::apply_int("sum", vec![a.clone(), b.clone()])
};
for a in &universe {
axioms.push(VerifyExpr::eq(sum_of(a, a), a.clone()));
for b in &universe {
axioms.push(VerifyExpr::eq(sum_of(a, b), sum_of(b, a)));
axioms.push(VerifyExpr::Iff(
Box::new(VerifyExpr::apply("Part", vec![a.clone(), b.clone()])),
Box::new(VerifyExpr::eq(sum_of(a, b), b.clone())),
));
for c in &universe {
let bc = sum_of(b, c);
axioms.push(VerifyExpr::Iff(
Box::new(VerifyExpr::apply("Part", vec![a.clone(), bc.clone()])),
Box::new(VerifyExpr::eq(sum_of(a, &bc), bc.clone())),
));
axioms.push(VerifyExpr::eq(
VerifyExpr::apply_int("sum", vec![sum_of(a, b), c.clone()]),
VerifyExpr::apply_int("sum", vec![a.clone(), bc]),
));
}
}
}
for mass in &theory.cumulative_predicates {
if !mentioned.contains(mass) {
continue;
}
for a in &universe {
for b in &universe {
axioms.push(VerifyExpr::implies(
VerifyExpr::and(
VerifyExpr::apply(mass, vec![a.clone()]),
VerifyExpr::apply(mass, vec![b.clone()]),
),
VerifyExpr::apply(mass, vec![sum_of(a, b)]),
));
}
}
}
}
for mass in &theory.cumulative_predicates {
if !mentioned.contains(mass) {
continue;
}
let mut kind = mass.clone();
if let Some(first) = kind.get_mut(0..1) {
first.make_ascii_uppercase();
}
axioms.push(VerifyExpr::forall(
vec![
("le".to_string(), VerifyType::Int),
("lx".to_string(), VerifyType::Int),
],
VerifyExpr::implies(
VerifyExpr::and(
VerifyExpr::apply(mass, vec![VerifyExpr::var("lx")]),
VerifyExpr::apply(
"Theme",
vec![VerifyExpr::var("le"), VerifyExpr::var("lx")],
),
),
VerifyExpr::apply(
"Theme",
vec![VerifyExpr::var("le"), VerifyExpr::var(&kind)],
),
),
));
}
axioms
}
fn build_smt_problem(
premises: &[ProofExpr],
goal: Option<&ProofExpr>,
session: &mut VerificationSession,
theory: &SmtTheory,
) -> Option<VerifyExpr> {
for axiom in lattice_axioms(premises, goal, theory) {
session.assume(&axiom);
}
let modal = goal.map(contains_modal_constructs).unwrap_or(false)
|| premises.iter().any(contains_modal_constructs);
let mut types = TypeInference::new();
if let Some(g) = goal {
types.infer_from_expr(g);
}
for premise in premises {
types.infer_from_expr(premise);
}
for (name, ty) in types.variables.iter() {
if modal && matches!(ty, VerifyType::Bool) {
continue;
}
session.declare(name, ty.clone());
}
if modal {
let mut translation = WorldTranslation::new();
let mut assumed = Vec::with_capacity(premises.len());
for premise in premises {
assumed.push(translation.translate(premise, "w0")?);
}
let goal_expr = match goal {
Some(g) => Some(translation.translate(g, "w0")?),
None => None,
};
for axiom in translation.finalize()? {
session.assume(&axiom);
}
for assumption in assumed {
session.assume(&assumption);
}
Some(goal_expr.unwrap_or_else(|| VerifyExpr::bool(true)))
} else {
for premise in premises {
let v = proof_expr_to_verify_expr(premise)?;
session.assume(&v);
}
match goal {
Some(g) => proof_expr_to_verify_expr(g),
None => Some(VerifyExpr::bool(true)),
}
}
}
fn contains_inductive_constructs(expr: &ProofExpr) -> bool {
match expr {
ProofExpr::Ctor { .. } => true,
ProofExpr::TypedVar { .. } => true,
ProofExpr::Match { .. } => true,
ProofExpr::Fixpoint { .. } => true,
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
contains_inductive_constructs(l) || contains_inductive_constructs(r)
}
ProofExpr::Not(inner) => contains_inductive_constructs(inner),
ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
contains_inductive_constructs(body)
}
ProofExpr::Modal { body, .. } | ProofExpr::Temporal { body, .. } => {
contains_inductive_constructs(body)
}
ProofExpr::Counterfactual { antecedent, consequent } => {
contains_inductive_constructs(antecedent)
|| contains_inductive_constructs(consequent)
}
ProofExpr::TemporalBinary { left, right, .. } => {
contains_inductive_constructs(left) || contains_inductive_constructs(right)
}
ProofExpr::NeoEvent { roles, .. } => roles
.iter()
.any(|(_, term)| contains_inductive_constructs_term(term)),
ProofExpr::Identity(l, r) => {
contains_inductive_constructs_term(l) || contains_inductive_constructs_term(r)
}
ProofExpr::Predicate { args, .. } => {
args.iter().any(contains_inductive_constructs_term)
}
_ => false,
}
}
fn contains_inductive_constructs_term(term: &ProofTerm) -> bool {
match term {
ProofTerm::Function(name, args) => {
matches!(name.as_str(), "Zero" | "Succ" | "Nil" | "Cons")
|| args.iter().any(contains_inductive_constructs_term)
}
ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) => {
v.contains(':')
}
ProofTerm::Group(terms) => terms.iter().any(contains_inductive_constructs_term),
ProofTerm::Constant(_) => false,
}
}
pub fn try_oracle(
goal: &ProofGoal,
knowledge_base: &[ProofExpr],
) -> ProofResult<Option<DerivationTree>> {
if contains_inductive_constructs(&goal.target) {
return Ok(None);
}
for kb_expr in knowledge_base {
if contains_inductive_constructs(kb_expr) {
return Ok(None);
}
}
if contains_modal_constructs(&goal.target)
|| goal.context.iter().any(contains_modal_constructs)
|| knowledge_base.iter().any(contains_modal_constructs)
{
let premises: Vec<ProofExpr> = goal
.context
.iter()
.chain(knowledge_base.iter())
.cloned()
.collect();
let mut session = VerificationSession::new();
let goal_expr = match build_smt_problem(
&premises,
Some(&goal.target),
&mut session,
&SmtTheory::default(),
) {
Some(g) => g,
None => return Ok(None),
};
return Ok(match session.verify(&goal_expr) {
Ok(()) => Some(DerivationTree::leaf(
goal.target.clone(),
InferenceRule::OracleVerification(
"Verified by Z3 (standard modal translation)".into(),
),
)),
Err(_) => None,
});
}
let mut session = VerificationSession::new();
let mut types = TypeInference::new();
types.infer_from_expr(&goal.target);
for ctx_expr in &goal.context {
types.infer_from_expr(ctx_expr);
}
for kb_expr in knowledge_base {
types.infer_from_expr(kb_expr);
}
for (name, ty) in types.variables.iter() {
session.declare(name, ty.clone());
}
for ctx_expr in &goal.context {
if let Some(verify_expr) = proof_expr_to_verify_expr(ctx_expr) {
session.assume(&verify_expr);
}
}
for kb_expr in knowledge_base {
if let Some(verify_expr) = proof_expr_to_verify_expr(kb_expr) {
session.assume(&verify_expr);
}
}
let goal_expr = match proof_expr_to_verify_expr(&goal.target) {
Some(e) => e,
None => return Ok(None), };
match session.verify(&goal_expr) {
Ok(()) => {
let tree = DerivationTree::leaf(
goal.target.clone(),
InferenceRule::OracleVerification("Verified by Z3".into()),
);
Ok(Some(tree))
}
Err(_) => {
Ok(None)
}
}
}
struct TypeInference {
variables: std::collections::HashMap<String, VerifyType>,
}
impl TypeInference {
fn new() -> Self {
Self {
variables: std::collections::HashMap::new(),
}
}
fn infer_from_expr(&mut self, expr: &ProofExpr) {
match expr {
ProofExpr::Predicate { args, .. } => {
for arg in args {
self.infer_from_term(arg, VerifyType::Int);
}
}
ProofExpr::Identity(left, right) => {
self.infer_from_term(left, VerifyType::Int);
self.infer_from_term(right, VerifyType::Int);
}
ProofExpr::Atom(name) => {
self.variables.insert(name.clone(), VerifyType::Bool);
}
ProofExpr::And(left, right)
| ProofExpr::Or(left, right)
| ProofExpr::Implies(left, right)
| ProofExpr::Iff(left, right) => {
self.infer_from_expr(left);
self.infer_from_expr(right);
}
ProofExpr::Not(inner) => {
self.infer_from_expr(inner);
}
ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
self.infer_from_expr(body);
}
_ => {}
}
}
fn infer_from_term(&mut self, term: &ProofTerm, context_type: VerifyType) {
match term {
ProofTerm::Variable(name) | ProofTerm::BoundVarRef(name) => {
if !self.variables.contains_key(name) {
self.variables.insert(name.clone(), context_type);
}
}
ProofTerm::Function(_, args) => {
for arg in args {
self.infer_from_term(arg, VerifyType::Int);
}
}
ProofTerm::Group(terms) => {
for t in terms {
self.infer_from_term(t, VerifyType::Int);
}
}
ProofTerm::Constant(_) => {
}
}
}
}
pub fn proof_expr_to_verify_expr(expr: &ProofExpr) -> Option<VerifyExpr> {
match expr {
ProofExpr::Atom(name) => Some(VerifyExpr::var(name)),
ProofExpr::Predicate { name, args, .. } => {
if args.len() == 2 {
let left = proof_term_to_verify_expr(&args[0])?;
let right = proof_term_to_verify_expr(&args[1])?;
match name.as_str() {
"Gt" => return Some(VerifyExpr::gt(left, right)),
"Lt" => return Some(VerifyExpr::lt(left, right)),
"Gte" => return Some(VerifyExpr::gte(left, right)),
"Lte" => return Some(VerifyExpr::lte(left, right)),
"Eq" => return Some(VerifyExpr::eq(left, right)),
"Neq" => return Some(VerifyExpr::neq(left, right)),
_ => {}
}
}
let verify_args: Vec<VerifyExpr> = args
.iter()
.filter_map(proof_term_to_verify_expr)
.collect();
Some(VerifyExpr::apply(name, verify_args))
}
ProofExpr::Identity(left, right) => {
let l = proof_term_to_verify_expr(left)?;
let r = proof_term_to_verify_expr(right)?;
Some(VerifyExpr::eq(l, r))
}
ProofExpr::And(left, right) => {
let l = proof_expr_to_verify_expr(left)?;
let r = proof_expr_to_verify_expr(right)?;
Some(VerifyExpr::and(l, r))
}
ProofExpr::Or(left, right) => {
let l = proof_expr_to_verify_expr(left)?;
let r = proof_expr_to_verify_expr(right)?;
Some(VerifyExpr::or(l, r))
}
ProofExpr::Implies(left, right) => {
let l = proof_expr_to_verify_expr(left)?;
let r = proof_expr_to_verify_expr(right)?;
Some(VerifyExpr::implies(l, r))
}
ProofExpr::Iff(left, right) => {
let l = proof_expr_to_verify_expr(left)?;
let r = proof_expr_to_verify_expr(right)?;
Some(VerifyExpr::and(
VerifyExpr::implies(l.clone(), r.clone()),
VerifyExpr::implies(r, l),
))
}
ProofExpr::Not(inner) => {
let i = proof_expr_to_verify_expr(inner)?;
Some(VerifyExpr::not(i))
}
ProofExpr::ForAll { variable, body } => {
let b = proof_expr_to_verify_expr(body)?;
Some(VerifyExpr::forall(
vec![(variable.clone(), VerifyType::Int)],
b,
))
}
ProofExpr::Exists { variable, body } => {
let b = proof_expr_to_verify_expr(body)?;
Some(VerifyExpr::exists(
vec![(variable.clone(), VerifyType::Int)],
b,
))
}
ProofExpr::Modal { .. }
| ProofExpr::Counterfactual { .. }
| ProofExpr::Temporal { .. }
| ProofExpr::TemporalBinary { .. } => None,
ProofExpr::Ctor { .. }
| ProofExpr::Match { .. }
| ProofExpr::Fixpoint { .. }
| ProofExpr::TypedVar { .. } => None,
ProofExpr::NeoEvent {
event_var,
verb,
roles,
} => {
let mut body = VerifyExpr::apply(verb, vec![VerifyExpr::var(event_var)]);
for (role, term) in roles {
let t = proof_term_to_verify_expr(term)?;
body = VerifyExpr::and(
body,
VerifyExpr::apply(role, vec![VerifyExpr::var(event_var), t]),
);
}
Some(VerifyExpr::exists(
vec![(event_var.clone(), VerifyType::Int)],
body,
))
}
ProofExpr::Lambda { .. }
| ProofExpr::App(_, _)
| ProofExpr::Hole(_)
| ProofExpr::Term(_)
| ProofExpr::Unsupported(_) => None,
}
}
pub fn proof_term_to_verify_expr(term: &ProofTerm) -> Option<VerifyExpr> {
match term {
ProofTerm::Constant(s) => {
if let Ok(n) = s.parse::<i64>() {
Some(VerifyExpr::int(n))
} else {
Some(VerifyExpr::var(s))
}
}
ProofTerm::Variable(name) | ProofTerm::BoundVarRef(name) => Some(VerifyExpr::var(name)),
ProofTerm::Function(name, args) => {
if args.len() == 2 {
let left = proof_term_to_verify_expr(&args[0])?;
let right = proof_term_to_verify_expr(&args[1])?;
match name.as_str() {
"Add" => {
return Some(VerifyExpr::binary(VerifyOp::Add, left, right))
}
"Sub" => {
return Some(VerifyExpr::binary(VerifyOp::Sub, left, right))
}
"Mul" => {
return Some(VerifyExpr::binary(VerifyOp::Mul, left, right))
}
"Div" => {
return Some(VerifyExpr::binary(VerifyOp::Div, left, right))
}
_ => {}
}
}
let verify_args: Vec<VerifyExpr> = args
.iter()
.filter_map(proof_term_to_verify_expr)
.collect();
Some(VerifyExpr::apply_int(name, verify_args))
}
ProofTerm::Group(terms) => {
let mut converted = terms.iter().filter_map(proof_term_to_verify_expr);
let first = converted.next()?;
Some(converted.fold(first, |acc, t| {
VerifyExpr::apply_int("sum", vec![acc, t])
}))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_convert_atom() {
let expr = ProofExpr::Atom("P".into());
let result = proof_expr_to_verify_expr(&expr);
assert!(matches!(result, Some(VerifyExpr::Var(s)) if s == "P"));
}
#[test]
fn test_convert_gt_predicate() {
let expr = ProofExpr::Predicate {
name: "Gt".into(),
args: vec![
ProofTerm::Variable("x".into()),
ProofTerm::Constant("10".into()),
],
world: None,
};
let result = proof_expr_to_verify_expr(&expr);
assert!(matches!(
result,
Some(VerifyExpr::Binary {
op: VerifyOp::Gt,
..
})
));
}
#[test]
fn test_convert_implication() {
let expr = ProofExpr::Implies(
Box::new(ProofExpr::Atom("P".into())),
Box::new(ProofExpr::Atom("Q".into())),
);
let result = proof_expr_to_verify_expr(&expr);
assert!(matches!(
result,
Some(VerifyExpr::Binary {
op: VerifyOp::Implies,
..
})
));
}
#[test]
fn test_convert_arithmetic_function() {
let term = ProofTerm::Function(
"Add".into(),
vec![
ProofTerm::Variable("x".into()),
ProofTerm::Constant("5".into()),
],
);
let result = proof_term_to_verify_expr(&term);
assert!(matches!(
result,
Some(VerifyExpr::Binary {
op: VerifyOp::Add,
..
})
));
}
}