Giter Club home page Giter Club logo

ascent's Introduction

Logic programming in Rust

Rust Crates.io

Ascent is a logic programming language (similar to Datalog) embedded in Rust via macros.

For more information, check out the CC paper on Ascent.

In addition, this OOPSLA paper describes the "Bring Your Own Data Structures to Datalog" aspect of Ascent.

Examples

Computing all the connected nodes in a graph

ascent! {
   relation edge(i32, i32);
   relation path(i32, i32);
   
   path(x, y) <-- edge(x, y);
   path(x, z) <-- edge(x, y), path(y, z);
}

Using Ascent

  1. Install Rust.
  2. Make a new Rust project:
    cargo new my-ascent-project
    cd my-ascent-project
  3. Add ascent as a dependency in Cargo.toml:
    [dependencies]
    ascent = "*"
  4. Write some Ascent code in main.rs. Here is a complete example:
    use ascent::ascent;
    ascent! {
       relation edge(i32, i32);
       relation path(i32, i32);
       
       path(x, y) <-- edge(x, y);
       path(x, z) <-- edge(x, y), path(y, z);
    }
    
    fn main() {
       let mut prog = AscentProgram::default();
       prog.edge = vec![(1, 2), (2, 3)];
       prog.run();
       println!("path: {:?}", prog.path);
    }
  5. Run the program:
    cargo run

Examples

See the ascent/examples directory for a more complete set of examples.

Features

Lattices

Ascent supports computing fixed points of user-defined lattices. The lattice keyword defines a lattice in Ascent. The type of the final column of a lattice must implement the Lattice trait. A lattice is like a relation, except that when a new lattice fact (v1, v2, ..., v(n-1), vn) is discovered, and a fact (v1, v2, ..., v(n-1), v'n) is already present in the database, vn and v'n are joined together to produce a single fact.

This feature enables writing programs not expressible in Datalog. For example we can use this feature to compute the lengths of shortest paths between nodes in a graph.

ascent! {
   lattice shortest_path(i32, i32, Dual<u32>);
   relation edge(i32, i32, u32);

   shortest_path(x, y, Dual(*w)) <-- edge(x, y, w);

   shortest_path(x, z, Dual(w + l)) <-- 
      edge(x, y, w), 
      shortest_path(y, z, ?Dual(l));
}

In this example, Dual<T> is the dual of the lattice T. We use Dual<T> because we are interested in shortest paths, given two path lengths l1 and l2 for any given pair of nodes, we only store min(l1, l2).

Parallel Ascent

Ascent is capable of producing parallel code. The macros ascent_par! and ascent_run_par! produce parallelized code. Naturally, column types must be Send + Sync to work in parallel Ascent.

Parallel Ascent utilizes rayon, so the parallelism level can be controlled either via rayon's ThreadPoolBuilder or using the RAYON_NUM_THREADS environment variable (see here for more info).

BYODS

ascent-byods-rels on crates.io

BYODS (short for Bring Your Own Data Structures to Datalog) is a feature of Ascent that enables relations to be backed by custom data structures. This feature allows improving the algorithmic complexity of Ascent programs by optimizing the data structures used to back relations. For example, a program that requires transitive closure computation of a large graph could improve its performance by choosing a union-find based data structure trrel_uf (defined in ascent-byods-rels) for the transitive closure relation:

in Cargo.toml:

[dependencies]
ascent-byods-rels = "*"
ascent = "*"
use ascent_byods_rels::trrel_uf;
ascent! {
   relation edge(Node, Node);
   
   #[ds(trrel_uf)] // Makes the relation transitive
   relation path(Node, Node);
   
   path(x, y) <-- edge(x, y);
}

The #[ds(trrel_uf)] attribute directs the Ascent compiler to use the data structure provider defined in the module trrel_uf for the path relation. You can find a complete example of BYODS dramatically speeding up Ascent computations here.

See BYODS.MD for more information on BYODS.

ascent_run!

In addition to ascent!, we provide the ascent_run! macro. Unlike ascent!, this macro evaluates the ascent program when invoked. The main advantage of ascent_run! is that local variables are in scope inside the Ascent program. For example, we can define a function for discovering the (optionally reflexive) transitive closure of a relation like so:

fn tc(r: Vec<(i32, i32)>, reflexive: bool) -> Vec<(i32, i32)> {
   ascent_run! {
      relation r(i32, i32) = r;
      relation tc(i32, i32);
      tc(x, y) <-- r(x, y);
      tc(x, z) <-- r(x, y), tc(y, z);
      tc(x, x), tc(y, y) <-- if reflexive, r(x, y);
   }.tc
}

In the above example, we initialize the relation r directly to shorten the program.

We also provide ascent_run_par!, the parallel version of ascent_run!.

Conditions and Generative Clauses

The syntax is designed to be familiar to Rust users. In this example, edge is populated with non-reflexive edges from node. Note that any type that implements Clone + Eq + Hash can be used as a relation column.

ascent! {
   relation node(i32, Rc<Vec<i32>>);
   relation edge(i32, i32);
   
   edge(x, y) <--
      node(x, neighbors),
      for &y in neighbors.iter(),
      if x != y;
}

Negation and Aggregation

Ascent supports stratified negation and aggregation. Aggregators are defined in ascent::aggregators. You can find sum, min, max, count, and mean there.

In the following example, the average grade of students is stored in avg_grade:

use ascent::aggregators::mean;
type Student = u32;
type Course = u32;
type Grade = u16;
ascent! {
   relation student(Student);
   relation course_grade(Student, Course, Grade);
   relation avg_grade(Student, Grade);

   avg_grade(s, avg as Grade) <--
      student(s),
      agg avg = mean(g) in course_grade(s, _, g);
}

You can define your own aggregators if the provided aggregators are not sufficient. For example, an aggregator for getting the 2nd highest value of a column can have the following signature:

fn second_highest<'a, N: 'a>(inp: impl Iterator<Item = (&'a N,)>) -> impl Iterator<Item = N>
where N: Ord + Clone

Aggregators can even be parameterized! For an example of a parameterized aggregator, lookup the definition of percentile in ascent::aggregators.

Macro definitions

It may be useful to define macros that expand to either body items or head items. Ascent allows you to do this.

You can find more about macros in Ascent macros here.

Misc

  • #![measure_rule_times] causes execution times of individual rules to be measured. Example:

    ascent! {
       #![measure_rule_times]
       // ...
    }
    let mut prog = AscentProgram::default();
    prog.run();
    println!("{}", prog.scc_times_summary());

    Note that scc_times_summary() is generated for all Ascent programs. With #![measure_rule_times] it reports execution times of individual rules too.

  • With #![generate_run_timeout], a run_timeout function is generated that stops after the given amount of time.

  • The feature wasm-bindgen allows Ascent programs to run in WASM environments.

  • struct declarations can be added to the top of ascent! definitions. This allows changing the name and visibility of the generated type and introduction of type/lifetime parameters and constraints.

    ascent! {
       struct GenericTC<N: Clone + Eq + Hash>;
       relation edge(N, N);
       // ...
    }

    Hint: If you get a "private type ... in public interface (error E0446)" warning, you can fix it by making the generated Ascent type private, as done in the above example.

ascent's People

Contributors

ammkrn avatar langston-barrett avatar mkatychev avatar regexident avatar s-arash avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

ascent's Issues

Lattice constrains not enforced on input in ascent_run

The following fails in 0.5.0:

 assert_eq!(ascent_run! {
        lattice a(i64) = vec![(0,),(1,)];
    }.a, vec![(1,)]);

Presumably because the lattice joins are not executed on the incoming vector, as it is seen as 'input'
I think it would make sense to generate an intermediate input relation, so the lattice's rules are upheld.

Macro limitations

The README.MD file hints at macros being useful for scaffolding head/body items …

It may be useful to define macros that expand to either body items or head items. Ascent allows you to do this.

… which the MACROS.MD then provides further examples for and gives the following motivation …

Macros can be defined inside Ascent programs to avoid having to repeat code.

As somebody accustomed to Rust's native macro the above would make one assume that —just like in Rust itself— ascent's macros could be used in effectively every position in the code. In reality however it seems that ascent's macros are limited to clause position.

As such this works:

ascent! {
    // Facts:

    relation unique(isize);
    
    // Macros:

    macro shared($x: expr) {
        shared(Rc::new($x))
    }

    // Rules:
    
    relation shared(Rc<isize>);

    shared!(*x) <-- unique(x);
}

This however doesn't:

ascent! {
    // Facts:

    relation unique(isize);
    
    // Macros:

    macro rc($x: expr) {
        Rc::new($x)
    }

    // Rules:
    
    relation shared(Rc<isize>);

    shared(rc!(*x)) <-- unique(x);
    //     ^^
    // error: cannot find macro `rc` in this scope
}

This is rather surprising and unexpected.

As such I would expect the above to either be accepted or at least provide a more useful error message than the current "cannot find macro in this scope".

Incorrect result?

Hi guys,

Consider the following program:

use ascent::ascent;
ascent!{
	relation a__(i32, i32);
	relation c__(i32, i32, i32);
	relation e__(i32);
	relation h__(i32, i32, i32);

	e__(a) <-- a__(b, a);
	h__(e, e, e) <-- a__(d, e), c__(e, f, e), e__(e);

}

fn main() {
	let mut prog = AscentProgram::default();
	prog.a__ = vec![(88,5), (37,24), (11,91)];
	prog.c__ = vec![(32,83,88), (2,8,5)];
	prog.e__ = vec![(44,), (83,)];
	prog.h__ = vec![(38,88,18), (76,18,65), (86,73,91), (98,26,91), (76,10,14)];

	prog.run();
	println!("{:?}", prog.h__);
}

If I run the program, I get the following for the relation h__:
[(38, 88, 18), (76, 18, 65), (86, 73, 91), (98, 26, 91), (76, 10, 14), (5, 5, 5)]

Now, if I add a subgoal c__(e, a, e) in the rule for h__ to get the following new program:

use ascent::ascent;
ascent!{
	relation a__(i32, i32);
	relation c__(i32, i32, i32);
	relation e__(i32);
	relation h__(i32, i32, i32);


	e__(a) <-- a__(b, a);
	h__(e, e, e) <-- a__(d, e), c__(e, f, e), e__(e), c__(e, a, e);
}

fn main() {
	let mut prog = AscentProgram::default();
	prog.a__ = vec![(88,5), (37,24), (11,91)];
	prog.c__ = vec![(32,83,88), (2,8,5)];
	prog.e__ = vec![(44,), (83,)];
	prog.h__ = vec![(38,88,18), (76,18,65), (86,73,91), (98,26,91), (76,10,14)];

	prog.run();
	println!("{:?}", prog.h__);
}

The result for h__ changes and I get:
[(38, 88, 18), (76, 18, 65), (86, 73, 91), (98, 26, 91), (76, 10, 14)]
Adding the subgoal c__(e, a, e) should not change the result for relation h__.
The tuple (5, 5, 5) disappears for the second program which should not have appeared for the first program in the first place.

My Cargo.toml file:

[package]
name = "ascent_project"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
ascent = "0.2"

Please let me know if you cannot reproduce this or if I am doing anything wrong.

ascent_macro: Upgrade syn to >= v2

syn has undergone a major version bump. Since it's a slow package to compile, it'd be nice to have just one version of it in my dependency tree.

MISP computation

Hello!

Could you point me where in this repository's code do you compute the optimal solution to the input program's MISP, as described in Subotič et al's "Automatic Index Selection for Large-Scale Datalog Computation"?

From what I understood, HIR transforms each program's rule to a relational form, and then MIR...maps it to primitive searches?

Composition Feature

I've browsed a bit through the available docs, but couldn't find whether this is possible.

So this is a question / maybe feature request.

Is there some way of composing multiple sets of predicates? Ideally you'd be able to multiple blocks of interdependent ascent code spread over multiple files.

Mimalloc?

Hi! Thank you for writing this library. It is awesome.

I've run into something weird which is probably not even an issue with ascent– crates.io shows mimalloc as a dependency of ascent, even though it doesn't appear in any of the Cargo.toml files in the workspace: https://crates.io/crates/ascent/0.2.0/dependencies

Screen Shot 2022-05-10 at 10 47 39 AM

Any ideas?

Private type `…` in public interface (error E0446)

When using non-pub types in ascent macros the generated code emits a "private type in public interface (error E0446)" warning that will turn into an error in future versions of Rust.

To reproduce compile this code:

fn main() {
    #[derive(Hash, Eq, PartialEq, Clone)]
    struct PrivateType;

    #[allow(clippy::all)]
    let _ = ascent::ascent_run! {
        // struct Program;

        relation dummy(PrivateType);
    };
}
warning: private type `PrivateType` in public interface (error E0446)
  --> src/main.rs:6:13
   |
6  |       let _ = ascent::ascent_run! {
   |  _____________^
7  | |         // struct Program;
8  | |
9  | |         relation dummy(PrivateType);
10 | |     };
   | |_____^
   |
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #34537 <https://github.com/rust-lang/rust/issues/34537>
   = note: `#[warn(private_in_public)]` on by default
   = note: this warning originates in the macro `ascent::ascent_run` (in Nightly builds, run with -Z macro-backtrace for more info)

Un-commenting the // struct Program; silences the warning.

I'm not sure if there's a good solution for this (which doesn't get in the way of scenarios for public types), but it might be a good idea to add a note in the docs for this, since the fix might not be obvious immediately?

ascent_macro: Fix minimal versions (by upgrading syn to v1.0.109, itertools to v0.12)

While a bump to >= v2.x would obviously be preferable I'd like to suggest bumping it at least to the highest v1.x version available for the time being.

Deleting Cargo.lock and running cargo minimal-versions check --workspace --all-features --ignore-private -v (crates.io) reveals that ascent_macro is using APIs from syn and itertools that are not available in their versions specified in Cargo.toml

Full terminal log
$ cargo minimal-versions check --workspace --all-features --ignore-private -v                                                                            
(base) 
info: modifying from <SNIP>/ascent/Cargo.toml
info: modifying from <SNIP>/ascent_base/Cargo.toml
info: modifying from <SNIP>/ascent_macro/Cargo.toml
info: running `rustup run nightly cargo update -Z minimal-versions`
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
    Updating crates.io index
info: running `~/.rustup/toolchains/stable-x86_64-apple-darwin/bin/cargo hack check --workspace --all-features`
info: running `cargo check --all-features` on ascent (1/3)
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
   Compiling crossbeam-utils v0.8.7
   Compiling proc-macro2 v1.0.52
   Compiling memoffset v0.6.1
    Checking lazy_static v1.4.0
   Compiling unicode-ident v1.0.0
   Compiling libc v0.2.95
    Checking scopeguard v1.1.0
   Compiling crossbeam-epoch v0.9.5
   Compiling syn v1.0.0
   Compiling unicode-xid v0.2.0
   Compiling ahash v0.8.4
   Compiling indexmap v1.6.2
   Compiling paste v1.0.0
    Checking either v1.0.0
    Checking zerocopy v0.7.3
   Compiling hashbrown v0.9.1
    Checking smallvec v1.6.1
    Checking allocator-api2 v0.2.9
   Compiling fixedbitset v0.4.0
   Compiling heck v0.4.0
   Compiling itertools v0.10.0
    Checking lock_api v0.4.10
    Checking instant v0.1.0
    Checking segvec v0.1.0
    Checking sync-unsafe-cell v0.1.0
   Compiling ascent_base v0.5.0 (<SNIP>/ascent_base)
    Checking crossbeam-channel v0.5.0
   Compiling petgraph v0.6.0
    Checking crossbeam-deque v0.8.1
    Checking num_cpus v1.2.0
    Checking parking_lot_core v0.9.8
    Checking rayon-core v1.11.0
   Compiling quote v1.0.26
    Checking rayon v1.7.0
   Compiling proc-macro-error-attr v1.0.4
    Checking hashbrown v0.14.0
    Checking dashmap v5.5.0
   Compiling proc-macro-error v1.0.4
   Compiling derive-syn-parse v0.1.5
   Compiling duplicate v0.4.0
error[E0599]: no method named `get_ident` found for struct `syn::Path` in the current scope
   --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/derive-syn-parse-0.1.5/src/fields.rs:192:26
    |
192 |     let name = attr.path.get_ident()?.to_string();
    |                          ^^^^^^^^^ help: there is a method with a similar name: `is_ident`

error[E0599]: no method named `get_ident` found for struct `syn::Path` in the current scope
   --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/derive-syn-parse-0.1.5/src/variants.rs:154:26
    |
154 |     let name = attr.path.get_ident()?.to_string();
    |                          ^^^^^^^^^ help: there is a method with a similar name: `is_ident`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `derive-syn-parse` (lib) due to 2 previous errors
warning: build failed, waiting for other jobs to finish...
error: process didn't exit successfully: `~/.rustup/toolchains/stable-x86_64-apple-darwin/bin/cargo check --all-features --manifest-path ascent/Cargo.toml` (exit status: 101)
info: restoring <SNIP>/ascent/Cargo.toml
info: restoring <SNIP>/ascent_base/Cargo.toml
info: restoring <SNIP>/ascent_macro/Cargo.toml
error: process didn't exit successfully: `~/.rustup/toolchains/stable-x86_64-apple-darwin/bin/cargo hack check --workspace --all-features` (exit status: 1)

Bumping syn and itertools from …

[dependencies]
syn = { version = "1.0", features = [
    "derive",
    "full",
    "extra-traits",
    "visit-mut",
] }
itertools = "0.10"
# ...

… to …

[dependencies]
syn = { version = "1.0.109", features = [
    "derive",
    "full",
    "extra-traits",
    "visit-mut",
] }
itertools = "0.12"
# ...

fixes it.

Full terminal log
$ cargo minimal-versions check --workspace --all-features --ignore-private -v                                                      (base) 
info: modifying from <SNIP>/ascent/ascent/Cargo.toml
info: modifying from <SNIP>/ascent/ascent_base/Cargo.toml
info: modifying from <SNIP>/ascent/ascent_macro/Cargo.toml
info: running `rustup run nightly cargo update -Z minimal-versions`
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
    Updating crates.io index
info: running `~/.rustup/toolchains/stable-x86_64-apple-darwin/bin/cargo hack check --workspace --all-features`
info: running `cargo check --all-features` on ascent (1/3)
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Finished dev [unoptimized + debuginfo] target(s) in 0.68s

info: running `cargo check --all-features` on ascent_base (2/3)
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s

info: running `cargo check --all-features` on ascent_macro (3/3)
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
    Finished dev [unoptimized + debuginfo] target(s) in 0.05s
info: restoring <SNIP>/ascent/ascent/Cargo.toml
info: restoring <SNIP>/ascent/ascent_base/Cargo.toml
info: restoring <SNIP>/ascent/ascent_macro/Cargo.toml

Arash's current email address?

Hi Arash--my apologies for writing you here, I did not know where else to look. We are trying to find your current email address (VLDB Slog resubmission). If you can please email me ([email protected]) so we can make an account for you, I would appreciate it.

WASM compatibility: `std::time`

The very minimal use of std::time in Ascent unfortunately panics on WASM.

Possible fixes include either a feature flag to gate use of std::time (this is only slightly complicated by run_timeout), or using an alternative library which provides a WASM-safe implementation such as https://github.com/sebcrozet/instant.

Just a thought. If you would prefer a PR for one or the other of these options, let me know. Thanks!

New release with parallel Ascent

Looks like parallel Ascent has been merged to the main branch. It would be great to have this feature available in a release on crates.io!

Compiling ascent at runtime

Is there any way I can make the ascent embedded program dynamically generated?
Would there be an internal function that might help, even if not part of the user-facing API?

Thanks!

Non-final lattice results visible to downstream rules

In the following program, the relation stop2 is a copy of stop.
And then obstruct is derived from stop the same way as obstruct2 is from stop2.
Therefore one would expect that the two relations have the same output, but instead the following happens:

1: [(1, 0, 2)]
2: []

But stop(1,2) is not part of the final result, (even though it is implied by roll(1,6), block(2), because it undergoes lattice merge with stop(1,4) implied by roll(1,6), block(4).

Yet it appears stop(1,2) is still used in deriving obstruct, even though its value isn't final yet.

use ascent::ascent_run;

fn main() {
    let prog = ascent_run! {
        relation block(i64) = vec![(2,),(4,)];
        relation roll(usize,i64) = vec![(0,3),(1,6)];
        lattice stop(usize,i64);
        stop(id,x) <--roll(id,x_start), block(x), if x < x_start;
        relation stop2(usize,i64);
        stop2(id,x) <-- stop(id,x);
        relation obstruct(usize,usize,i64);
        obstruct(id,id1,x2) <-- stop(id,x2), stop(id1,x2), if id1 < id;
        relation obstruct2(usize,usize,i64);
        obstruct2(id,id1,x2) <-- stop2(id,x2), stop2(id1,x2), if id1 < id;
    };
    println!(
        "1: {:?}\n2: {:?}",
        prog.obstruct,
        prog.obstruct2,
    );
}

ascent = "0.2" broke?

Hi @s-arash,

I think something broke upon your latest commit.
Now the instructions in Using Ascent does not work anymore.
I get:

$ cargo run
    Updating crates.io index
   Compiling ascent_base v0.2.1
   Compiling hashbrown v0.12.1
error: type parameters must be declared prior to const parameters
   --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/product.rs:102:23
    |
102 | impl <const N: usize, T: PartialOrd> PartialOrd for Product<[T; N]> {
    |      -----------------^------------- help: reorder the parameters: lifetimes, then types, then consts: `<T: PartialOrd, const N: usize>`

error: type parameters must be declared prior to const parameters
   --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/product.rs:134:23
    |
134 | impl <const N: usize, T: Lattice> Lattice for Product<[T; N]> {
    |      -----------------^---------- help: reorder the parameters: lifetimes, then types, then consts: `<T: Lattice, const N: usize>`

error: type parameters must be declared prior to const parameters
   --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/product.rs:150:23
    |
150 | impl <const N: usize, T: BoundedLattice> BoundedLattice for Product<[T; N]> {
    |      -----------------^----------------- help: reorder the parameters: lifetimes, then types, then consts: `<T: BoundedLattice, const N: usize>`

error: type parameters must be declared prior to const parameters
  --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:11:43
   |
11 | pub struct BoundedSet<const BOUND: usize, T: PartialEq + Eq + Hash + Ord>(Option<Set<T>>);
   |                      ---------------------^------------------------------ help: reorder the parameters: lifetimes, then types, then consts: `<T: PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: type parameters must be declared prior to const parameters
  --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:10:10
   |
10 | #[derive(Clone, PartialEq, Eq, Hash)]
   |          ^^^^^ help: reorder the parameters: lifetimes, then types, then consts: `<T: ::core::clone::Clone + PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: type parameters must be declared prior to const parameters
  --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:10:17
   |
10 | #[derive(Clone, PartialEq, Eq, Hash)]
   |                 ^^^^^^^^^ help: reorder the parameters: lifetimes, then types, then consts: `<T: ::core::cmp::PartialEq + PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: type parameters must be declared prior to const parameters
  --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:10:28
   |
10 | #[derive(Clone, PartialEq, Eq, Hash)]
   |                            ^^ help: reorder the parameters: lifetimes, then types, then consts: `<T: ::core::cmp::Eq + PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: type parameters must be declared prior to const parameters
  --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:10:32
   |
10 | #[derive(Clone, PartialEq, Eq, Hash)]
   |                                ^^^^ help: reorder the parameters: lifetimes, then types, then consts: `<T: ::core::hash::Hash + PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: type parameters must be declared prior to const parameters
  --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:14:26
   |
14 | impl<const BOUND: usize, T: PartialEq + Eq + Hash + Ord> Default for BoundedSet<BOUND, T> {
   |     ---------------------^------------------------------ help: reorder the parameters: lifetimes, then types, then consts: `<T: PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: type parameters must be declared prior to const parameters
  --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:18:26
   |
18 | impl<const BOUND: usize, T: PartialEq + Eq + Hash + Ord> BoundedSet<BOUND, T> {
   |     ---------------------^------------------------------ help: reorder the parameters: lifetimes, then types, then consts: `<T: PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: type parameters must be declared prior to const parameters
  --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:60:26
   |
60 | impl<const BOUND: usize, T: PartialEq + Eq + Hash + Ord> PartialOrd for BoundedSet<BOUND, T> {
   |     ---------------------^------------------------------ help: reorder the parameters: lifetimes, then types, then consts: `<T: PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: type parameters must be declared prior to const parameters
  --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:74:26
   |
74 | impl<const BOUND: usize, T: PartialEq + Eq + Hash + Ord> Lattice for BoundedSet<BOUND, T> {
   |     ---------------------^------------------------------ help: reorder the parameters: lifetimes, then types, then consts: `<T: PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: type parameters must be declared prior to const parameters
   --> /home/taylorswift/.cargo/registry/src/github.com-1ecc6299db9ec823/ascent_base-0.2.1/src/lattice/bounded_set.rs:103:26
    |
103 | impl<const BOUND: usize, T: PartialEq + Eq + Hash + Ord> BoundedLattice for BoundedSet<BOUND, T> {
    |     ---------------------^------------------------------ help: reorder the parameters: lifetimes, then types, then consts: `<T: PartialEq + Eq + Hash + Ord, const BOUND: usize>`

error: could not compile `ascent_base` due to 13 previous errors
warning: build failed, waiting for other jobs to finish...
error: build failed

When i change ascent = "0.2" in Cargo.toml to a previous commit, e.g.
ascent = {git = "https://github.com/s-arash/ascent", rev="da6078289bbf87bcc843d5cd3f079a08a232051c"}
Then everything works fine.

Are you already aware of this problem or am I doing something wrong?

Comparison to Crepe?

Crepe is another Rust-macro-embedded Datalog-like eDSL. It would be nice to point to it from the README as an alternative, or even better, to provide a short comparison between the two projects.

Ascent should omit trait bounds on the generated program's type declaration

For generic programs ascent currently requires something along the following:

ascent! {
    pub struct AscentProgram<T: Clone + Eq + Hash>;

    relation dummy(T);

    // ...
}

or

ascent! {
    pub struct AscentProgram<T> where T: Clone + Eq + Hash;

    relation dummy(T);

    // ...
}

which then generates code that looks something like this:

pub struct AscentProgram<T>
where
    T: Clone + Eq + Hash,
{
    pub dummy: Vec<(T,), Global>,

    // ...
}

impl<T> AscentProgram<T>
where
    T: Clone + Eq + Hash,
{
    pub fn run(&mut self){
        // ...
    }

    // ...
}

// ...

This unfortunately tends to lead to problems with rather nasty effects one one's code and is thus generally considered an anti-pattern.

As such the official Rust API guidelines has a chapter on the topic (it talks specifically about derived trait bounds, but the viral breaking change effect is not limited to derived traits):

Data structures do not duplicate derived trait bounds

Generic data structures should not use trait bounds that can be derived or do not otherwise add semantic value. […]

// Prefer this:
#[derive(Clone, Debug, PartialEq)]
struct Good<T> { /* ... */ }

// Over this:
#[derive(Clone, Debug, PartialEq)]
struct Bad<T: Clone + Debug + PartialEq> { /* ... */ }

Generally speaking, adding a trait bound to a data structure is a breaking change because every consumer of that structure will need to start satisfying the additional bound. […]

There is a grey area around other non-derivable trait bounds that are not strictly required by the structure definition, like Read or Write. They may communicate the intended behavior of the type better in its definition but also limits future extensibility. Including semantically useful trait bounds on data structures is still less problematic than including derivable traits as bounds. […]

In short the problem is that trait bounds attached to a type's declaration spread virally (i.e. they require your surrounding code to now also be bounded by these traits), while those only attached to a type's implementation generally don't.

As such the generated code should preferably look like this instead:

pub struct AscentProgram<T>
// notice the lack of a where clause here!
{
    pub dummy: Vec<(T,), Global>,

    // ...
}

impl<T> AscentProgram<T>
where
    T: Clone + Eq + Hash,
{
    pub fn run(&mut self){
        // ...
    }

    // ...
}

// ...

This change should have no affect on ascent itself, but make integrating it much more pleasant and less intrusive.

Calling run() multiple times

If I understand correctly, ascent is't a 'differential datalog'-style implementation, but is optimized for batch processing.
The API does however allow .run() to be called multiple times, so users will eventually do it.
I did a little experiment:

use std::time::Instant;

use ascent::ascent;

fn main() {
    ascent!{
        #![measure_rule_times]
        relation x(u64,u64);
        relation y(u64,u64);
        y(i,n) <-- x(i,n), x(i,n+100);
        x(i,n+200) <-- y(i,n), if *n < 400000;  
    }
    let mut prog = AscentProgram::default();
    fn time(prog: &mut AscentProgram, title: &str) {
        let start = Instant::now();
        prog.run();
        println!("{:?} {:?}", prog.x.len(), prog.y.len());
        println!("{}", prog.scc_times_summary());
        println!("{}: {:?}",title, start.elapsed());
    }
    prog.x = vec![(0, 1,), (0, 101,)];
    time(&mut prog, "Initial run");

    prog.x.push((1,1,));    
    time(&mut prog,"Add of non-consequential fact");

    prog.x.push((1,101,));
    time(&mut prog,"Add of consequential fact");

    let mut prog = AscentProgram::default();
    prog.x = vec![(0, 1,), (0, 101,), (1,1,), (1,101)];
    time(&mut prog,"Full rerun");
}

The output is:

4002 4001
scc 0 time: 1.436391002s
  some of rule times: 1.42136186s
  rule x <-- y_indices_none_delta, if ⋯
    time: 4.024887ms
  rule y <-- x_indices_none_delta, x_indices_0_1_total+delta
    time: 2.227919ms
  rule y <-- x_indices_none_total, x_indices_0_1_delta
    time: 1.415109054s
-----------------------------------------

Initial run: 1.436422348s
4003 4001
scc 0 time: 1.443135223s
  some of rule times: 1.42810157s
  rule x <-- y_indices_none_delta, if ⋯
    time: 5.573965ms
  rule y <-- x_indices_none_delta, x_indices_0_1_total+delta
    time: 7.418424ms
  rule y <-- x_indices_none_total, x_indices_0_1_delta
    time: 1.415109181s
-----------------------------------------

Add of non-consequential fact: 9.375828ms
8004 8002
scc 0 time: 11.459918911s
  some of rule times: 11.423892339s
  rule x <-- y_indices_none_delta, if ⋯
    time: 12.857089ms
  rule y <-- x_indices_none_delta, x_indices_0_1_total+delta
    time: 17.993869ms
  rule y <-- x_indices_none_total, x_indices_0_1_delta
    time: 11.393041381s
-----------------------------------------

Add of consequential fact: 10.019436059s
8004 8002
scc 0 time: 3.073175727s
  some of rule times: 3.047507618s
  rule x <-- y_indices_none_delta, if ⋯
    time: 7.811237ms
  rule y <-- x_indices_none_delta, x_indices_0_1_total+delta
    time: 4.606486ms
  rule y <-- x_indices_none_total, x_indices_0_1_delta
    time: 3.035089895s
-----------------------------------------

Full rerun: 3.073211278s

It seems to me that:

  • running after adding to the input relation sometimes (my second run), does a lot less work than a full rerun.
  • at other times, when a lot of new facts were implied by the new fact, it does more work than a full rerun.

While (as I expected), removing facts from relations isn't supported.

 ascent!{
        struct A;
        relation x(u64);
        relation y(u64);
        y(n) <-- x(n);
    }
    let mut prog = A::default();
    prog.x = vec![(1,), (2,)];
    prog.run();
    println!("{:?}", prog.y);
    prog.x = vec![(1,)];
    prog.run();
    println!("{:?}", prog.y)

produces
[(1,), (2,)]
[(1,), (2,)]

My question is, would it be possible to either:

  • add some support for incrmentalized computation, so that you could indicate 'I've added facts to this relation' and 'I've removed facts from this relation', causing some of the indices to be recomputed?
  • prevent the user from calling .run() multiple times, by making run take ownership of the program instead of taking it by mutable reference

Project's current formatting makes it hard to contribute non-trivial changes

The project's current code formatting suffers from a couple of issues, affect:

tl;dr: For the sake of making contributions hassle-free it would arguably be preferable to revert to Rust's default format (as applied by rustfmt without a custom rustfmt.toml file) and used by >90% of Rust projects.

  1. It uses a non-standard format

    The Rust project recommends using the default formatting configuration to ease collaboration.

  2. It provides a custom rustfmt.toml file but doesn't actually use it

    While the project provides a rustfmt.toml file

    ./rustfmt.toml

    # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md
    tab_spaces = 3
    fn_call_width = 120
    chain_width = 120
    max_width = 120
    fn_single_line = true

    Running cargo fmt results in 30+ reformatted files, which should not happen if the code had previously been formatted with it.

    As such it is currently impossible to have rustfmt/cargo fmt apply the same formatting as used by the project. And anybody opening a PR on the project gets a nasty surprise when they notice that their IDE auto-formatted the code (as is usually desirable) and now the semantic changes are lost in thousands of formatting changes (as it just happened to me, again).

  3. The rustfmt.toml file requires nightly and is also invalid

    Further more running cargo fmt results in a bunch of warnings:

    $ cargo fmt
    
    Warning: can't set `fn_single_line = true`, unstable features are only available in nightly channel.
    Warning: can't set `fn_single_line = true`, unstable features are only available in nightly channel.
    `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
    `chain_width` cannot have a value that exceeds `max_width`. `chain_width` will be set to the same value as `max_width`
    Warning: can't set `fn_single_line = true`, unstable features are only available in nightly channel.
    `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
    `chain_width` cannot have a value that exceeds `max_width`. `chain_width` will be set to the same value as `max_width`
    `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
    `chain_width` cannot have a value that exceeds `max_width`. `chain_width` will be set to the same value as `max_width`
    `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
    `chain_width` cannot have a value that exceeds `max_width`. `chain_width` will be set to the same value as `max_width`
    `fn_call_width` cannot have a value that exceeds `max_width`. `fn_call_width` will be set to the same value as `max_width`
    `chain_width` cannot have a value that exceeds `max_width`. `chain_width` will be set to the same value as `max_width`
    
  4. The project mixes CRLF and LF

    • 37 files use CRLF
    • 31 use LF.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.