Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimizations #19

Merged
merged 5 commits into from
Apr 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rify"
version = "0.7.0"
version = "0.7.1"
authors = [
"Andrew Dirksen <[email protected]>",
"Sam Hellawell <[email protected]>"
Expand Down
5 changes: 5 additions & 0 deletions benches/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/target
/pkg
/wasm-pack.log
/tmp
/Cargo.lock
8 changes: 8 additions & 0 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "benches"
version = "0.1.0"
authors = ["Andrew Dirksen <[email protected]>"]
edition = "2018"

[dependencies]
rify = { path = ".." }
1 change: 1 addition & 0 deletions benches/rust-toolchain
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nightly
137 changes: 137 additions & 0 deletions benches/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<rify::Rule<&'static str, &'static str>> {
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<Unbound: Ord + Debug + Clone, Bound: Ord + Clone>(
rs: &[[&[[rify::Entity<Unbound, Bound>; 4]]; 2]],
) -> Vec<rify::Rule<Unbound, Bound>> {
rs.iter()
.map(|[ifa, then]| rify::Rule::create(ifa.to_vec(), then.to_vec()).unwrap())
.collect()
}
2 changes: 1 addition & 1 deletion bindings/js_wasm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"Sam Hellawell <[email protected]>"
],
"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",
Expand Down
32 changes: 29 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 5 additions & 5 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize, usize>,
pub instantiations: Vec<Option<usize>>,
}

impl LowRuleApplication {
Expand Down Expand Up @@ -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());
}
Expand All @@ -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<T: Ord>(translator: &Translator<T>, key: &[T; 4]) -> Option<Quad> {
pub(crate) fn forward<T: Ord>(translator: &Translator<T>, key: &[T; 4]) -> Option<Quad> {
let [s, p, o, g] = key;
Some(
[
Expand All @@ -97,7 +97,7 @@ pub fn forward<T: Ord>(translator: &Translator<T>, key: &[T; 4]) -> Option<Quad>
/// 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<T: Ord>(translator: &Translator<T>, key: Quad) -> Option<[&T; 4]> {
pub(crate) fn back<T: Ord>(translator: &Translator<T>, key: Quad) -> Option<[&T; 4]> {
let Quad { s, p, o, g } = key;
Some([
translator.back(s.0)?,
Expand Down
63 changes: 39 additions & 24 deletions src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,51 @@ pub fn infer<Unbound: Ord + Clone, Bound: Ord + Clone>(
/// A version of infer that operates on lowered input an returns output in lowered form.
fn low_infer(premises: &[Quad], rules: &[LowRule]) -> Vec<Quad> {
let mut rs = Reasoner::default();
for prem in premises {
rs.insert(prem.clone());

let mut to_add: BTreeSet<Quad> = 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<LowRule> = 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::<Quad>::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);
}
}
});
}
}
}

Expand Down
Loading