use crate::reasoner::{Triple, TripleStore};
use crate::rule::{Entity, LowRule, Rule};
use crate::translator::Translator;
use crate::Claim;
use alloc::collections::{BTreeMap, BTreeSet};
use core::convert::TryInto;
use core::fmt::{Debug, Display};
pub fn prove<'a, Unbound: Ord + Clone, Bound: Ord + Clone>(
premises: &'a [Claim<Bound>],
to_prove: &'a [Claim<Bound>],
rules: &'a [Rule<Unbound, Bound>],
) -> Result<Vec<RuleApplication<Bound>>, CantProve> {
let tran: Translator<Bound> = rules
.iter()
.flat_map(|rule| rule.iter_entities().filter_map(Entity::as_bound))
.chain(premises.iter().flatten())
.cloned()
.collect();
let as_raw = |[s, p, o]: &Claim<Bound>| -> Option<Triple> {
Some(Triple::from_tuple(
tran.forward(&s)?,
tran.forward(&p)?,
tran.forward(&o)?,
))
};
let lpremises: Vec<Triple> = premises.iter().map(|spo| as_raw(spo).unwrap()).collect();
let lto_prove: Vec<Triple> = to_prove
.iter()
.map(as_raw)
.collect::<Option<_>>()
.ok_or(CantProve::NovelName)?;
let lrules: Vec<LowRule> = rules
.iter()
.cloned()
.map(|rule: Rule<Unbound, Bound>| -> LowRule { rule.lower(&tran).map_err(|_| ()).unwrap() })
.collect();
let lproof = low_prove(&lpremises, <o_prove, &lrules)?;
Ok(lproof
.iter()
.map(|rra: &LowRuleApplication| -> RuleApplication<Bound> {
rra.raise(&rules[rra.rule_index], &tran)
})
.collect())
}
fn low_prove(
premises: &[Triple],
to_prove: &[Triple],
rules: &[LowRule],
) -> Result<Vec<LowRuleApplication>, CantProve> {
let mut ts = TripleStore::new();
for prem in premises {
ts.insert(prem.clone());
}
let mut arguments: BTreeMap<Triple, LowRuleApplication> = BTreeMap::new();
loop {
if to_prove.iter().all(|tp| ts.contains(tp)) {
break;
}
let mut to_add = BTreeSet::<Triple>::new();
for (rule_index, rr) in rules.iter().enumerate() {
ts.apply(&mut rr.if_all.clone(), &mut rr.inst.clone(), &mut |inst| {
let ins = inst.as_ref();
for implied in &rr.then {
let new_triple = Triple::from_tuple(
ins[&implied.subject.0],
ins[&implied.property.0],
ins[&implied.object.0],
);
if !ts.contains(&new_triple) {
arguments
.entry(new_triple.clone())
.or_insert_with(|| LowRuleApplication {
rule_index,
instantiations: ins.clone(),
});
to_add.insert(new_triple);
}
}
});
}
if to_add.is_empty() {
break;
}
for new in to_add.into_iter() {
ts.insert(new);
}
}
if !to_prove.iter().all(|tp| ts.contains(tp)) {
return Err(CantProve::ExhaustedSearchSpace);
}
let mut ret: Vec<LowRuleApplication> = Vec::new();
for claim in to_prove {
recall_proof(claim, &mut arguments, rules, &mut ret);
}
Ok(ret)
}
fn recall_proof<'a>(
to_prove: &Triple,
arguments: &mut BTreeMap<Triple, LowRuleApplication>,
rules: &[LowRule],
outp: &mut Vec<LowRuleApplication>,
) {
let to_global_scope = |rra: &LowRuleApplication, locally_scoped_entity: u32| -> u32 {
let concrete = rules[rra.rule_index]
.inst
.as_ref()
.get(&locally_scoped_entity);
let found = rra.instantiations.get(&locally_scoped_entity);
debug_assert!(if let (Some(c), Some(f)) = (concrete, found) {
c == f
} else {
true
});
*concrete.or(found).unwrap()
};
if let Some(application) = arguments.remove(to_prove) {
for triple in &rules[application.rule_index].if_all {
recall_proof(
&Triple::from_tuple(
to_global_scope(&application, triple.subject.0),
to_global_scope(&application, triple.property.0),
to_global_scope(&application, triple.object.0),
),
arguments,
rules,
outp,
);
}
outp.push(application);
}
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum CantProve {
ExhaustedSearchSpace,
NovelName,
}
impl Display for CantProve {
fn fmt(&self, fmtr: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
Debug::fmt(self, fmtr)
}
}
#[cfg(feature = "std")]
impl std::error::Error for CantProve {}
#[derive(Clone, Debug, PartialEq, Eq)]
struct LowRuleApplication {
rule_index: usize,
instantiations: BTreeMap<u32, u32>,
}
impl LowRuleApplication {
fn raise<Unbound: Ord, Bound: Ord + Clone>(
&self,
original_rule: &Rule<Unbound, Bound>,
trans: &Translator<Bound>,
) -> RuleApplication<Bound> {
let mut instantiations = Vec::new();
let uhul: BTreeMap<&Unbound, u32> = original_rule
.cononical_unbound()
.enumerate()
.map(|(a, b)| (b, a.try_into().unwrap()))
.collect();
for unbound_human in original_rule.cononical_unbound() {
let unbound_local = uhul[unbound_human];
let bound_global = self.instantiations[&unbound_local];
let bound_human = trans.back(bound_global).unwrap();
instantiations.push(bound_human.clone());
}
RuleApplication {
rule_index: self.rule_index,
instantiations,
}
}
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RuleApplication<Bound> {
pub rule_index: usize,
pub instantiations: Vec<Bound>,
}
impl<Bound: Clone> RuleApplication<Bound> {
pub(crate) fn assumptions_when_applied<'a, Unbound: Ord + Clone>(
&'a self,
rule: &'a Rule<Unbound, Bound>,
) -> Result<impl Iterator<Item = Claim<Bound>> + 'a, BadRuleApplication> {
self.bind_claims(rule, rule.if_all())
}
pub(crate) fn implications_when_applied<'a, Unbound: Ord + Clone>(
&'a self,
rule: &'a Rule<Unbound, Bound>,
) -> Result<impl Iterator<Item = Claim<Bound>> + 'a, BadRuleApplication> {
self.bind_claims(rule, rule.then())
}
fn bind_claims<'a, Unbound: Ord + Clone>(
&'a self,
rule: &'a Rule<Unbound, Bound>,
claims: &'a [Claim<Entity<Unbound, Bound>>],
) -> Result<impl Iterator<Item = Claim<Bound>> + 'a, BadRuleApplication> {
let cannon: BTreeMap<&Unbound, usize> = rule
.cononical_unbound()
.enumerate()
.map(|(ub, n)| (n, ub))
.collect();
if cannon.len() != self.instantiations.len() {
return Err(BadRuleApplication);
}
Ok(claims
.iter()
.cloned()
.map(move |claim| bind_claim(claim, &cannon, &self.instantiations)))
}
}
fn bind_claim<Unbound: Ord, Bound: Clone>(
[s, p, o]: Claim<Entity<Unbound, Bound>>,
map: &BTreeMap<&Unbound, usize>,
instanitations: &[Bound],
) -> Claim<Bound> {
[
bind_entity(s, map, instanitations),
bind_entity(p, map, instanitations),
bind_entity(o, map, instanitations),
]
}
fn bind_entity<Unbound: Ord, Bound: Clone>(
e: Entity<Unbound, Bound>,
map: &BTreeMap<&Unbound, usize>,
instanitations: &[Bound],
) -> Bound {
match e {
Entity::Unbound(a) => instanitations[map[&a]].clone(),
Entity::Bound(e) => e,
}
}
#[derive(Debug)]
pub struct BadRuleApplication;
#[cfg(test)]
mod test {
use super::*;
use crate::common::{decl_rules, inc};
use crate::rule::Entity::{Bound, Unbound};
use crate::validate::validate;
use crate::validate::Valid;
#[test]
fn novel_name() {
assert_eq!(
prove::<&str, &str>(&[], &[["andrew", "score", "awesome"]], &[]).unwrap_err(),
CantProve::NovelName
);
}
#[test]
fn search_space_exhausted() {
let err = prove::<&str, &str>(
&[
["score", "score", "score"],
["andrew", "andrew", "andrew"],
["awesome", "awesome", "awesome"],
],
&[["andrew", "score", "awesome"]],
&[],
)
.unwrap_err();
assert_eq!(err, CantProve::ExhaustedSearchSpace);
let err = prove::<&str, &str>(
&[
["score", "score", "score"],
["andrew", "andrew", "andrew"],
["awesome", "awesome", "awesome"],
["backflip", "backflip", "backflip"],
["ability", "ability", "ability"],
],
&[["andrew", "score", "awesome"]],
&[
Rule::create(vec![], vec![]).unwrap(),
Rule::create(
vec![[Unbound("a"), Bound("ability"), Bound("backflip")]],
vec![[Unbound("a"), Bound("score"), Bound("awesome")]],
)
.unwrap(),
],
)
.unwrap_err();
assert_eq!(err, CantProve::ExhaustedSearchSpace);
}
#[test]
fn prove_already_stated() {
assert_eq!(
prove::<&str, &str>(
&[["doggo", "score", "11"]],
&[["doggo", "score", "11"]],
&[]
)
.unwrap(),
Vec::new()
);
}
#[test]
fn prove_single_step() {
let awesome_score_axiom = Rule::create(
vec![
[Unbound("boi"), Bound("is"), Bound("awesome")],
[Unbound("boi"), Bound("score"), Unbound("s")],
],
vec![[Unbound("boi"), Bound("score"), Bound("awesome")]],
)
.unwrap();
assert_eq!(
prove::<&str, &str>(
&[["you", "score", "unspecified"], ["you", "is", "awesome"]],
&[["you", "score", "awesome"]],
&[awesome_score_axiom]
)
.unwrap(),
vec![
RuleApplication {
rule_index: 0,
instantiations: vec!["you", "unspecified"]
}
]
);
}
#[test]
fn prove_multi_step() {
let rules: Vec<Rule<&str, &str>> = {
let ru: &[[&[Claim<Entity<&str, &str>>]; 2]] = &[
[
&[
[Bound("andrew"), Bound("claims"), Unbound("c")],
[Unbound("c"), Bound("subject"), Unbound("s")],
[Unbound("c"), Bound("property"), Unbound("p")],
[Unbound("c"), Bound("object"), Unbound("o")],
],
&[[Unbound("s"), Unbound("p"), Unbound("o")]],
],
[
&[
[Unbound("person_a"), Bound("is"), Bound("awesome")],
[
Unbound("person_a"),
Bound("friendswith"),
Unbound("person_b"),
],
],
&[[Unbound("person_b"), Bound("is"), Bound("awesome")]],
],
[
&[[
Unbound("person_a"),
Bound("friendswith"),
Unbound("person_b"),
]],
&[[
Unbound("person_b"),
Bound("friendswith"),
Unbound("person_a"),
]],
],
];
ru.iter()
.map(|[ifa, then]| Rule::create(ifa.to_vec(), then.to_vec()).unwrap())
.collect()
};
let facts: &[Claim<&str>] = &[
["soyoung", "friendswith", "nick"],
["nick", "friendswith", "elina"],
["elina", "friendswith", "sam"],
["sam", "friendswith", "fausto"],
["fausto", "friendswith", "lovesh"],
["andrew", "claims", "_:claim1"],
["_:claim1", "subject", "lovesh"],
["_:claim1", "property", "is"],
["_:claim1", "object", "awesome"],
];
let composite_claims: &[Claim<&str>] =
&[["soyoung", "is", "awesome"], ["nick", "is", "awesome"]];
let expected_proof: Vec<RuleApplication<&str>> = {
let ep: &[(usize, &[&str])] = &[
(0, &["_:claim1", "lovesh", "is", "awesome"]),
(2, &["fausto", "lovesh"]),
(1, &["lovesh", "fausto"]),
(2, &["sam", "fausto"]),
(1, &["fausto", "sam"]),
(2, &["elina", "sam"]),
(1, &["sam", "elina"]),
(2, &["nick", "elina"]),
(1, &["elina", "nick"]),
(2, &["soyoung", "nick"]),
(1, &["nick", "soyoung"]),
];
ep.iter()
.map(|(rule_index, inst)| RuleApplication {
rule_index: *rule_index,
instantiations: inst.to_vec(),
})
.collect()
};
let proof = prove::<&str, &str>(facts, composite_claims, &rules).unwrap();
assert!(
proof.len() <= expected_proof.len(),
"if this assertion fails, the generated proof length is longer than it was previously"
);
assert_eq!(
proof, expected_proof,
"if this assertion fails the proof may still be valid but the order of the proof may \
have changed"
);
let Valid { assumed, implied } = validate(&rules, &proof).unwrap();
for claim in composite_claims {
assert!(implied.contains(claim));
assert!(
!facts.contains(claim),
"all theorems are expected to be composite for this particular problem"
);
}
for assumption in &assumed {
assert!(
assumed.contains(assumption),
"This problem was expected to use all porvided assumptions."
);
}
}
#[test]
fn ancestry_high_prove_and_verify() {
let mut next_uniq = 0u32;
let parent = inc(&mut next_uniq);
let ancestor = inc(&mut next_uniq);
let nodes: Vec<u32> = (0usize..10).map(|_| inc(&mut next_uniq)).collect();
let facts: Vec<Claim<u32>> = nodes
.iter()
.zip(nodes.iter().cycle().skip(1))
.map(|(a, b)| [*a, parent, *b])
.collect();
let rules = decl_rules(&[
[
&[[Unbound("a"), Bound(parent), Unbound("b")]],
&[[Unbound("a"), Bound(ancestor), Unbound("b")]],
],
[
&[
[Unbound("a"), Bound(ancestor), Unbound("b")],
[Unbound("b"), Bound(ancestor), Unbound("c")],
],
&[[Unbound("a"), Bound(ancestor), Unbound("c")]],
],
]);
let composite_claims = vec![
[nodes[0], ancestor, *nodes.last().unwrap()],
[*nodes.last().unwrap(), ancestor, nodes[0]],
[nodes[0], ancestor, nodes[0]],
[nodes[0], parent, nodes[1]],
];
let proof = prove::<&str, u32>(&facts, &composite_claims, &rules).unwrap();
let Valid { assumed, implied } = validate(&rules, &proof).unwrap();
assert_eq!(
&assumed,
&facts.iter().cloned().collect(),
"all supplied premises are expected to be used for this proof"
);
assert!(!facts.contains(&composite_claims[0]));
assert!(!facts.contains(&composite_claims[1]));
assert!(!facts.contains(&composite_claims[2]));
assert!(facts.contains(&composite_claims[3]));
for claim in composite_claims {
assert!(implied.contains(&claim) ^ facts.contains(&claim));
}
for fact in facts {
assert!(!implied.contains(&fact));
}
}
#[test]
fn no_proof_is_generated_for_facts() {
let facts: Vec<Claim<&str>> = vec![
["tacos", "are", "tasty"],
["nachos", "are", "tasty"],
["nachos", "are", "food"],
];
let rules = decl_rules::<&str, &str>(&[[
&[[Bound("nachos"), Bound("are"), Bound("tasty")]],
&[[Bound("nachos"), Bound("are"), Bound("food")]],
]]);
let composite_claims = vec![["nachos", "are", "food"]];
let proof = prove(&facts, &composite_claims, &rules).unwrap();
assert_eq!(&proof, &vec![]);
}
#[test]
fn unconditional_rule() {
let facts: Vec<Claim<&str>> = vec![];
let rules =
decl_rules::<&str, &str>(&[[&[], &[[Bound("nachos"), Bound("are"), Bound("food")]]]]);
let composite_claims = vec![["nachos", "are", "food"]];
let proof = prove(&facts, &composite_claims, &rules).unwrap();
assert_eq!(
&proof,
&[RuleApplication {
rule_index: 0,
instantiations: vec![]
}]
);
}
}