diff --git a/Cargo.toml b/Cargo.toml index 25c9555..5ac6149 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rify" -version = "0.7.0" +version = "0.7.1" authors = [ "Andrew Dirksen ", "Sam Hellawell " diff --git a/benches/.gitignore b/benches/.gitignore new file mode 100644 index 0000000..0363452 --- /dev/null +++ b/benches/.gitignore @@ -0,0 +1,5 @@ +/target +/pkg +/wasm-pack.log +/tmp +/Cargo.lock diff --git a/benches/Cargo.toml b/benches/Cargo.toml new file mode 100644 index 0000000..343742c --- /dev/null +++ b/benches/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "benches" +version = "0.1.0" +authors = ["Andrew Dirksen "] +edition = "2018" + +[dependencies] +rify = { path = ".." } diff --git a/benches/rust-toolchain b/benches/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/benches/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/benches/src/lib.rs b/benches/src/lib.rs new file mode 100644 index 0000000..c5cef33 --- /dev/null +++ b/benches/src/lib.rs @@ -0,0 +1,137 @@ +#![cfg(test)] +#![feature(test)] + +extern crate test; + +use core::fmt::Debug; +use rify::Entity::{Bound as B, Unbound as U}; +use rify::*; +use test::Bencher; + +const DG: &str = "default_graph"; + +mod ancestry { + use super::*; + const PARENT: &str = "parent"; + const ANCESTOR: &str = "ancestor"; + + fn rules() -> Vec> { + decl_rules(&[ + [ + &[[U("a"), B(PARENT), U("b"), B(DG)]], + &[[U("a"), B(ANCESTOR), U("b"), B(DG)]], + ], + [ + &[ + [U("a"), B(ANCESTOR), U("b"), B(DG)], + [U("b"), B(ANCESTOR), U("c"), B(DG)], + ], + &[[U("a"), B(ANCESTOR), U("c"), B(DG)]], + ], + ]) + } + + // Contains intentional leak; don't use outside of tests. + fn facts(numnodes: usize) -> Vec<[&'static str; 4]> { + let nodes: Vec<&str> = (0..numnodes) + .map(|n| Box::leak(format!("node_{}", n).into_boxed_str()) as &str) + .collect(); + let facts: Vec<[&str; 4]> = nodes + .iter() + .zip(nodes.iter().cycle().skip(1)) + .map(|(a, b)| [a, PARENT, b, DG]) + .collect(); + facts + } + + #[bench] + fn infer_(b: &mut Bencher) { + let facts = facts(20); + let rules = rules(); + b.iter(|| infer(&facts, &rules)); + } + + #[bench] + fn prove_(b: &mut Bencher) { + let facts = facts(20); + let rules = rules(); + b.iter(|| prove(&facts, &[[PARENT, PARENT, PARENT, PARENT]], &rules).unwrap_err()); + } + + #[bench] + fn infer_30(b: &mut Bencher) { + let facts = facts(30); + let rules = rules(); + b.iter(|| infer(&facts, &rules)); + } + + #[bench] + fn prove_30(b: &mut Bencher) { + let facts = facts(30); + let rules = rules(); + b.iter(|| prove(&facts, &[[PARENT, PARENT, PARENT, PARENT]], &rules).unwrap_err()); + } +} + +mod recursion_minimal { + use super::*; + + const RULES: &[[&[[rify::Entity<&str, &str>; 4]]; 2]] = &[ + [ + &[ + [B("andrew"), B("claims"), U("c"), B(DG)], + [U("c"), B("subject"), U("s"), B(DG)], + [U("c"), B("property"), U("p"), B(DG)], + [U("c"), B("object"), U("o"), B(DG)], + ], + &[[U("s"), U("p"), U("o"), B(DG)]], + ], + [ + &[ + [U("person_a"), B("is"), B("awesome"), B(DG)], + [U("person_a"), B("friendswith"), U("person_b"), B(DG)], + ], + &[[U("person_b"), B("is"), B("awesome"), B(DG)]], + ], + [ + &[[U("person_a"), B("friendswith"), U("person_b"), B(DG)]], + &[[U("person_b"), B("friendswith"), U("person_a"), B(DG)]], + ], + ]; + + const FACTS: &[[&str; 4]] = &[ + ["soyoung", "friendswith", "nick", DG], + ["nick", "friendswith", "elina", DG], + ["elina", "friendswith", "sam", DG], + ["sam", "friendswith", "fausto", DG], + ["fausto", "friendswith", "lovesh", DG], + ["andrew", "claims", "_:claim1", DG], + ["_:claim1", "subject", "lovesh", DG], + ["_:claim1", "property", "is", DG], + ["_:claim1", "object", "awesome", DG], + ]; + + #[bench] + fn infer_(b: &mut Bencher) { + let rules = decl_rules(RULES); + b.iter(|| infer(FACTS, &rules)); + } + + #[bench] + fn prove_(b: &mut Bencher) { + let rules = decl_rules(RULES); + let composite_claims: &[[&str; 4]] = &[ + ["soyoung", "is", "awesome", "default_graph"], + ["nick", "is", "awesome", "default_graph"], + ]; + b.iter(|| prove(FACTS, composite_claims, &rules)); + } +} + +pub fn decl_rules( + rs: &[[&[[rify::Entity; 4]]; 2]], +) -> Vec> { + rs.iter() + .map(|[ifa, then]| rify::Rule::create(ifa.to_vec(), then.to_vec()).unwrap()) + .collect() +} diff --git a/bindings/js_wasm/package.json b/bindings/js_wasm/package.json index 64d3e2a..b60401d 100644 --- a/bindings/js_wasm/package.json +++ b/bindings/js_wasm/package.json @@ -5,7 +5,7 @@ "Sam Hellawell " ], "description": "RDF reasoner that operates on RIF-like conjunctive rules. Outputs a machine\nreadable proof of some claim which can be cheaply verified.\n", - "version": "0.7.0", + "version": "0.7.1", "license": "MIT OR Apache-2.0", "repository": { "type": "git", diff --git a/justfile b/justfile index 301281a..72b8abf 100644 --- a/justfile +++ b/justfile @@ -30,10 +30,36 @@ js-test: # remove dist and node_modules from js bindings tests clean: cargo clean - rm -r bindings/js_wasm/pkg || true + cd benches; cargo clean + rm -rf bindings/js_wasm/pkg just clean-js # remove artifacts from js bindings tests clean-js: - rm -r bindings/js_wasm/binding_tests/dist || true - rm -r bindings/js_wasm/binding_tests/node_modules || true + rm -rf bindings/js_wasm/binding_tests/dist + rm -rf bindings/js_wasm/binding_tests/node_modules + +bench: + #!/usr/bin/env bash + cd benches + cargo bench + +checkall-buildall: + just clean + cargo test + cargo clippy -- -D warnings + just bench + just js-test + +assert-clean-workdir: + ! test -n "$(git status --porcelain)" # please commit before publish + +publish-js: + just checkall-buildall + just assert-clean-workdir + cd bindings/js_wasm/pkg && npm publish + +publish-rs: + just checkall-buildall + just assert-clean-workdir + cargo publish diff --git a/src/common.rs b/src/common.rs index 6ea7e23..7dc9f2b 100644 --- a/src/common.rs +++ b/src/common.rs @@ -33,9 +33,9 @@ where /// [crate::prove::prove] function does not immediately convert LowRuleApplication's to /// [RuleApplication]'s. Rather, it converts only the LowRuleApplication's it is going to return /// to the caller. -pub struct LowRuleApplication { +pub(crate) struct LowRuleApplication { pub rule_index: usize, - pub instantiations: BTreeMap, + pub instantiations: Vec>, } impl LowRuleApplication { @@ -65,7 +65,7 @@ impl LowRuleApplication { for unbound_human in original_rule.cononical_unbound() { let unbound_local: usize = uhul[unbound_human]; - let bound_global: usize = self.instantiations[&unbound_local]; + let bound_global: usize = self.instantiations.get(unbound_local).unwrap().unwrap(); let bound_human: &Bound = trans.back(bound_global).unwrap(); instantiations.push(bound_human.clone()); } @@ -81,7 +81,7 @@ impl LowRuleApplication { /// This is a helper function. It translates all four element of a quad, calling /// [Translator::forward] for each element. If any element has no translation /// this function will return `None`. -pub fn forward(translator: &Translator, key: &[T; 4]) -> Option { +pub(crate) fn forward(translator: &Translator, key: &[T; 4]) -> Option { let [s, p, o, g] = key; Some( [ @@ -97,7 +97,7 @@ pub fn forward(translator: &Translator, key: &[T; 4]) -> Option /// Reverse of [forward]. /// [forward] translates each element from `T` to `usize`. This function translates each element /// from `usize` to `T`. If any element has no translation this function will return `None`. -pub fn back(translator: &Translator, key: Quad) -> Option<[&T; 4]> { +pub(crate) fn back(translator: &Translator, key: Quad) -> Option<[&T; 4]> { let Quad { s, p, o, g } = key; Some([ translator.back(s.0)?, diff --git a/src/infer.rs b/src/infer.rs index a6ab38c..b17c410 100644 --- a/src/infer.rs +++ b/src/infer.rs @@ -28,36 +28,51 @@ pub fn infer( /// A version of infer that operates on lowered input an returns output in lowered form. fn low_infer(premises: &[Quad], rules: &[LowRule]) -> Vec { let mut rs = Reasoner::default(); - for prem in premises { - rs.insert(prem.clone()); + + let mut to_add: BTreeSet = premises.iter().cloned().collect(); + let initial_len = to_add.len(); // number of premises after dedup + assert!(initial_len <= premises.len()); + + // apply_related is sufficient except for in the case of unconditional rules. + // in order to avoid calling apply() directly, rules with empty "if" clauses + // lists will get special treatment. + for rule in rules { + if rule.if_all.is_empty() { + for rule_part in &rule.then { + to_add.insert(rule_part.clone().local_to_global(&rule.inst).unwrap()); + } + } } - let initial_len = rs.claims_ref().len(); // number of premises after dedup - debug_assert!(initial_len <= premises.len()); + let mut rules: Vec = rules + .iter() + .filter(|r| !r.if_all.is_empty()) + .cloned() + .collect(); + // subsequent reasoning is done in a loop using apply_related loop { - let mut to_add = BTreeSet::::new(); - for rr in rules.iter() { - rs.apply(&mut rr.if_all.clone(), &mut rr.inst.clone(), &mut |inst| { - let ins = inst.as_ref(); - for implied in &rr.then { - let new_quad = [ - ins[&implied.s.0], - ins[&implied.p.0], - ins[&implied.o.0], - ins[&implied.g.0], - ] - .into(); - if !rs.contains(&new_quad) { - to_add.insert(new_quad); - } - } - }); - } if to_add.is_empty() { break; } - for new in to_add.into_iter() { - rs.insert(new); + let mut adding = BTreeSet::default(); + core::mem::swap(&mut to_add, &mut adding); + for new in adding.iter().cloned() { + rs.insert(new.clone()); + for LowRule { + ref mut if_all, + then, + ref mut inst, + } in rules.iter_mut() + { + rs.apply_related(new.clone(), if_all, inst, &mut |inst| { + for implied in then.iter().cloned() { + let new_quad = implied.local_to_global(inst).unwrap(); + if !rs.contains(&new_quad) && !adding.contains(&new_quad) { + to_add.insert(new_quad); + } + } + }); + } } } diff --git a/src/mapstack.rs b/src/mapstack.rs index a17ab67..08d9970 100644 --- a/src/mapstack.rs +++ b/src/mapstack.rs @@ -1,51 +1,58 @@ -use alloc::collections::BTreeMap; use core::fmt; use core::fmt::Debug; use core::iter::FromIterator; /// A mapping that keeps a history of writes. Writes to the map effect "pushes" to a stack. Those /// "pushes" can be undone with a "pop". -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MapStack { - current: BTreeMap, - history: Vec<(K, Option)>, +/// +/// Beware large keys when using this data structure. The memory used byt this structure scales +/// with the size of the largest key! +#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Default)] +pub(crate) struct MapStack { + current: Vec>, + history: Vec<(usize, Option)>, } -impl MapStack { +impl MapStack { pub fn new() -> Self { Self { - current: BTreeMap::new(), + current: Vec::new(), history: Vec::new(), } } } -impl MapStack { +impl MapStack { /// Set a value appending to write history. - pub fn write(&mut self, key: K, val: V) { - let old_val = self.current.insert(key.clone(), val); - self.history.push((key, old_val)); + pub fn write(&mut self, key: usize, val: V) { + debug_assert!(key < 30 ^ 2, "Thats a pretty large key. Are you sure?"); + if self.current.len() <= key { + self.current.resize_with(key + 1, || None); + } + let mut val = Some(val); + core::mem::swap(&mut self.current[key], &mut val); + self.history.push((key, val)); } /// Undo most recent write or return error if there is no history to undo. pub fn undo(&mut self) -> Result<(), NoMoreHistory> { let (key, old_val) = self.history.pop().ok_or(NoMoreHistory)?; - match old_val { - Some(val) => self.current.insert(key, val), - None => self.current.remove(&key), - }; + self.current[key] = old_val; Ok(()) } -} -impl AsRef> for MapStack { - fn as_ref(&self) -> &BTreeMap { + /// Get the current value at key. + pub fn get(&self, key: usize) -> Option<&V> { + self.current.get(key).and_then(|o| o.as_ref()) + } + + pub fn inner(&self) -> &Vec> { &self.current } } -impl FromIterator<(K, V)> for MapStack { - fn from_iter>(kvs: T) -> Self { +impl FromIterator<(usize, V)> for MapStack { + fn from_iter>(kvs: T) -> Self { let mut ret = Self::new(); for (k, v) in kvs.into_iter() { ret.write(k, v); diff --git a/src/prove.rs b/src/prove.rs index e377882..145d5db 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -7,7 +7,7 @@ use crate::translator::Translator; use alloc::collections::{BTreeMap, BTreeSet}; use core::fmt::{Debug, Display}; -/// Locate a proof of some composite claims given the provied premises and rules. +/// Locate a proof of some composite claims given the provided premises and rules. /// /// ``` /// # fn main() -> Result<(), Box> { @@ -93,47 +93,63 @@ fn low_prove( rules: &[LowRule], ) -> Result, CantProve> { let mut rs = Reasoner::default(); - for prem in premises { - rs.insert(prem.clone()); - } - // statement (Quad) is proved by applying rule (LowRuleApplication) let mut arguments: BTreeMap = BTreeMap::new(); + let mut to_add: BTreeSet = premises.iter().cloned().collect(); + + for (rule_index, rule) in rules.iter().enumerate() { + if rule.if_all.is_empty() { + for implied in &rule.then { + let new_quad = implied.clone().local_to_global(&rule.inst).unwrap(); + if to_add.insert(new_quad.clone()) { + arguments.insert( + new_quad, + LowRuleApplication { + rule_index, + instantiations: Default::default(), + }, + ); + } + } + } + } + let mut rules2: Vec<(usize, LowRule)> = rules + .iter() + .cloned() + .enumerate() + .filter(|(_index, rule)| !rule.if_all.is_empty()) + .collect(); // reason - loop { - if to_prove.iter().all(|tp| rs.contains(tp)) { - break; - } - let mut to_add = BTreeSet::::new(); - for (rule_index, rr) in rules.iter().enumerate() { - rs.apply(&mut rr.if_all.clone(), &mut rr.inst.clone(), &mut |inst| { - let ins = inst.as_ref(); - for implied in &rr.then { - let new_quad = [ - ins[&implied.s.0], - ins[&implied.p.0], - ins[&implied.o.0], - ins[&implied.g.0], - ] - .into(); - if !rs.contains(&new_quad) { - arguments - .entry(new_quad.clone()) - .or_insert_with(|| LowRuleApplication { - rule_index, - instantiations: ins.clone(), + while !to_add.is_empty() && !to_prove.iter().all(|tp| rs.contains(tp)) { + let mut adding_now = BTreeSet::::new(); + core::mem::swap(&mut adding_now, &mut to_add); + for fact in &adding_now { + rs.insert(fact.clone()); + for ( + rule_index, + LowRule { + ref mut if_all, + then, + ref mut inst, + }, + ) in rules2.iter_mut() + { + rs.apply_related(fact.clone(), if_all, inst, &mut |inst| { + for implied in then.iter().cloned() { + let new_quad = implied.local_to_global(inst).unwrap(); + if !rs.contains(&new_quad) && !adding_now.contains(&new_quad) { + arguments.entry(new_quad.clone()).or_insert_with(|| { + LowRuleApplication { + rule_index: *rule_index, + instantiations: inst.inner().clone(), + } }); - to_add.insert(new_quad); + to_add.insert(new_quad); + } } - } - }); - } - if to_add.is_empty() { - break; - } - for new in to_add.into_iter() { - rs.insert(new); + }); + } } } @@ -160,8 +176,11 @@ fn recall_proof( outp: &mut Vec, ) { let to_global_scope = |rra: &LowRuleApplication, locally_scoped: usize| -> usize { - let concrete = rules[rra.rule_index].inst.as_ref().get(&locally_scoped); - let found = rra.instantiations.get(&locally_scoped); + let concrete = rules[rra.rule_index].inst.get(locally_scoped); + let found = rra + .instantiations + .get(locally_scoped) + .and_then(|o| o.as_ref()); if let (Some(c), Some(f)) = (concrete, found) { debug_assert_eq!(c, f); } @@ -214,7 +233,7 @@ impl std::error::Error for CantProve {} /// An element of a deductive proof. Proofs can be transmitted and later validatated as long as the /// validator assumes the same rule list as the prover. /// -/// Unbound variables are bound to the values in `instanitations`. They are bound in order of +/// Unbound variables are bound to the values in `instantiations`. They are bound in order of /// initial appearance. /// /// Given the rule: @@ -288,31 +307,31 @@ impl RuleApplication { /// Panics /// /// panics if an unbound entity is not registered in map -/// panics if the canonical index of unbound (according to map) is too large to index instanitations +/// panics if the canonical index of unbound (according to map) is too large to index instantiations fn bind_claim( [s, p, o, g]: [Entity; 4], map: &BTreeMap<&Unbound, usize>, - instanitations: &[Bound], + instantiations: &[Bound], ) -> [Bound; 4] { [ - bind_entity(s, map, instanitations), - bind_entity(p, map, instanitations), - bind_entity(o, map, instanitations), - bind_entity(g, map, instanitations), + bind_entity(s, map, instantiations), + bind_entity(p, map, instantiations), + bind_entity(o, map, instantiations), + bind_entity(g, map, instantiations), ] } /// Panics /// /// panics if an unbound entity is not registered in map -/// panics if the canonical index of unbound (according to map) is too large to index instanitations +/// panics if the canonical index of unbound (according to map) is too large to index instantiations fn bind_entity( e: Entity, map: &BTreeMap<&Unbound, usize>, - instanitations: &[Bound], + instantiations: &[Bound], ) -> Bound { match e { - Entity::Unbound(a) => instanitations[map[&a]].clone(), + Entity::Unbound(a) => instantiations[map[&a]].clone(), Entity::Bound(e) => e, } } diff --git a/src/reasoner.rs b/src/reasoner.rs index 572feaf..bcd5c70 100644 --- a/src/reasoner.rs +++ b/src/reasoner.rs @@ -3,13 +3,37 @@ use crate::vecset::VecSet; use core::cmp::Ordering; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Quad { +pub(crate) struct Quad { pub s: Subj, pub p: Prop, pub o: Obje, pub g: Grap, } +impl Quad { + fn zip(self, other: Quad) -> [(usize, usize); 4] { + [ + (self.s.0, other.s.0), + (self.p.0, other.p.0), + (self.o.0, other.o.0), + (self.g.0, other.g.0), + ] + } + + /// Attempt dereference all variable in self. + pub fn local_to_global(self, inst: &Instantiations) -> Option { + Some( + [ + *inst.get(self.s.0)?, + *inst.get(self.p.0)?, + *inst.get(self.o.0)?, + *inst.get(self.g.0)?, + ] + .into(), + ) + } +} + impl From<[usize; 4]> for Quad { fn from([s, p, o, g]: [usize; 4]) -> Self { Self { @@ -57,10 +81,10 @@ type O = (Obje,); type G = (Grap,); /// Bindings of slots within the context of a rule. -pub type Instantiations = MapStack; +pub(crate) type Instantiations = MapStack; #[derive(Default)] -pub struct Reasoner { +pub(crate) struct Reasoner { claims: Vec, spog: VecSet, posg: VecSet, @@ -102,6 +126,34 @@ impl Reasoner { .all(|wn| wn[0] == wn[1])); } + /// Granted a statement find in this store all possible valid instantiations of rule that + /// involve the statement. It is expexcted that the statement has already been [Self::insert]ed. + pub fn apply_related( + &self, + quad: Quad, + rule: &mut [Quad], + instantiations: &mut Instantiations, + cb: &mut impl FnMut(&Instantiations), + ) { + debug_assert!(self.contains(&quad)); + debug_assert!(!rule.is_empty(), "potentialy confusing so disallowed"); + // for each atom of rule, if the atom can match the quad, bind the unbound variables in the + // atom to the corresponding elements of quad, then call apply. + for i in 0..rule.len() { + let (rule_part, rule_rest) = evict(i, rule).unwrap(); + if can_match(quad.clone(), rule_part.clone(), instantiations) { + let to_set = rule_part.clone().zip(quad.clone()); + for (k, v) in &to_set { + instantiations.write(*k, *v); + } + self.apply(rule_rest, instantiations, cb); + for _ in &to_set { + instantiations.undo().unwrap(); + } + } + } + } + /// Find in this store all possible valid instantiations of rule. Report the /// instantiations through a callback. /// TODO: This function is recursive, but not tail recursive. Rules that are too long may @@ -125,15 +177,10 @@ impl Reasoner { // in the requirement to the instantiation then recurse. for index in self.matches(strictest, instantiations) { let quad = &self.claims[*index]; - let to_write = [ - (strictest.s.0, quad.s.0), - (strictest.p.0, quad.p.0), - (strictest.o.0, quad.o.0), - (strictest.g.0, quad.g.0), - ]; + let to_write = strictest.clone().zip(quad.clone()); for (k, v) in &to_write { debug_assert!( - if let Some(committed_v) = instantiations.as_ref().get(&k) { + if let Some(committed_v) = instantiations.get(*k) { committed_v == v } else { true @@ -152,13 +199,12 @@ impl Reasoner { /// Return a slice representing all possible matches to the pattern provided. /// pattern is in a local scope. instantiations is a partial translation of that /// local scope to the global scope represented by self.claims - fn matches(&self, pattern: &Quad, instantiations: &Instantiations) -> &[usize] { - let inst = instantiations.as_ref(); + fn matches(&self, pattern: &Quad, inst: &Instantiations) -> &[usize] { let pattern: (Option, Option, Option, Option) = ( - inst.get(&pattern.s.0).cloned().map(Subj), - inst.get(&pattern.p.0).cloned().map(Prop), - inst.get(&pattern.o.0).cloned().map(Obje), - inst.get(&pattern.g.0).cloned().map(Grap), + inst.get(pattern.s.0).cloned().map(Subj), + inst.get(pattern.p.0).cloned().map(Prop), + inst.get(pattern.o.0).cloned().map(Obje), + inst.get(pattern.g.0).cloned().map(Grap), ); match pattern { (Some(s), Some(p), Some(o), Some(g)) => (s, p, o, g).search(self), @@ -193,9 +239,7 @@ impl Reasoner { ) -> Option<(&'rule Quad, &'rule mut [Quad])> { let index_strictest = (0..rule.len()) .min_by_key(|index| self.matches(&rule[*index], instantiations).len())?; - rule.swap(0, index_strictest); - let (strictest, less_strict) = rule.split_first_mut().expect("rule to be non-empty"); - Some((strictest, less_strict)) + evict(index_strictest, rule) } /// Get the deduplicated history of all claims that were inserted into this reasoner. @@ -203,14 +247,32 @@ impl Reasoner { pub fn claims(self) -> Vec { self.claims } +} - /// Get the deduplicated history of all claims that were inserted into this reasoner. - /// The returned list will be in insertion order. - pub fn claims_ref(&self) -> &[Quad] { - &self.claims +fn evict(index: usize, unordered_list: &mut [T]) -> Option<(&T, &mut [T])> { + if index >= unordered_list.len() { + None + } else { + unordered_list.swap(0, index); + let (popped, rest) = unordered_list + .split_first_mut() + .expect("list to be non-empty"); + Some((popped, rest)) } } +/// returns whether rule_part could validly be applied to quad assuming the already given +/// instantiations +fn can_match(quad: Quad, rule_part: Quad, inst: &Instantiations) -> bool { + rule_part + .zip(quad) + .iter() + .all(|(rp, q)| match inst.get(*rp) { + Some(a) => a == q, + None => true, + }) +} + trait Indexed { fn target(rs: &Reasoner) -> &VecSet; fn qcmp(&self, quad: &Quad) -> Ordering; @@ -287,7 +349,7 @@ mod tests { LowRule, Rule, }; use crate::translator::Translator; - use alloc::collections::{BTreeMap, BTreeSet}; + use alloc::collections::BTreeSet; #[test] fn ancestry_raw() { @@ -343,7 +405,7 @@ mod tests { // This test only does one round of reasoning, no forward chaining. We will need a forward // chaining test eventually. - let mut results = Vec::>::new(); + let mut results = Vec::>>::new(); for rule in rules { let Rule { mut if_all, @@ -351,7 +413,7 @@ mod tests { mut inst, } = rule.clone(); ts.apply(&mut if_all, &mut inst, &mut |inst| { - results.push(inst.as_ref().clone()) + results.push(inst.inner().clone()) }); } @@ -360,20 +422,15 @@ mod tests { // The second rule, (?a ancestor ?b ?g) and (?b ancestor ?c ?g) -> (?a ancestor ?c ?g), // should not activate because results from application of first rule have not been added // to the rdf store so there are there are are not yet any ancestry relations present. - let mut expected_intantiations: Vec> = nodes + let mut expected_intantiations: Vec>> = nodes .iter() .zip(nodes.iter().cycle().skip(1)) .map(|(a, b)| { - [ - (0, *a), - (1, parent.0), - (2, *b), - (3, ancestor.0), - (4, default_graph.0), - ] - .iter() - .cloned() - .collect() + [*a, parent.0, *b, ancestor.0, default_graph.0] + .iter() + .cloned() + .map(Some) + .collect() }) .collect(); results.sort(); @@ -452,14 +509,7 @@ mod tests { let then = &rule.then; ts.apply(&mut if_all, &mut inst, &mut |inst| { for implied in then { - let inst = inst.as_ref(); - let new: Quad = [ - inst[&implied.s.0], - inst[&implied.p.0], - inst[&implied.o.0], - inst[&implied.g.0], - ] - .into(); + let new: Quad = implied.clone().local_to_global(inst).unwrap(); if !ts.contains(&new) { to_add.insert(new); } diff --git a/src/rule.rs b/src/rule.rs index f512e11..605e566 100644 --- a/src/rule.rs +++ b/src/rule.rs @@ -285,8 +285,7 @@ mod test { .map(|local_name| { re_rulea .inst - .as_ref() - .get(&local_name) + .get(*local_name) .map(|global_name| trans.back(*global_name).unwrap().clone()) }) .collect(); @@ -340,8 +339,7 @@ mod test { .map(|local_name| { re_ruleb .inst - .as_ref() - .get(&local_name) + .get(*local_name) .map(|global_name| trans.back(*global_name).unwrap().clone()) }) .collect();