use std::collections::{HashMap, HashSet};
use logicaffeine_kernel::{
double_check, infer_type, is_subtype, prelude::StandardLibrary, Context, DoubleCheck, Term,
Universe,
};
use crate::certifier::{certify, proof_expr_to_type, proof_expr_to_type_ctx, CertificationContext};
use crate::{BackwardChainer, DerivationTree, ProofExpr, ProofTerm};
pub struct VerifiedProof {
pub derivation: Option<DerivationTree>,
pub proof_term: Option<Term>,
pub kernel_ctx: Context,
pub verified: bool,
pub verification_error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Definition {
pub name: String,
pub params: Vec<String>,
pub definiens: ProofExpr,
}
pub fn prove_certify_check(premises: &[ProofExpr], goal: &ProofExpr) -> VerifiedProof {
prove_certify_check_bounded(premises, goal, 100)
}
#[derive(Debug, Clone, PartialEq)]
pub struct LibraryTheorem {
pub name: String,
pub premises: Vec<ProofExpr>,
pub goal: ProofExpr,
pub cites: Vec<String>,
}
pub struct LibraryResult {
pub name: String,
pub verified: bool,
pub verification_error: Option<String>,
}
pub(crate) fn citation_order(theorems: &[LibraryTheorem]) -> Vec<usize> {
use std::collections::HashSet;
let known: HashSet<&str> = theorems.iter().map(|t| t.name.as_str()).collect();
let mut placed: HashSet<usize> = HashSet::new();
let mut placed_names: HashSet<&str> = HashSet::new();
let mut order = Vec::with_capacity(theorems.len());
loop {
let mut progressed = false;
for (i, t) in theorems.iter().enumerate() {
if placed.contains(&i) {
continue;
}
let ready = t
.cites
.iter()
.all(|c| !known.contains(c.as_str()) || placed_names.contains(c.as_str()));
if ready {
order.push(i);
placed.insert(i);
placed_names.insert(t.name.as_str());
progressed = true;
}
}
if !progressed {
break;
}
}
for i in 0..theorems.len() {
if !placed.contains(&i) {
order.push(i);
}
}
order
}
pub fn prove_library(theorems: &[LibraryTheorem]) -> Vec<LibraryResult> {
prove_library_with_axioms(&[], theorems)
}
pub fn prove_library_with_axioms(
axioms: &[ProofExpr],
theorems: &[LibraryTheorem],
) -> Vec<LibraryResult> {
use std::collections::HashMap;
let mut proved: HashMap<&str, &ProofExpr> = HashMap::new();
let mut by_name: HashMap<&str, LibraryResult> = HashMap::new();
for &i in &citation_order(theorems) {
let t = &theorems[i];
let mut premises = axioms.to_vec();
premises.extend(t.premises.iter().cloned());
for cite in &t.cites {
if let Some(goal) = proved.get(cite.as_str()) {
premises.push((*goal).clone());
}
}
let vp = prove_certify_check(&premises, &t.goal);
if vp.verified {
proved.insert(t.name.as_str(), &t.goal);
}
by_name.insert(
t.name.as_str(),
LibraryResult {
name: t.name.clone(),
verified: vp.verified,
verification_error: vp.verification_error,
},
);
}
theorems
.iter()
.map(|t| by_name.remove(t.name.as_str()).expect("every theorem produces a result"))
.collect()
}
pub fn prove_certify_check_with_defs(
premises: &[ProofExpr],
goal: &ProofExpr,
definitions: &[Definition],
) -> VerifiedProof {
prove_certify_check_bounded_with_defs(premises, goal, definitions, 100)
}
pub fn prove_certify_check_bounded(
premises: &[ProofExpr],
goal: &ProofExpr,
max_depth: usize,
) -> VerifiedProof {
prove_certify_check_bounded_with_defs(premises, goal, &[], max_depth)
}
pub fn prove_certify_check_bounded_with_defs(
premises: &[ProofExpr],
goal: &ProofExpr,
definitions: &[Definition],
max_depth: usize,
) -> VerifiedProof {
on_big_stack(|| {
prove_certify_check_bounded_with_defs_inner(premises, goal, definitions, max_depth)
})
}
fn prove_certify_check_bounded_with_defs_inner(
premises: &[ProofExpr],
goal: &ProofExpr,
definitions: &[Definition],
max_depth: usize,
) -> VerifiedProof {
let abstracted = abstract_definitions(definitions);
let definitions = &abstracted[..];
if let Err(e) = validate_definitions(definitions) {
return definition_error(e);
}
let expanded: Option<(Vec<ProofExpr>, ProofExpr)> = if definitions.is_empty() {
None
} else {
let defmap: HashMap<&str, &Definition> =
definitions.iter().map(|d| (d.name.as_str(), d)).collect();
let ep = premises
.iter()
.map(|p| expand_defs_in_expr(p, &defmap, 0))
.collect();
let eg = expand_defs_in_expr(goal, &defmap, 0);
Some((ep, eg))
};
let (search_premises, search_goal): (&[ProofExpr], &ProofExpr) = match &expanded {
Some((ep, eg)) => (ep, eg),
None => (premises, goal),
};
let (kernel_ctx, flat_premises, prepared_goal) =
prepare_ctx_with_defs(search_premises, search_goal, definitions);
let abstracted_goal = if expanded.is_none() {
prepared_goal
} else {
BackwardChainer::new().abstract_all_events(goal)
};
let mut engine = BackwardChainer::new();
engine.set_max_depth(max_depth);
for premise in &flat_premises {
engine.add_axiom(premise.clone());
}
let search_goal = engine.abstract_all_events(search_goal);
let derivation = match engine.prove(search_goal) {
Ok(d) => d,
Err(e) => {
return VerifiedProof {
derivation: None,
proof_term: None,
kernel_ctx,
verified: false,
verification_error: Some(format!("Proof search failed: {}", e)),
};
}
};
finish_check(kernel_ctx, &abstracted_goal, derivation)
}
pub fn check_derivation(
premises: &[ProofExpr],
goal: &ProofExpr,
derivation: DerivationTree,
) -> VerifiedProof {
check_derivation_with_defs(premises, goal, &[], derivation)
}
pub fn check_derivation_with_defs(
premises: &[ProofExpr],
goal: &ProofExpr,
definitions: &[Definition],
derivation: DerivationTree,
) -> VerifiedProof {
on_big_stack(|| check_derivation_with_defs_inner(premises, goal, definitions, derivation))
}
fn check_derivation_with_defs_inner(
premises: &[ProofExpr],
goal: &ProofExpr,
definitions: &[Definition],
derivation: DerivationTree,
) -> VerifiedProof {
let abstracted = abstract_definitions(definitions);
let definitions = &abstracted[..];
if let Err(e) = validate_definitions(definitions) {
return definition_error(e);
}
let (kernel_ctx, _flat_premises, abstracted_goal) =
prepare_ctx_with_defs(premises, goal, definitions);
finish_check(kernel_ctx, &abstracted_goal, derivation)
}
fn prepare_ctx_with_defs(
premises: &[ProofExpr],
goal: &ProofExpr,
definitions: &[Definition],
) -> (Context, Vec<ProofExpr>, ProofExpr) {
let mut kernel_ctx = Context::new();
StandardLibrary::register(&mut kernel_ctx);
for def in definitions {
register_definition(&mut kernel_ctx, def);
}
fn split_conjuncts(expr: &ProofExpr, out: &mut Vec<ProofExpr>) {
if let ProofExpr::And(l, r) = expr {
split_conjuncts(l, out);
split_conjuncts(r, out);
} else {
out.push(expr.clone());
}
}
let engine_for_abstraction = BackwardChainer::new();
let mut flat_premises = Vec::new();
for premise in premises {
let abstracted = engine_for_abstraction.abstract_all_events(premise);
split_conjuncts(&expand_iff(&abstracted), &mut flat_premises);
}
let abstracted_goal = engine_for_abstraction.abstract_all_events(goal);
let mut collector = SymbolCollector::new();
for premise in &flat_premises {
collector.collect(premise);
}
collector.collect(&abstracted_goal);
for def in definitions {
collector.collect(&def.definiens);
}
for (name, ind) in collector.inductive_predicates() {
if kernel_ctx.get_global(name).is_none() {
kernel_ctx.add_declaration(
name,
Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Global(ind.to_string())),
body_type: Box::new(Term::Sort(Universe::Prop)),
},
);
}
}
for (name, arity) in collector.predicates() {
register_predicate(&mut kernel_ctx, name, arity);
}
for (name, arity) in collector.functions() {
register_function(&mut kernel_ctx, name, arity);
}
for name in collector.modalities() {
register_modality(&mut kernel_ctx, name);
}
for name in collector.int_atoms() {
if kernel_ctx.get_global(name).is_none() {
kernel_ctx.add_declaration(name, Term::Global("Int".to_string()));
}
}
let param_names: HashSet<&str> = definitions
.iter()
.flat_map(|d| d.params.iter().map(String::as_str))
.collect();
for name in collector.constants() {
if param_names.contains(name.as_str()) {
continue;
}
register_constant(&mut kernel_ctx, name);
}
for (i, premise) in flat_premises.iter().enumerate() {
if let Ok(hyp_type) = proof_expr_to_type_ctx(premise, &kernel_ctx) {
let hyp_name = format!("h{}", i + 1);
kernel_ctx.add_declaration(&hyp_name, hyp_type);
}
}
for (i, premise) in premises.iter().enumerate() {
let whole = expand_iff(&engine_for_abstraction.abstract_all_events(premise));
if matches!(whole, ProofExpr::And(_, _)) {
if let Ok(hyp_type) = proof_expr_to_type(&whole) {
kernel_ctx.add_declaration(&format!("hw{}", i + 1), hyp_type);
}
}
}
(kernel_ctx, flat_premises, abstracted_goal)
}
pub(crate) fn on_big_stack<T: Send>(f: impl FnOnce() -> T + Send) -> T {
#[cfg(target_arch = "wasm32")]
{
f()
}
#[cfg(not(target_arch = "wasm32"))]
{
std::thread::scope(|s| {
std::thread::Builder::new()
.stack_size(32 * 1024 * 1024)
.spawn_scoped(s, f)
.expect("spawn proof thread")
.join()
.expect("proof thread panicked")
})
}
}
fn finish_check(
kernel_ctx: Context,
abstracted_goal: &ProofExpr,
derivation: DerivationTree,
) -> VerifiedProof {
let trace = std::env::var("LOGOS_TRACE").is_ok();
let t_cert = trace.then(std::time::Instant::now);
let proof_term = {
let cert_ctx = CertificationContext::new(&kernel_ctx);
match certify(&derivation, &cert_ctx) {
Ok(t) => t,
Err(e) => {
return VerifiedProof {
derivation: Some(derivation),
proof_term: None,
kernel_ctx,
verified: false,
verification_error: Some(format!("Certification failed: {}", e)),
};
}
}
};
if let Some(t_cert) = t_cert {
eprintln!(
"[cert] certify(build) {:.2?} → {} kernel-term nodes",
t_cert.elapsed(),
count_term_nodes(&proof_term)
);
}
let t_infer = trace.then(std::time::Instant::now);
let inferred = match infer_type(&kernel_ctx, &proof_term) {
Ok(t) => t,
Err(e) => {
return VerifiedProof {
derivation: Some(derivation),
proof_term: None,
kernel_ctx,
verified: false,
verification_error: Some(format!("Type check failed: {}", e)),
};
}
};
if let Some(t_infer) = t_infer {
eprintln!("[cert] infer_type(check) {:.2?}", t_infer.elapsed());
}
let goal_type = match proof_expr_to_type_ctx(abstracted_goal, &kernel_ctx) {
Ok(t) => t,
Err(e) => {
return VerifiedProof {
derivation: Some(derivation),
proof_term: None,
kernel_ctx,
verified: false,
verification_error: Some(format!(
"Cannot express the goal as a kernel type: {}",
e
)),
};
}
};
if !is_subtype(&kernel_ctx, &inferred, &goal_type) {
return VerifiedProof {
derivation: Some(derivation),
proof_term: None,
kernel_ctx,
verified: false,
verification_error: Some(format!(
"Proof term proves a different proposition: inferred {:?}, goal {:?}",
inferred, goal_type
)),
};
}
if let DoubleCheck::Disagree(why) = double_check(&kernel_ctx, &proof_term) {
return VerifiedProof {
derivation: Some(derivation),
proof_term: None,
kernel_ctx,
verified: false,
verification_error: Some(format!("Independent re-checker disagreement: {}", why)),
};
}
VerifiedProof {
derivation: Some(derivation),
proof_term: Some(proof_term),
kernel_ctx,
verified: true,
verification_error: None,
}
}
pub struct ConflictReport {
pub inconsistent: bool,
pub proof_term: Option<Term>,
pub kernel_ctx: Context,
pub conflicting_premises: Vec<usize>,
pub error: Option<String>,
}
pub fn detect_conflict(premises: &[ProofExpr]) -> ConflictReport {
let falsum = ProofExpr::Atom("⊥".to_string());
let outcome = prove_certify_check(premises, &falsum);
if !outcome.verified {
return ConflictReport {
inconsistent: false,
proof_term: None,
kernel_ctx: outcome.kernel_ctx,
conflicting_premises: Vec::new(),
error: outcome.verification_error,
};
}
let mut used: Vec<usize> = Vec::new();
if let Some(derivation) = &outcome.derivation {
let mut leaves: Vec<&ProofExpr> = Vec::new();
collect_premise_leaves(derivation, &mut leaves);
for (i, premise) in premises.iter().enumerate() {
if leaves.iter().any(|leaf| *leaf == premise) && !used.contains(&i) {
used.push(i);
}
}
}
ConflictReport {
inconsistent: true,
proof_term: outcome.proof_term,
kernel_ctx: outcome.kernel_ctx,
conflicting_premises: used,
error: None,
}
}
fn collect_premise_leaves<'a>(tree: &'a DerivationTree, out: &mut Vec<&'a ProofExpr>) {
if matches!(tree.rule, crate::InferenceRule::PremiseMatch) {
out.push(&tree.conclusion);
}
for premise in &tree.premises {
collect_premise_leaves(premise, out);
}
}
struct SymbolCollector {
predicates: HashMap<String, usize>,
functions: HashMap<String, usize>,
constants: HashSet<String>,
weak_constants: HashSet<String>,
variables: HashSet<String>,
int_atoms: HashSet<String>,
inductive_predicates: HashMap<String, &'static str>,
modalities: HashSet<String>,
}
impl SymbolCollector {
fn new() -> Self {
SymbolCollector {
predicates: HashMap::new(),
functions: HashMap::new(),
constants: HashSet::new(),
weak_constants: HashSet::new(),
variables: HashSet::new(),
int_atoms: HashSet::new(),
inductive_predicates: HashMap::new(),
modalities: HashSet::new(),
}
}
fn mark_int(&mut self, term: &ProofTerm) {
match term {
ProofTerm::Constant(s) if s.parse::<i64>().is_ok() => {}
ProofTerm::Constant(s) | ProofTerm::Variable(s) | ProofTerm::BoundVarRef(s) => {
self.int_atoms.insert(s.clone());
}
ProofTerm::Function(name, args)
if matches!(name.as_str(), "add" | "sub" | "mul" | "div" | "mod") =>
{
for a in args {
self.mark_int(a);
}
}
_ => {}
}
}
fn note_predicate(&mut self, name: &str, arity: usize) {
self.predicates
.entry(name.to_string())
.and_modify(|a| *a = (*a).max(arity))
.or_insert(arity);
}
fn note_function(&mut self, name: &str, arity: usize) {
self.functions
.entry(name.to_string())
.and_modify(|a| *a = (*a).max(arity))
.or_insert(arity);
}
fn collect(&mut self, expr: &ProofExpr) {
match expr {
ProofExpr::Predicate { name, args, .. } => {
self.note_predicate(name, args.len());
if args.len() == 1 {
if let Some(ind) = constructor_domain(&args[0]) {
self.inductive_predicates.insert(name.clone(), ind);
}
}
for arg in args {
self.collect_term(arg);
}
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
self.collect(l);
self.collect(r);
}
ProofExpr::Not(inner) => self.collect(inner),
ProofExpr::ForAll { variable, body } | ProofExpr::Exists { variable, body } => {
self.variables.insert(variable.clone());
self.collect(body);
}
ProofExpr::Temporal { operator, body } => {
self.modalities.insert(operator.clone());
self.collect(body);
}
ProofExpr::Identity(l, r) => {
self.collect_term(l);
self.collect_term(r);
}
_ => {}
}
}
fn collect_term(&mut self, term: &ProofTerm) {
match term {
ProofTerm::Constant(name) => {
if name
.chars()
.next()
.map(|c| c.is_uppercase() || c.is_ascii_digit())
.unwrap_or(false)
{
self.constants.insert(name.clone());
} else {
self.weak_constants.insert(name.clone());
}
}
ProofTerm::Variable(name) | ProofTerm::BoundVarRef(name) => {
self.variables.insert(name.clone());
}
ProofTerm::Function(name, args) => {
if matches!(
name.as_str(),
"le" | "lt" | "ge" | "gt" | "add" | "sub" | "mul" | "div" | "mod"
) {
for arg in args {
self.mark_int(arg);
}
} else {
self.note_function(name, args.len());
}
for arg in args {
self.collect_term(arg);
}
}
_ => {}
}
}
fn predicates(&self) -> impl Iterator<Item = (&String, usize)> {
self.predicates.iter().map(|(n, a)| (n, *a))
}
fn functions(&self) -> impl Iterator<Item = (&String, usize)> {
self.functions.iter().map(|(n, a)| (n, *a))
}
fn int_atoms(&self) -> impl Iterator<Item = &String> {
self.int_atoms.iter()
}
fn inductive_predicates(&self) -> impl Iterator<Item = (&String, &'static str)> {
self.inductive_predicates.iter().map(|(n, d)| (n, *d))
}
fn constants(&self) -> Vec<&String> {
self.constants
.iter()
.chain(
self.weak_constants
.iter()
.filter(|n| !self.variables.contains(*n)),
)
.collect()
}
fn modalities(&self) -> impl Iterator<Item = &String> {
self.modalities.iter()
}
}
fn constructor_domain(t: &ProofTerm) -> Option<&'static str> {
let name = match t {
ProofTerm::Constant(s) => s.as_str(),
ProofTerm::Function(n, _) => n.as_str(),
_ => return None,
};
match name {
"Zero" | "Succ" => Some("Nat"),
"ENil" | "ECons" => Some("EList"),
_ => None,
}
}
#[doc(hidden)]
fn count_term_nodes(t: &Term) -> usize {
match t {
Term::App(f, a) => 1 + count_term_nodes(f) + count_term_nodes(a),
Term::Pi { param_type, body_type, .. } => 1 + count_term_nodes(param_type) + count_term_nodes(body_type),
Term::Lambda { param_type, body, .. } => 1 + count_term_nodes(param_type) + count_term_nodes(body),
Term::Match { discriminant, motive, cases } => {
1 + count_term_nodes(discriminant) + count_term_nodes(motive)
+ cases.iter().map(count_term_nodes).sum::<usize>()
}
Term::Fix { body, .. } => 1 + count_term_nodes(body),
_ => 1,
}
}
fn register_predicate(ctx: &mut Context, name: &str, arity: usize) {
if ctx.get_global(name).is_some() {
return;
}
let mut ty = Term::Sort(Universe::Prop);
for _ in 0..arity {
ty = Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Global("Entity".to_string())),
body_type: Box::new(ty),
};
}
ctx.add_declaration(name, ty);
}
fn register_function(ctx: &mut Context, name: &str, arity: usize) {
if ctx.get_global(name).is_some() {
return;
}
let mut ty = Term::Global("Entity".to_string());
for _ in 0..arity {
ty = Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Global("Entity".to_string())),
body_type: Box::new(ty),
};
}
ctx.add_declaration(name, ty);
}
fn register_constant(ctx: &mut Context, name: &str) {
if ctx.get_global(name).is_some() {
return;
}
ctx.add_declaration(name, Term::Global("Entity".to_string()));
}
fn register_modality(ctx: &mut Context, name: &str) {
if ctx.get_global(name).is_some() {
return;
}
ctx.add_declaration(
name,
Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Sort(Universe::Prop)),
body_type: Box::new(Term::Sort(Universe::Prop)),
},
);
}
fn definition_error(message: String) -> VerifiedProof {
VerifiedProof {
derivation: None,
proof_term: None,
kernel_ctx: Context::new(),
verified: false,
verification_error: Some(message),
}
}
fn abstract_definitions(definitions: &[Definition]) -> Vec<Definition> {
let abstractor = BackwardChainer::new();
definitions
.iter()
.map(|d| Definition {
name: d.name.clone(),
params: d.params.clone(),
definiens: abstractor.abstract_all_events(&d.definiens),
})
.collect()
}
fn validate_definitions(definitions: &[Definition]) -> Result<(), String> {
if definitions.is_empty() {
return Ok(());
}
for def in definitions {
if let Err(e) = proof_expr_to_type(&def.definiens) {
return Err(format!("cannot lower definition `{}`: {:?}", def.name, e));
}
}
if let Some(cycle) = find_definition_cycle(definitions) {
return Err(format!(
"circular definition among {{{}}}: a definition may not recursively refer \
to itself, directly or transitively",
cycle.join(", ")
));
}
Ok(())
}
fn register_definition(ctx: &mut Context, def: &Definition) {
if ctx.get_global(&def.name).is_some() {
return;
}
let mut body = match proof_expr_to_type(&def.definiens) {
Ok(b) => b,
Err(_) => {
register_predicate(ctx, &def.name, def.params.len());
return;
}
};
for p in &def.params {
body = subst_global_to_var(body, p);
}
for p in def.params.iter().rev() {
body = Term::Lambda {
param: p.clone(),
param_type: Box::new(Term::Global("Entity".to_string())),
body: Box::new(body),
};
}
let mut ty = Term::Sort(Universe::Prop);
for _ in &def.params {
ty = Term::Pi {
param: "_".to_string(),
param_type: Box::new(Term::Global("Entity".to_string())),
body_type: Box::new(ty),
};
}
ctx.add_definition(def.name.clone(), ty, body);
}
fn subst_global_to_var(term: Term, name: &str) -> Term {
match term {
Term::Global(n) => {
if n == name {
Term::Var(n)
} else {
Term::Global(n)
}
}
Term::App(f, a) => Term::App(
Box::new(subst_global_to_var(*f, name)),
Box::new(subst_global_to_var(*a, name)),
),
Term::Pi { param, param_type, body_type } => Term::Pi {
param,
param_type: Box::new(subst_global_to_var(*param_type, name)),
body_type: Box::new(subst_global_to_var(*body_type, name)),
},
Term::Lambda { param, param_type, body } => Term::Lambda {
param,
param_type: Box::new(subst_global_to_var(*param_type, name)),
body: Box::new(subst_global_to_var(*body, name)),
},
Term::Match { discriminant, motive, cases } => Term::Match {
discriminant: Box::new(subst_global_to_var(*discriminant, name)),
motive: Box::new(subst_global_to_var(*motive, name)),
cases: cases.into_iter().map(|c| subst_global_to_var(c, name)).collect(),
},
Term::Fix { name: fix_name, body } => Term::Fix {
name: fix_name,
body: Box::new(subst_global_to_var(*body, name)),
},
other => other,
}
}
fn collect_defined_predicates(expr: &ProofExpr, defined: &HashSet<&str>, out: &mut Vec<String>) {
match expr {
ProofExpr::Predicate { name, .. } => {
if defined.contains(name.as_str()) && !out.iter().any(|n| n == name) {
out.push(name.clone());
}
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
collect_defined_predicates(l, defined, out);
collect_defined_predicates(r, defined, out);
}
ProofExpr::Not(p) => collect_defined_predicates(p, defined, out),
ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
collect_defined_predicates(body, defined, out)
}
_ => {}
}
}
fn def_edges(definitions: &[Definition]) -> Vec<(String, Vec<String>)> {
let defined: HashSet<&str> = definitions.iter().map(|d| d.name.as_str()).collect();
definitions
.iter()
.map(|d| {
let mut uses = Vec::new();
collect_defined_predicates(&d.definiens, &defined, &mut uses);
(d.name.clone(), uses)
})
.collect()
}
fn find_definition_cycle(definitions: &[Definition]) -> Option<Vec<String>> {
let edges = def_edges(definitions);
let adj: HashMap<&str, &[String]> =
edges.iter().map(|(n, u)| (n.as_str(), u.as_slice())).collect();
let mut indeg: HashMap<&str, usize> = adj.keys().map(|n| (*n, 0usize)).collect();
for deps in adj.values() {
for d in deps.iter() {
if let Some(c) = indeg.get_mut(d.as_str()) {
*c += 1;
}
}
}
let mut queue: Vec<&str> = indeg
.iter()
.filter(|(_, &c)| c == 0)
.map(|(n, _)| *n)
.collect();
let mut removed = 0usize;
while let Some(n) = queue.pop() {
removed += 1;
if let Some(deps) = adj.get(n) {
for d in deps.iter() {
if let Some(c) = indeg.get_mut(d.as_str()) {
*c -= 1;
if *c == 0 {
queue.push(d.as_str());
}
}
}
}
}
if removed == adj.len() {
None
} else {
let mut cyclic: Vec<String> = indeg
.iter()
.filter(|(_, &c)| c > 0)
.map(|(n, _)| n.to_string())
.collect();
cyclic.sort();
Some(cyclic)
}
}
#[derive(Debug, Clone, Default)]
pub struct DependencyGraph {
pub def_uses: Vec<(String, Vec<String>)>,
pub theorem_uses: Vec<String>,
}
pub fn dependency_graph(
definitions: &[Definition],
premises: &[ProofExpr],
goal: &ProofExpr,
) -> DependencyGraph {
let defined: HashSet<&str> = definitions.iter().map(|d| d.name.as_str()).collect();
let def_uses = def_edges(definitions);
let mut theorem_uses = Vec::new();
for p in premises {
collect_defined_predicates(p, &defined, &mut theorem_uses);
}
collect_defined_predicates(goal, &defined, &mut theorem_uses);
DependencyGraph {
def_uses,
theorem_uses,
}
}
const MAX_EXPANSION_DEPTH: usize = 64;
fn expand_defs_in_expr(
expr: &ProofExpr,
defs: &HashMap<&str, &Definition>,
depth: usize,
) -> ProofExpr {
if depth >= MAX_EXPANSION_DEPTH {
return expr.clone();
}
match expr {
ProofExpr::Predicate { name, args, .. } => {
if let Some(def) = defs.get(name.as_str()) {
if def.params.len() == args.len() {
let substituted = substitute_params(&def.definiens, &def.params, args);
return expand_defs_in_expr(&substituted, defs, depth + 1);
}
}
expr.clone()
}
ProofExpr::And(l, r) => ProofExpr::And(
Box::new(expand_defs_in_expr(l, defs, depth)),
Box::new(expand_defs_in_expr(r, defs, depth)),
),
ProofExpr::Or(l, r) => ProofExpr::Or(
Box::new(expand_defs_in_expr(l, defs, depth)),
Box::new(expand_defs_in_expr(r, defs, depth)),
),
ProofExpr::Implies(l, r) => ProofExpr::Implies(
Box::new(expand_defs_in_expr(l, defs, depth)),
Box::new(expand_defs_in_expr(r, defs, depth)),
),
ProofExpr::Iff(l, r) => ProofExpr::Iff(
Box::new(expand_defs_in_expr(l, defs, depth)),
Box::new(expand_defs_in_expr(r, defs, depth)),
),
ProofExpr::Not(p) => ProofExpr::Not(Box::new(expand_defs_in_expr(p, defs, depth))),
ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
variable: variable.clone(),
body: Box::new(expand_defs_in_expr(body, defs, depth)),
},
ProofExpr::Exists { variable, body } => ProofExpr::Exists {
variable: variable.clone(),
body: Box::new(expand_defs_in_expr(body, defs, depth)),
},
other => other.clone(),
}
}
fn substitute_params(expr: &ProofExpr, params: &[String], args: &[ProofTerm]) -> ProofExpr {
match expr {
ProofExpr::Predicate { name, args: pargs, world } => ProofExpr::Predicate {
name: name.clone(),
args: pargs.iter().map(|t| subst_term(t, params, args)).collect(),
world: world.clone(),
},
ProofExpr::And(l, r) => ProofExpr::And(
Box::new(substitute_params(l, params, args)),
Box::new(substitute_params(r, params, args)),
),
ProofExpr::Or(l, r) => ProofExpr::Or(
Box::new(substitute_params(l, params, args)),
Box::new(substitute_params(r, params, args)),
),
ProofExpr::Implies(l, r) => ProofExpr::Implies(
Box::new(substitute_params(l, params, args)),
Box::new(substitute_params(r, params, args)),
),
ProofExpr::Iff(l, r) => ProofExpr::Iff(
Box::new(substitute_params(l, params, args)),
Box::new(substitute_params(r, params, args)),
),
ProofExpr::Not(p) => ProofExpr::Not(Box::new(substitute_params(p, params, args))),
ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
variable: variable.clone(),
body: Box::new(substitute_params(body, params, args)),
},
ProofExpr::Exists { variable, body } => ProofExpr::Exists {
variable: variable.clone(),
body: Box::new(substitute_params(body, params, args)),
},
ProofExpr::Identity(l, r) => {
ProofExpr::Identity(subst_term(l, params, args), subst_term(r, params, args))
}
other => other.clone(),
}
}
fn expand_iff(expr: &ProofExpr) -> ProofExpr {
match expr {
ProofExpr::Iff(p, q) => {
let p = expand_iff(p);
let q = expand_iff(q);
ProofExpr::And(
Box::new(ProofExpr::Implies(Box::new(p.clone()), Box::new(q.clone()))),
Box::new(ProofExpr::Implies(Box::new(q), Box::new(p))),
)
}
ProofExpr::And(l, r) => {
ProofExpr::And(Box::new(expand_iff(l)), Box::new(expand_iff(r)))
}
ProofExpr::Or(l, r) => ProofExpr::Or(Box::new(expand_iff(l)), Box::new(expand_iff(r))),
ProofExpr::Implies(l, r) => {
ProofExpr::Implies(Box::new(expand_iff(l)), Box::new(expand_iff(r)))
}
ProofExpr::Not(p) => ProofExpr::Not(Box::new(expand_iff(p))),
ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
variable: variable.clone(),
body: Box::new(expand_iff(body)),
},
ProofExpr::Exists { variable, body } => ProofExpr::Exists {
variable: variable.clone(),
body: Box::new(expand_iff(body)),
},
other => other.clone(),
}
}
fn subst_term(term: &ProofTerm, params: &[String], args: &[ProofTerm]) -> ProofTerm {
match term {
ProofTerm::Constant(n) => match params.iter().position(|p| p == n) {
Some(i) => args[i].clone(),
None => term.clone(),
},
ProofTerm::Function(name, fargs) => ProofTerm::Function(
name.clone(),
fargs.iter().map(|t| subst_term(t, params, args)).collect(),
),
ProofTerm::Group(ts) => {
ProofTerm::Group(ts.iter().map(|t| subst_term(t, params, args)).collect())
}
ProofTerm::Variable(_) | ProofTerm::BoundVarRef(_) => term.clone(),
}
}