use logicaffeine_language::proof_convert::DefaultRule;
use logicaffeine_proof::oracle::{
oracle_consistent_with_theory, oracle_entails_with_theory, SmtConsistency, SmtTheory,
SmtVerdict,
};
use logicaffeine_proof::{ProofExpr, ProofTerm};
fn ground_constants(exprs: &[ProofExpr]) -> Vec<String> {
fn in_term(term: &ProofTerm, out: &mut Vec<String>) {
match term {
ProofTerm::Constant(c) => {
if c.parse::<i64>().is_err() && !out.contains(c) {
out.push(c.clone());
}
}
ProofTerm::Function(_, args) | ProofTerm::Group(args) => {
for arg in args {
in_term(arg, out);
}
}
_ => {}
}
}
fn walk(expr: &ProofExpr, out: &mut Vec<String>) {
match expr {
ProofExpr::Predicate { args, .. } => {
for arg in args {
in_term(arg, out);
}
}
ProofExpr::Identity(l, r) => {
in_term(l, out);
in_term(r, out);
}
ProofExpr::NeoEvent { roles, .. } => {
for (_, term) in roles {
in_term(term, out);
}
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
walk(l, out);
walk(r, out);
}
ProofExpr::Not(i) => walk(i, out),
ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => walk(body, out),
ProofExpr::Modal { body, .. } | ProofExpr::Temporal { body, .. } => walk(body, out),
ProofExpr::Counterfactual {
antecedent,
consequent,
} => {
walk(antecedent, out);
walk(consequent, out);
}
_ => {}
}
}
let mut out = Vec::new();
for expr in exprs {
walk(expr, &mut out);
}
out
}
fn specificity_order(defaults: &[DefaultRule], premises: &[ProofExpr]) -> Vec<usize> {
fn implication_edge(expr: &ProofExpr) -> Option<(String, String)> {
if let ProofExpr::ForAll { body, .. } = expr {
if let ProofExpr::Implies(lhs, rhs) = body.as_ref() {
let from = match lhs.as_ref() {
ProofExpr::Predicate { name, .. } => Some(name.clone()),
ProofExpr::And(l, _) => match l.as_ref() {
ProofExpr::Predicate { name, .. } => Some(name.clone()),
_ => None,
},
_ => None,
};
let to = match rhs.as_ref() {
ProofExpr::Predicate { name, .. } => Some(name.clone()),
_ => None,
};
if let (Some(f), Some(t)) = (from, to) {
return Some((f, t));
}
}
}
None
}
let edges: Vec<(String, String)> = premises.iter().filter_map(implication_edge).collect();
let reaches = |from: &str, to: &str| -> bool {
let mut frontier = vec![from.to_string()];
let mut seen = vec![from.to_string()];
while let Some(cur) = frontier.pop() {
for (f, t) in &edges {
if *f == cur && !seen.contains(t) {
if t == to {
return true;
}
seen.push(t.clone());
frontier.push(t.clone());
}
}
}
false
};
let mut order: Vec<usize> = (0..defaults.len()).collect();
order.sort_by(|&i, &j| {
match (&defaults[i].restriction_pred, &defaults[j].restriction_pred) {
(Some(ri), Some(rj)) => {
let i_specific = reaches(ri, rj);
let j_specific = reaches(rj, ri);
match (i_specific, j_specific) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => defaults[i].ab_name.cmp(&defaults[j].ab_name),
}
}
_ => defaults[i].ab_name.cmp(&defaults[j].ab_name),
}
});
order
}
fn circumscribe(
premises: &[ProofExpr],
goal: Option<&ProofExpr>,
defaults: &[DefaultRule],
theory: &SmtTheory,
) -> Vec<ProofExpr> {
let mut scope: Vec<ProofExpr> = premises.to_vec();
if defaults.is_empty() {
return scope;
}
let mut universe_src: Vec<ProofExpr> = premises.to_vec();
if let Some(g) = goal {
universe_src.push(g.clone());
}
let constants = ground_constants(&universe_src);
for &idx in &specificity_order(defaults, premises) {
let rule = &defaults[idx];
let candidates: Vec<ProofExpr> = if rule.unary {
constants
.iter()
.map(|c| {
ProofExpr::Not(Box::new(ProofExpr::Predicate {
name: rule.ab_name.clone(),
args: vec![ProofTerm::Constant(c.clone())],
world: None,
}))
})
.collect()
} else {
vec![ProofExpr::Not(Box::new(ProofExpr::Atom(
rule.ab_name.clone(),
)))]
};
for candidate in candidates {
let mut attempt = scope.clone();
attempt.push(candidate);
if matches!(
oracle_consistent_with_theory(&attempt, theory),
SmtConsistency::Consistent
) {
scope = attempt;
}
}
}
scope
}
pub fn defeasible_entails(
premises: &[ProofExpr],
goal: &ProofExpr,
defaults: &[DefaultRule],
theory: &SmtTheory,
) -> SmtVerdict {
let scope = circumscribe(premises, Some(goal), defaults, theory);
oracle_entails_with_theory(&scope, goal, theory)
}
pub fn defeasible_consistent(
premises: &[ProofExpr],
defaults: &[DefaultRule],
theory: &SmtTheory,
) -> SmtConsistency {
let scope = circumscribe(premises, None, defaults, theory);
oracle_consistent_with_theory(&scope, theory)
}