summaryrefslogtreecommitdiff
path: root/crates/elfmind/src/knowledge.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/elfmind/src/knowledge.rs')
-rw-r--r--crates/elfmind/src/knowledge.rs154
1 files changed, 139 insertions, 15 deletions
diff --git a/crates/elfmind/src/knowledge.rs b/crates/elfmind/src/knowledge.rs
index cbcce28..a610328 100644
--- a/crates/elfmind/src/knowledge.rs
+++ b/crates/elfmind/src/knowledge.rs
@@ -112,8 +112,8 @@ impl Rule {
pub fn unify_body(&self, facts: &[&Relation]) -> Option<Unification> {
if self.body.len() != facts.len() { return None; }
let mut u = Unification::default();
- for (r1, r2) in self.body.iter().zip(facts) {
- u = r1.unify(r2, u)?;
+ for (r, f) in self.body.iter().zip(facts) {
+ u = r.unify(f, u)?;
}
Some(u)
}
@@ -130,6 +130,17 @@ impl Facts {
pub fn with_sort(&self, s: RelationDiscriminants) -> &HashSet<Relation> {
&self.facts[s]
}
+ pub fn with_sort_mut(&mut self, s: RelationDiscriminants) -> &mut HashSet<Relation> {
+ &mut self.facts[s]
+ }
+ pub fn contains(&self, f: &Relation) -> bool {
+ self.facts[f.discriminant()].contains(f)
+ }
+ pub fn absorb(&mut self, other: &mut Facts) {
+ for (rd, fs) in self.facts.iter_mut() {
+ fs.extend(other.with_sort_mut(rd).drain());
+ }
+ }
}
#[derive(Debug, Default, Clone)]
@@ -138,28 +149,141 @@ pub struct Database {
rules: Vec<Rule>,
}
impl Database {
- pub fn add_fact(&mut self, r: Relation) { self.facts.add(r); }
- pub fn add_rule(&mut self, r: Rule) { self.rules.push(r) }
- fn apply_rule(r: &Rule, facts: &mut Facts) -> bool {
- let found: Vec<Unification> = r.body.iter()
- .map(|r| facts.with_sort(r.discriminant())).multi_cartesian_product()
- .filter_map(|fs| r.unify_body(&fs))
- .collect();
+ pub fn is_known(&self, r: &Relation) -> bool { self.facts.contains(r) }
+ pub fn query<'a>(&'a mut self) -> Query<'a> {
+ Query {
+ db: self,
+ facts: Facts::default(),
+ rules: Vec::new(),
+ }
+ }
+}
+
+#[must_use]
+#[derive(Debug)]
+pub struct Query<'a> {
+ db: &'a mut Database,
+ facts: Facts,
+ rules: Vec<Rule>,
+}
+#[derive(Debug, Clone, Copy)]
+enum QueryFactsToConsider<'a> {
+ LastIteration(&'a Facts),
+ Query,
+ DB,
+}
+impl<'a> Query<'a> {
+ fn apply_rule(&self, r: &Rule, new_facts: &mut Facts, required_facts: QueryFactsToConsider) -> bool {
+ // we don't need to consider every possible fact against each body term (naive evaluation)
+ // instead, we can limit ourselves to the subset where at least one of the facts is new (semi-naive)
+ // let found: Vec<Unification> = r.body.iter()
+ // .map(|r| {
+ // let d = r.discriminant();
+ // new_facts.with_sort(d).iter()
+ // .chain(self.facts.with_sort(d).iter())
+ // .chain(self.db.facts.with_sort(d).iter())
+ // })
+ // .multi_cartesian_product()
+ // .filter_map(|fs| r.unify_body(&fs))
+ // .collect();
+ let mut found = Vec::new();
+ // identify which slots of this rule body possibly have new facts to consider
+ let changed_slots = r.body.iter()
+ .enumerate()
+ .filter_map(|(i, r)| {
+ match required_facts {
+ // if we are only considering facts from the provided set
+ QueryFactsToConsider::LastIteration(rf) =>
+ if rf.with_sort(r.discriminant()).is_empty() { None } else { Some(i) },
+ // if we are only considering facts added by this query
+ QueryFactsToConsider::Query =>
+ if self.facts.with_sort(r.discriminant()).is_empty() { None } else { Some(i) },
+ // if we are considering all facts, simply consider every index
+ QueryFactsToConsider::DB => Some(i),
+ }
+ });
+ let empty = HashSet::new();
+ for cs in changed_slots {
+ let attempts = r.body.iter()
+ .enumerate()
+ .map(|(i, r)| {
+ let d = r.discriminant();
+ match required_facts {
+ // if this is an index that changed, limit it to only the provided set of facts
+ QueryFactsToConsider::LastIteration(rf) if i == cs =>
+ rf.with_sort(d).iter().chain(empty.iter()),
+ // (or the set of facts from this query)
+ QueryFactsToConsider::Query if i == cs =>
+ self.facts.with_sort(d).iter().chain(empty.iter()),
+ // otherwise, consider all facts - a new fact might be used alongside an unchanged fact!
+ _ =>
+ self.facts.with_sort(d).iter().chain(self.db.facts.with_sort(d).iter()),
+ }
+ })
+ .multi_cartesian_product();
+ for fs in attempts {
+ if let Some(u) = r.unify_body(&fs) {
+ found.push(u);
+ }
+ }
+ }
let mut changed = false;
for u in found {
- if facts.add(r.head.substitute(&u)) { changed = true; }
+ let new = r.head.substitute(&u);
+ if new.is_concrete() && !self.facts.contains(&new) && !self.db.facts.contains(&new) {
+ if new_facts.add(new) { changed = true; }
+ }
}
changed
}
- pub fn apply_rules_once(&mut self) -> bool {
+ fn apply_new_once(&mut self, new_facts: &mut Facts, required_facts: QueryFactsToConsider) -> bool {
let mut changed = false;
- for r in self.rules.iter() {
- if Self::apply_rule(r, &mut self.facts) { changed = true }
+ match &required_facts {
+ QueryFactsToConsider::DB => {
+ // if we are considering only DB facts, we only need to consider new rules
+ for r in self.rules.iter() {
+ if self.apply_rule(r, new_facts, required_facts) { changed = true }
+ }
+ },
+ _ => { // otherwise, we should consider all rules
+ for r in self.rules.iter().chain(self.db.rules.iter()) {
+ if self.apply_rule(r, new_facts, required_facts) { changed = true }
+ }
+ }
}
changed
}
- pub fn apply_rules(&mut self) {
- while self.apply_rules_once() {}
+ pub fn fact(mut self, r: Relation) -> Self {
+ if !self.db.is_known(&r) {
+ self.facts.add(r);
+ }
+ self
+ }
+ pub fn rule(mut self, r: Rule) -> Self {
+ self.rules.push(r);
+ self
+ }
+ fn run(&mut self) {
+ let mut new_facts = Facts::default();
+ // compare the new rules with the old facts
+ self.apply_new_once(&mut new_facts, QueryFactsToConsider::DB);
+ // compare the new+old rules with the new facts
+ self.apply_new_once(&mut new_facts, QueryFactsToConsider::Query);
+ let mut prev_facts = new_facts.clone();
+ self.facts.absorb(&mut new_facts);
+ while self.apply_new_once(&mut new_facts, QueryFactsToConsider::LastIteration(&prev_facts)) {
+ prev_facts = new_facts.clone();
+ self.facts.absorb(&mut new_facts);
+ }
+ }
+ pub fn ask(mut self) -> Facts {
+ self.run();
+ self.facts
+ }
+ pub fn learn(mut self) {
+ self.run();
+ self.db.facts.absorb(&mut self.facts);
+ self.db.rules.extend_from_slice(&mut self.rules);
}
}