├── .github
└── workflows
│ └── rust.yml
├── .gitignore
├── Cargo.toml
├── Dockerfile
├── LICENSE
├── README.md
├── book
├── .gitignore
├── book.toml
└── src
│ ├── SUMMARY.md
│ ├── background.md
│ ├── background
│ ├── chase.md
│ ├── satisfiability.md
│ └── termination.md
│ ├── build.md
│ ├── example.md
│ ├── example
│ ├── golden-head.md
│ ├── hold-the-door.md
│ └── valar-morghulis.md
│ ├── intro.md
│ ├── run.md
│ ├── run
│ ├── bounded.md
│ └── scheduler.md
│ ├── syntax.md
│ └── syntax
│ ├── grammar.md
│ ├── precedence.md
│ ├── syntax
│ └── variations.md
├── razor-chase
├── Cargo.toml
├── benches
│ └── perf_test.rs
└── src
│ ├── chase.rs
│ ├── chase
│ ├── bounder.rs
│ ├── impl.rs
│ ├── impl
│ │ ├── basic.rs
│ │ ├── batch.rs
│ │ ├── collapse.rs
│ │ ├── relational.rs
│ │ └── relational
│ │ │ ├── attribute.rs
│ │ │ ├── constants.rs
│ │ │ ├── evaluator.rs
│ │ │ ├── expression.rs
│ │ │ ├── model.rs
│ │ │ ├── pre_processor.rs
│ │ │ ├── rewrite.rs
│ │ │ ├── sequent.rs
│ │ │ └── symbol.rs
│ ├── scheduler.rs
│ └── strategy.rs
│ ├── lib.rs
│ ├── test_prelude.rs
│ ├── trace.rs
│ └── trace
│ └── subscriber.rs
├── razor-fol
├── Cargo.toml
├── build.rs
└── src
│ ├── grammar.lalrpop
│ ├── lib.rs
│ ├── parser.rs
│ ├── syntax.rs
│ ├── syntax
│ ├── formula.rs
│ ├── formula
│ │ ├── clause.rs
│ │ ├── fof.rs
│ │ └── qff.rs
│ ├── macros.rs
│ ├── signature.rs
│ ├── symbol.rs
│ ├── term.rs
│ └── theory.rs
│ ├── test_macros.rs
│ ├── transform.rs
│ └── transform
│ ├── cnf.rs
│ ├── dnf.rs
│ ├── gnf.rs
│ ├── linear.rs
│ ├── nnf.rs
│ ├── pnf.rs
│ ├── range_restrict.rs
│ ├── relational.rs
│ ├── simplify.rs
│ └── snf.rs
├── razor
├── Cargo.toml
└── src
│ ├── command.rs
│ ├── constants.rs
│ ├── main.rs
│ ├── terminal.rs
│ └── utils.rs
└── theories
├── bounded
├── thy0.config
├── thy0.model
├── thy0.raz
├── thy1.config
├── thy1.model
├── thy1.raz
├── thy2.config
├── thy2.model
├── thy2.raz
├── thy3.config
├── thy3.model
├── thy3.raz
├── thy4.config
├── thy4.model
├── thy4.raz
├── thy5.config
├── thy5.model
├── thy5.raz
├── thy6.config
├── thy6.model
├── thy6.raz
├── thy7.config
├── thy7.model
└── thy7.raz
├── core
├── thy0.model
├── thy0.raz
├── thy1.model
├── thy1.raz
├── thy10.model
├── thy10.raz
├── thy11.model
├── thy11.raz
├── thy12.model
├── thy12.raz
├── thy13.model
├── thy13.raz
├── thy14.model
├── thy14.raz
├── thy15.model
├── thy15.raz
├── thy16.model
├── thy16.raz
├── thy17.model
├── thy17.raz
├── thy18.model
├── thy18.raz
├── thy19.model
├── thy19.raz
├── thy2.model
├── thy2.raz
├── thy20.model
├── thy20.raz
├── thy21.model
├── thy21.raz
├── thy22.model
├── thy22.raz
├── thy23.model
├── thy23.raz
├── thy24.model
├── thy24.raz
├── thy25.model
├── thy25.raz
├── thy26.model
├── thy26.raz
├── thy27.model
├── thy27.raz
├── thy28.model
├── thy28.raz
├── thy29.model
├── thy29.raz
├── thy3.model
├── thy3.raz
├── thy30.model
├── thy30.raz
├── thy31.model
├── thy31.raz
├── thy32.model
├── thy32.raz
├── thy33.model
├── thy33.raz
├── thy35.model
├── thy35.raz
├── thy36.model
├── thy36.raz
├── thy37.model
├── thy37.raz
├── thy38.model
├── thy38.raz
├── thy39.model
├── thy39.raz
├── thy4.model
├── thy4.raz
├── thy40.model
├── thy40.raz
├── thy41.model
├── thy41.raz
├── thy42.model
├── thy42.raz
├── thy43.model
├── thy43.raz
├── thy44.model
├── thy44.raz
├── thy45.model
├── thy45.raz
├── thy46.model
├── thy46.raz
├── thy47.model
├── thy47.raz
├── thy5.model
├── thy5.raz
├── thy6.model
├── thy6.raz
├── thy7.model
├── thy7.raz
├── thy8.model
├── thy8.raz
├── thy9.model
└── thy9.raz
└── examples
├── golden-lion.raz
├── grandpa.raz
├── hodor-linear.raz
├── hodor-time-loop.raz
├── lannisters.raz
├── toy.raz
└── valar-morghulis.raz
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Build
13 | run: cargo build --verbose
14 | - name: Run tests
15 | run: cargo test --verbose
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7 | Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 |
12 | /target
13 | **/*.rs.bk
14 | rusty-razor.iml
15 | .idea/
16 | .criterion/
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "razor-fol",
4 | "razor-chase",
5 | "razor",
6 | ]
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM rust:1.37
2 |
3 | COPY . .
4 |
5 | RUN cargo test
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Salman Saghafi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rusty-razor
2 |
3 | Rusty Razor is a tool for constructing finite models for first-order theories. The model-finding algorithm is inspired
4 | by [the chase](https://en.wikipedia.org/wiki/Chase_(algorithm)) for database systems. Given a first-order theory,
5 | Razor constructs a set of *homomorphically minimal* models for the theory.
6 |
7 | Check out Razor's [documentation](https://salmans.github.io/rusty-razor/intro.html) for more information about the project and introductory examples.
8 |
9 | ## Build
10 |
11 | rusty-razor is written in Rust, so you will need [Rust](https://www.rust-lang.org) 1.48.0 or newer to compile it.
12 | To build rusty-razor:
13 |
14 | ```
15 | git clone https://github.com/salmans/rusty-razor.git
16 | cd rusty-razor
17 | cargo build --release
18 | ```
19 |
20 | This puts rusty-razor's executable in `/target/release`.
21 |
22 | ## Run
23 |
24 | ### `solve`
25 |
26 | Use the `solve` command to find models for a theory written in an `` file:
27 |
28 | ```
29 | razor solve -i
30 | ```
31 |
32 | The `--count` parameter limits the number of models to construct:
33 |
34 | ```
35 | razor solve -i --count
36 | ```
37 |
38 | #### Bounded Model-Finding
39 |
40 | Unlike conventional model-finders such as [Alloy](http://alloytools.org), Razor doesn't require the user to provide a
41 | bound on the size of the models that it constructs. However, Razor may never terminate when running on theories with
42 | non-finite models -- it can be shown that a run of Razor on an unsatisfiable theory (i.e., a theory with no models)
43 | is guaranteed to terminate (although it might take a very very long time).This is a direct consequence of
44 | semi-decidability of first-order logic.
45 |
46 | To guarantee termination, limit the size of the resulting models by the number of their elements using the `--bound`
47 | option with a value for the `domain` parameter:
48 |
49 | ```
50 | razor solve -i --bound domain=
51 | ```
52 |
53 | #### Model-Finding Scheduler
54 |
55 | Use the `--scheduler` option to choose how Razor processes search branches. The `fifo` scheduler (the default scheduler)
56 | schedules new branches last and is a more suitable option for processing theories with few small satisfying models.
57 | The `lifo` scheduler schedules new branches first, and is more suitable for processing theories with many large models.
58 |
59 | ```
60 | razor solve -i --scheduler
61 | ```
62 |
63 | ## Toy Example
64 |
65 | Consider the following example:
66 |
67 | ```
68 | // All cats love all toys:
69 | forall c, t . (Cat(c) and Toy(t) implies Loves(c, t));
70 |
71 | // All squishies are toys:
72 | forall s . (Squishy(s) implies Toy(s));
73 |
74 | Cat('duchess); // Duchess is a cat
75 | Squishy('ducky); // Ducky is a squishy
76 | ```
77 |
78 | You can download the example file [here](https://github.com/salmans/rusty-razor/blob/master/theories/examples/toy.raz) and run the `razor` command-line tool on it:
79 |
80 | ```
81 | razor solve -i theories/examples/toy.raz
82 | ```
83 |
84 | Razor returns only one model with `e#0` and `e#1` as elements that denote `'duchess` and
85 | `'ducky` respectively. The facts `Cat(e#0)`, `Squishy(e#1)`, and `Toy(e#1)` in the model
86 | are directly forced by the last two formula in the input theory. The fact `Loves(e#0, e#1)`
87 | is deduced by Razor:
88 |
89 | ```
90 | Domain: e#0, e#1
91 |
92 | Elements: 'duchess -> e#0, 'ducky -> e#1
93 |
94 | Facts: Cat(e#0), Loves(e#0, e#1), Squishy(e#1), Toy(e#1)
95 | ```
96 |
97 | Razor's documentation describes the [syntax](https://salmans.github.io/rusty-razor/syntax.html)
98 | of Razor's input and contains more [examples](https://salmans.github.io/rusty-razor/example.html).
99 |
--------------------------------------------------------------------------------
/book/.gitignore:
--------------------------------------------------------------------------------
1 | book
2 |
--------------------------------------------------------------------------------
/book/book.toml:
--------------------------------------------------------------------------------
1 | [book]
2 | authors = ["Salman Saghafi"]
3 | multilingual = false
4 | src = "src"
5 | title = "rusty-razor"
6 |
--------------------------------------------------------------------------------
/book/src/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | [Introduction](./intro.md)
4 | - [Background](./background.md)
5 | - [Satisfiability of Geometric Theories](./background/satisfiability.md)
6 | - [The Chase](./background/chase.md)
7 | - [Termination](./background/termination.md)
8 | - [Syntax](./syntax.md)
9 | - [Syntactic Variations](./syntax/variations.md)
10 | - [Connective Precedence](./syntax/precedence.md)
11 | - [Grammar](./syntax/grammar.md)
12 | - [Build](./build.md)
13 | - [Run](./run.md)
14 | - [Bounded Model-Finding](./run/bounded.md)
15 | - [Model-Finding Scheduler](./run/scheduler.md)
16 | - [Example](./example.md)
17 | - [Valar Morghulis](./example/valar-morghulis.md)
18 | - [Golden Head](./example/golden-head.md)
19 | - [Hold the Door](./example/hold-the-door.md)
--------------------------------------------------------------------------------
/book/src/background.md:
--------------------------------------------------------------------------------
1 | # Background
2 |
3 | Razor implements a variant of [the chase][chase] algorithm to construct models for first-order theories with equality. The chase operates on [geometric theories][geometric], theories that contain a syntactic
4 | variation of first-order formulae which we refer to as the __Geometric Normal Form__ (GNF). Formulae
5 | in GNF have the following shape:
6 |
7 | A1 ∧ ... ∧ Am →
8 | (∃ x11, ..., x1j1 . A11 ∧ ... ∧ A1n1)
9 |
10 | ∨ (∃ x21, ..., x2j2 . A21 ∧ ... ∧ A2n2)
11 |
12 | ∨ ...
13 |
14 | ∨ (∃ xi1, ..., xiji . Ai1 ∧ ... ∧ Aini)
15 |
16 | where Aks are (positive) atomic formulae (possibly including equality) and free
17 | variables are assumed to be universally qualified over the entire formula.
18 |
19 | In the context of a run of the chase, we refer to the formulae in their GNF as
20 | __sequents__. The premise (left side) and the consequence (right side) of the implication are
21 | respectively said to be the _body_ and the _head_ of the sequent.
22 |
23 | [chase]: https://en.wikipedia.org/wiki/Chase_(algorithm)
24 | [geometric]: https://www.cs.bham.ac.uk/~sjv/GLiCS.pdf
25 |
--------------------------------------------------------------------------------
/book/src/background/chase.md:
--------------------------------------------------------------------------------
1 | ## The Chase
2 |
3 | Given a geometric theory and starting with an empty model, a run of the chase consists of repeated
4 | applications of [chase-steps](#step) by which the model is augmented with _necessary_ pieces of
5 | information until there is a contradiction or the model satisfies the theory. Inspired by
6 | [Steven Vickers][vickers], we refer to the units of information that augment models as _observations_.
7 |
8 | ### Chase Step
9 | Given a geometric theory and an existing model, a chase-step proceeds as follows:
10 |
11 | 1. A sequent from the theory is selected to be evaluated against the model.
12 |
13 | 2. The selected sequent is evaluated against the model: given an assignment from the free
14 | variables of the sequent to the elements of the model, if the body of the sequent is true and
15 | its head is not true in the model, new observations are added to the model to make the
16 | sequent's head true.
17 |
18 | 2.1. If the sequent is headless, meaning its consequence is falsehood (an empty disjunction),
19 | the chase fails on the given model.
20 |
21 | 2.2. If the head of the sequent contains more than one disjunct (with at least one
22 | non-trivial disjunction), the chase branches and satisfies each disjunct independently on clones
23 | of the model.
24 |
25 | 2.3. If no sequent can be found such that its body and head are respectively true and false
26 | in the model, the model already satisfies the theory and will be returned as an output of the
27 | chase.
28 |
29 | [vickers]: https://www.cs.bham.ac.uk/~sjv/GeoZ.pdf
30 |
--------------------------------------------------------------------------------
/book/src/background/satisfiability.md:
--------------------------------------------------------------------------------
1 | ## Satisfiability of Geometric Theories
2 |
3 | It turns out that every first-order theory can be transformed to a geometric theory that is
4 | _equisatisfiable_ to the original theory via standard syntactic manipulation.
5 | In fact, for every model `N` of the original theory, there exists a model `M` of the geometric
6 | theory such that there is a homomorphism from `M` to `N`. This is an important result that
7 | enables Razor to utilize the chase to construct homomorphically minimal models of a given
8 | first-order theory.
9 |
10 | In the context of a model-finding application, the models that the chase produces are desirable
11 | since they contain minimum amount of information, thus they induce minimal distraction.
12 | As a direct consequence of semi-decidability of satisfiability in first-order logic
13 | (see [Gödel's incompleteness theorems][godel]), satisfiability of geometric theories is
14 | semi-decidable as well.
15 |
16 | > __Note:__ A comprehensive discussion on the properties of the models that are constructed by
17 | the chase is out of the scope of this document.
18 |
19 | [godel]: https://en.wikipedia.org/wiki/Gödel%27s_incompleteness_theorems
20 |
--------------------------------------------------------------------------------
/book/src/background/termination.md:
--------------------------------------------------------------------------------
1 | ## Termination
2 |
3 | As a result of semi-decidability of geometric theories, it can be shown if a geometric theory
4 | is unsatisfiable, a run of the chase on the theory always terminates, although it may take
5 | a very very long time.
6 | However, when the theory is satisfiable, a run of the chase may not terminate, producing
7 | infinitely large models and/or infinitely many models that satisfy the theory. Nevertheless,
8 | in practice, Razor can _bound_ the size of models created by the chase to guarantee termination.
9 |
10 |
--------------------------------------------------------------------------------
/book/src/build.md:
--------------------------------------------------------------------------------
1 | # Build
2 |
3 | rusty-razor is written in Rust, so you will need [Rust](https://www.rust-lang.org) 1.37.0 or newer to compile it.
4 | To build rusty-razor:
5 |
6 | ```
7 | git clone https://github.com/salmans/rusty-razor.git
8 | cd rusty-razor
9 | cargo build --release
10 | ```
11 |
12 | This puts rusty-razor's executable in `/target/release`.
--------------------------------------------------------------------------------
/book/src/example.md:
--------------------------------------------------------------------------------
1 | # Example
2 |
3 | This section presents sample first-order theories, written in Razor's syntax.
4 | All examples are inspired by the events of Game of Thrones (_spoiler alert!!_).
5 |
6 | - [Valar Morghulis](./example/valar-morghulis.html): demonstrates a run of Razor on a simple example.
7 | - [Golden Head](./example/golden-head.html): runs Razor over an unsatisfiable theory for which no models exist.
8 | - [Hold the Door](./example/hold-the-door.html): covers more advanced features of Razor on theories with infinite models.
9 |
10 | For more examples, see Razor's [example theories](https://github.com/salmans/rusty-razor/tree/master/theories/examples).
11 |
--------------------------------------------------------------------------------
/book/src/example/golden-head.md:
--------------------------------------------------------------------------------
1 | ## Golden Head
2 |
3 | While reading "The Lineages and Histories of the Great Houses of the Seven Kingdoms", Lord Eddard Stark learns that
4 | throughout the history, all male members of House Baratheon were described as "black of hair" and concludes that King
5 | Robert is not Prince Joffrey's (biological) father. A judgment that eventually put his head on a spike.
6 |
7 | The next theory describes Ned's thought process:
8 |
9 | ```
10 | // A person "x" cannot be both "black of hair" and "golden head"
11 | ~(BlackOfHair(x) & GoldenHead(x));
12 |
13 | // Traditionally, a Baratheon child "y" inherited his/her father's ("x"'s) family name
14 | Baratheon(x) & father(y) = x -> Baratheon(y);
15 |
16 | // King Robert Baratheon is black of hair
17 | Baratheon('robert) & BlackOfHair('robert);
18 |
19 | // King Robert is Joffrey's father
20 | father('joffrey) = 'robert;
21 |
22 | // Joffrey has golden hair
23 | GoldenHead('joffrey);
24 |
25 | // Ned Stark's discovery (every Baratheon "x" is black of hair)
26 | Baratheon(x) -> BlackOfHair(x);
27 | ```
28 |
29 | We can verify Ned's conclusion by running Razor on this theory
30 | [golden-lion.raz](https://github.com/salmans/rusty-razor/blob/master/theories/examples/golden-lion.raz), asking for a
31 | scenario (i.e., model of the theory) that justifies Joffrey's golden head:
32 |
33 | ```
34 | razor solve -i theories/examples/golden-lion.raz
35 | ```
36 |
37 | Razor cannot find a model for the previous theory, meaning the theory is inconsistent. Notice that this theory
38 | is satisfiable (i.e., has a model) in the absence of Ned's discovery (try running Razor after commenting out the last
39 | line).
40 |
--------------------------------------------------------------------------------
/book/src/example/hold-the-door.md:
--------------------------------------------------------------------------------
1 | ## Hold the Door
2 |
3 | Wyllis was a young stable boy when he heard a voice from his future: "Hold the Door!" The voice transformed Wyllis to
4 | Hodor (Hold the door, Holdde door, Hoddedor, Hodor, Hodor...!), putting him on a life-long journey, leading him to the
5 | moment that he saves Bran's life. Indeed, because of this defining moment in his future, Wyllis became Hodor in his past.
6 |
7 | #### Linear Time
8 | The theory below describes Hodor's journey assuming that time progresses linearly
9 | [hodor-linear.raz](https://github.com/salmans/rusty-razor/blob/master/theories/examples/hodor-linear.raz)
10 |
11 | ```
12 | // Wyllis hears "Hold the Door" (at time `t`), then he becomes Hodor in the next
13 | // point of time
14 | HoldTheDoor(t) -> Hodor(next(t));
15 |
16 | // Hodor, after turning into Hodor at time "t", holds the Door at some time "tt"
17 | // in future ("tt > t")
18 | Hodor(t) -> ? tt . HoldTheDoor(tt) & After(t, tt);
19 |
20 | // These are the rules by which time progresses linearly:
21 | // (1) a point of time "t1" that is the next of "t0" (i.e., "next(t0)") is a point of
22 | // time after "t0" ("t1 > t0")
23 | next(t0) = t1 -> After(t0, t1);
24 |
25 | // (2) if a point of time "t1" is after "t0", it is either immediately
26 | // after "t0" (i.e., "next(t0)") or there exists some point of time "t2"
27 | // that is immediately after "t0" and before "t1".
28 | After(t0, t1) -> next(t0) = t1 | ? t2 . next(t0) = t2 & After(t2, t1);
29 |
30 | // And we know at some point of time (namely "'t_hodor"), Wyllis became Hodor
31 | Hodor('t_hodor);
32 | ```
33 |
34 | An unbounded run of Razor on the previous theory will never terminate (feel free to press `ctrl + c` after a
35 | few seconds):
36 |
37 | ```
38 | razor solve -i theories/examples/hodor-linear.raz
39 | ```
40 |
41 | Assuming that time progresses linearly, the circular causality between the two events of "holding the door" and
42 | "becoming Hodor" results in an infinitely large model where time progresses unboundedly. We can restrict the size of
43 | the structures constructed by Razor by bounding the number of their elements. For example, if we restrict the number of
44 | elements to 4, Razor will find 9 *incomplete* structures, which do *not* satisfy the theory:
45 |
46 | ```
47 | razor solve -i theories/examples/hodor-linear.raz --bound domain=4
48 | ```
49 |
50 | For example, the following structure corresponds to an incomplete model where `e#0` denotes the starting point `t_hodor`
51 | and `e#1`, `e#2` and `e#4` are other points in time:
52 |
53 | ```
54 | Domain: e#0, e#2, e#4, e#1
55 |
56 | Elements: 't_hodor -> e#0, sk#0[e#0] -> e#1, next[e#0], sk#1[e#0, e#1] -> e#2,
57 | next[e#1] -> e#4
58 |
59 | Facts: After(e#0, e#1), After(e#2, e#1), Hodor(e#0), Hodor(e#4), HoldTheDoor(e#1)
60 | ```
61 |
62 | Now consider `e#1` and `e#2`. The incomplete model shows that `e#1` is after `e#2`, but neither `e#1`
63 | immediately follows `e#2` (no next point for `e#2`) nor there exists a point that is after `e#2` and
64 | before `e#1`, violating the second rule of linear time progression. In general, it may be possible to extend the
65 | incomplete structure to a model of the theory by adding more information to the model. Any model of this particular
66 | theory, however, is infinitely large.
67 |
68 | #### Time-Loop
69 |
70 | Next, we model time as a "big ball of wibbly wobbly timey wimey stuff!" To make it simple, let's assume that time-loops
71 | can only happen at the moment that Hodor heard a voice from the future, namely `'t_hodor`, changing our rules of
72 | time progression ([hodor-time-loop.raz](https://github.com/salmans/rusty-razor/blob/master/theories/examples/hodor-time-loop.raz)):
73 |
74 | ```
75 | HoldTheDoor(t) -> Hodor(next(t));
76 |
77 | Hodor(t) -> ? tt . HoldTheDoor(tt) & After(t, tt);
78 |
79 | next(t0) = t1 -> After(t0, t1);
80 | After(t0, t1) -> (next(t0) = t1) | ? t2 . next(t0) = t2 & After(t2, t1);
81 |
82 | // Hold the door moment only happens at 't_hodor
83 | HoldTheDoor(t) -> t = 't_hodor;
84 |
85 | Hodor('t_hodor);
86 | ```
87 |
88 | In presence of time-loops, Razor can explain Hodor's curious journey:
89 |
90 | ```
91 | razor solve -i theories/examples/hodor-time-loop.raz
92 | ```
93 |
94 | This time, Razor produces infinitely many (finite) models with time-loops of different size. Use can use the `--count`
95 | option to limit the number of models and halt the process.
96 |
--------------------------------------------------------------------------------
/book/src/example/valar-morghulis.md:
--------------------------------------------------------------------------------
1 | ## Valar Morghulis
2 |
3 | All men must die.
4 | Ser Gregor is a man.
5 |
6 | ```
7 | // All men must die:
8 | forall x. (Man(x) implies MustDie(x));
9 |
10 | // Ser Gregor is a man:
11 | Man('gregor);
12 | ```
13 |
14 | Run Razor on the previous theory [valar-morghulis.raz](https://github.com/salmans/rusty-razor/blob/master/theories/examples/valar-morghulis.raz):
15 |
16 | ```
17 | razor solve -i theories/examples/valar-morghulis.raz
18 | ```
19 |
20 | Razor returns only one model:
21 |
22 | ```
23 | Domain: e#0
24 |
25 | Elements: 'gregor -> e#0
26 |
27 | Facts: Man(e#0), MustDie(e#0)
28 | ```
29 |
30 | The model contains only one element `e#0` in its domain. This element denotes `'gregor`, a constant in the theory that
31 | represents Ser Gregor. The model also contains two facts: `Man(e#0)` is a fact that is derived from the second statement
32 | of the theory (i.e., `Man('gregor)`). The fact `MustDie(e#0)` is deduced by Razor according to the first statement of
33 | the theory.
34 |
35 | > Notice that the previous model is a "minimal" model for the given theory. The element `e#0` is required to represent
36 | the constant `'gregor`; the fact `Man(e#0)` must be present because the theory says so; and, the fact `MustDie(e#0)`
37 | must be true because of the first statement. Removing any piece of information makes the given structure a non-model of
38 | the theory.
--------------------------------------------------------------------------------
/book/src/intro.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Rusty Razor is a tool for constructing finite models for first-order theories. The model-finding algorithm is inspired
4 | by [the chase](https://en.wikipedia.org/wiki/Chase_(algorithm)) for database systems. Given a first-order theory,
5 | Razor constructs a set of *homomorphically minimal* models. The constructed models are models of an equisatisfiable
6 | theory over an alphabet, extended with Skolem functions that represent the existential quantifiers of the original theory.
7 |
8 | To learn more about the theoretical foundation of Razor, check out my
9 | [PhD dissertation](https://digitalcommons.wpi.edu/etd-dissertations/458/).
10 |
--------------------------------------------------------------------------------
/book/src/run.md:
--------------------------------------------------------------------------------
1 | # Run
2 |
3 | ## `solve`
4 |
5 | Use the `solve` command to find models for an input theory. The `-i` (short for `--input`)
6 | reads the input from a file:
7 |
8 | ```
9 | razor solve -i
10 | ```
11 |
12 | > Run `solve` without the `-i` option to read the input from the standard input.
13 |
14 | The `--count` parameter limits the number of models to construct:
15 |
16 | ```
17 | razor solve -i --count
18 | ```
--------------------------------------------------------------------------------
/book/src/run/bounded.md:
--------------------------------------------------------------------------------
1 | ### Bounded Model-Finding
2 |
3 | Unlike conventional model-finders such as [Alloy](http://alloytools.org), Razor doesn't require the user to provide a
4 | bound on the size of the models that it constructs. However, Razor may never terminate when running on theories with
5 | non-finite models -- it can be shown that a run of Razor on an unsatisfiable theory (i.e., a theory with no models)
6 | is guaranteed to terminate (although it might take a very very long time).This is a direct consequence of
7 | semi-decidability of first-order logic.
8 |
9 | To guarantee termination, limit the size of the resulting models by the number of their elements using the `--bound`
10 | option with a value for the `domain` parameter:
11 |
12 | ```
13 | razor solve -i --bound domain=
14 | ```
15 |
16 |
--------------------------------------------------------------------------------
/book/src/run/scheduler.md:
--------------------------------------------------------------------------------
1 | ### Model-Finding Scheduler
2 |
3 | Use the `--scheduler` option to choose how Razor processes search branches. The `fifo` scheduler (the default scheduler)
4 | schedules new branches last and is a more suitable option for processing theories with few small satisfying models.
5 | The `lifo` scheduler schedules new branches first, and is more suitable for processing theories with many large models.
6 |
7 | ```
8 | razor solve -i --scheduler
9 | ```
--------------------------------------------------------------------------------
/book/src/syntax.md:
--------------------------------------------------------------------------------
1 | # Syntax
2 |
3 | The syntax and semantics of Razor's input is that of conventional
4 | [first-order logic](https://en.wikipedia.org/wiki/First-order_logic),
5 | also known as predicate logic.
6 |
7 | Razor's input supports three [syntactic variations](./syntax/variations.html)
8 | for the logical symbols and follows one of the more predominant conventions for
9 | the [precedence](./syntax/precedence.html) of the logical connectives.
10 | For consistency and readability purposes, we use the _alpha_ variation
11 | everywhere in this document.
12 |
13 | ## Identifier
14 |
15 | Lowercase and uppercase identifiers in Razor are defined by the following:
16 |
17 | * A _lowercase_ identifier is a word starting with either a lowercase alphabetic character (`[a-z]`) or
18 | the underscore (`_`), followed by any number of alphanumeric characters (`[a-zA-Z0-9]`) and/or the
19 | underscore. For example, `rusty`, `_razor`, and `rusty123_RAZOR456` are lowercase identifiers.
20 | * An _uppercase_ identifier is a word that starts with an uppercase alphabetic character (`[A-Z]`) and
21 | is followed by any number of alphanumeric characters (`[a-zA-Z0-9]`) and/or the underscore (`_`).
22 | For example, `Rusty`, and `RAZOR_123` are uppercase identifiers.
23 |
24 | ## Term
25 |
26 | A _term_ in Razor's input is a conventional first-order term, inductively defined by the following:
27 |
28 | * A __variable__ is a lowercase identifier, and it is a term on its own. For example, `v`,
29 | `variable`, and `_var` may be used as variable symbols.
30 | * A _composite_ term consists of a lowercase identifier as a __function__ symbol that is applied
31 | to zero or more terms as arguments that are wrapped in parentheses. For example, `f()`, `f(a)`,
32 | `f(g(a, b), c)`, and `func(plus(x, y))` are terms.
33 |
34 | Razor treats _nullary_ functions (of arity zero that take no arguments)
35 | as __constants__. An apostrophe (`'`) followed by a lowercase identifier is a syntactic sugar
36 | for constructing a constant. For example, `'a` is a constant that is syntactically
37 | equivalent to `a()`.
38 |
39 | ## Formula
40 |
41 | A _formula_ in Razor's input is a conventional first-order formula _with equality_, inductively
42 | defined by the following:
43 |
44 | * An __atomic__ formula consists of an upper case identifier as a __predicate__ symbol that is
45 | applied to zero or more terms as arguments that are wrapped in parentheses. For example, `R()`,
46 | `R(x)`, and `R(f(x, 'a), y, 'b)` are atomic formulae.
47 |
48 | * An __equality__ is a especial type of atomic formula, with a binary infix connective `=` that is
49 | applied on two terms as its arguments. Razor treats an equality as identity between its arguments.
50 | For example, `x = y`, and `f(x) = g(f(y), 'a)` are equalities.
51 |
52 | * The result of applying the logical connectives `and`, `or`, `implies` and `iff` as infix binary
53 | connectives to two fromulae is itself a formula. Parentheses may be used to override the conventional
54 | [precedence](./syntax/precedence.html) of the connectives.
55 | For example `P(x) and x = y`, `P(x) and (Q(y) or R(x))`, and `P(x, y) implies x = y and R(z)`
56 | are formulae.
57 |
58 | * The result of applying the logical connectives `forall` and `exists` to one or more comma (`,`)
59 | separated _bound_ variables and a formula, as a propositional function, is itself a formula. The
60 | list of bound variables and the propositional function are separated by a period (`.`).
61 | For example, `exists x. P(x)`, `forall x, y . Q(x, y) or R(z)`, and
62 | `forall x. exists y. P(x, y)` are formulae.
63 |
64 | > __Note:__ A variable that is not bound by a universal (`forall`) or existential (`exists`) quantifier
65 | is said to be a _free_ variable. Razor assumes all free variables to be universally
66 | quantified over the entire formula in which they appear.
67 | For example, `P(x) -> exists y . Q(x, y)` is assumed to be equivalent to
68 | `forall x . (P(x) -> exists y . Q(x, y))`.
69 |
70 | ## Input
71 |
72 | Razor's input is a first-order theory, consisting of a list of zero or more formulae, separated by
73 | the semi-colon (`;`). The input may contain conventional C-style comments (`//` for comment lines and
74 | `/*` and `*/` for comment blocks). Whitespaces including new lines are allowed.
75 | See the following input for an example:
76 |
77 | ```
78 | // equality axioms
79 |
80 | forall x . x = x; /* Reflexitivity */
81 | forall x, y . (x = y implies y = x); /* Symmetry */
82 | forall x, y, z . (x = y and y = z implies x = z); /* Transitivity */
83 | ```
--------------------------------------------------------------------------------
/book/src/syntax/grammar.md:
--------------------------------------------------------------------------------
1 | ## Grammar
2 |
3 | The input theory accepted by Razor is defined by the grammar below:
4 |
5 | ```
6 | LOWER ::= [a-z_][a-zA-Z0-9_]*
7 | UPPER ::= [A-Z][a-zA-Z0-9_]*
8 |
9 | TRUE ::= "true" | "'|'" | "⊤" (U+22A4)
10 | FALSE ::= "false" | "_|_" | "⟘" (U+27D8)
11 | NOT ::= "not" | "~" | "¬" (U+00AC)
12 | AND ::= "and" | "&" | "∧" (U+2227)
13 | OR ::= "or" | "|" | "∨" (U+2228)
14 | IMPLIES ::= "implies" | "->" | "→" (U+2192)
15 | IFF ::= "iff" | "<=>" | "⇔" (U+21D4)
16 | EXISTS ::= "exists" | "?" | "∃" (U+2203)
17 | FORALL ::= "forall" | "!" | "∀" (U+2200)
18 |
19 | VARIABLE ::= LOWER
20 | FUNCTION ::= LOWER
21 | PREDICATE ::= UPPER
22 | VARIABLES ::= VARIABLE ("," VARIABLES)*
23 | TERM ::= VARIABLE | FUNCTION "(" TERMS? ")"
24 | TERMS ::= TERM ("," TERMS)*
25 |
26 | ATOM ::= TRUE | FALSE
27 | | TERM "=" TERM | PREDICATE "(" TERMS? ")"
28 | | "(" FORMULA ")"
29 | F_NOT ::= NOT F_QUANTIFIED | ATOM
30 | F_AND ::= F_NOT (AND F_QUANTIFIED)?
31 | F_OR ::= F_AND (OR F_QUANTIFIED)?
32 | F_QUANTIFIED ::= (EXISTS | FORALL) VARIABLES "." F_QUANTIFIED | F_OR
33 | FORMULA ::= F_QUANTIFIED ((IMPLIES | IFF) F_QUANTIFIED)*
34 |
35 | THEORY ::= (FORMULA ";")*
36 | ```
--------------------------------------------------------------------------------
/book/src/syntax/precedence.md:
--------------------------------------------------------------------------------
1 | ## Connective Precedence
2 |
3 | When a formula contains two or more logical connectives, the connectives
4 | are applied by the following order from the highest to the lowest precedence:
5 |
6 | * Negation (`not`) is applied first.
7 | * Conjunction (`and`) is applied next.
8 | * Disjunction (`or`) is applied next.
9 | * Implication (`implies`) and bi-implication (`iff`) are applied next.
10 | * Existential (`exists`) and universal (`forall`) quantifiers are applied last.
11 |
12 | A connective with a higher precedence is applied before a consecutive
13 | connective with a lower precedence; that is, the connective with the higher
14 | precedence binds tighter to the formula on which it operates.
15 | For example, `P() implies not Q() and R()` is a formula consisting of
16 | an implication where `P()` is the premise and the conjunction of `not Q()`
17 | and `R()` is the consequence.
18 |
19 | Parentheses may be used to override the precedence of connectives.
20 | For example, in `P() and (Q() or R())` the disjunction (`or`) is applied before
21 | the conjunction (`and`).
22 |
23 | ### Associativity
24 |
25 | All binary connectives of equal precedence except for implication
26 | (`implies`) and bi-implication (`iff`) are left-associative.
27 | For example, `P() | Q() | R()` is evaluated as `(P() | Q()) | R()`.
28 |
29 | Implication and bi-implication are right-associative.
30 | For example, `P() <=> Q() -> R() <=> S()` is evaluated as
31 | `P() <=> (Q() -> (R() <=> S()))`.
--------------------------------------------------------------------------------
/book/src/syntax/syntax:
--------------------------------------------------------------------------------
1 | /Users/salmansaghafi/code/rusty-razor/book/src/syntax:
2 | total used in directory 24 available 2447352537
3 | drwxr-xr-x 5 salmansaghafi staff 160 Dec 29 16:57 .
4 | drwxr-xr-x 13 salmansaghafi staff 416 Dec 28 18:45 ..
5 | -rw-r--r-- 1 salmansaghafi staff 817 Dec 29 17:04 grammar.md
6 | -rw-r--r-- 1 salmansaghafi staff 983 Dec 29 16:57 precedence.md
7 | -rw-r--r-- 1 salmansaghafi staff 1629 Dec 28 18:19 variations.md
8 |
--------------------------------------------------------------------------------
/book/src/syntax/variations.md:
--------------------------------------------------------------------------------
1 | ## Syntactic Variations
2 |
3 | Razor supports three syntactic variations of the logical symbols in the input:
4 |
5 | * __alpha__ where logical symbols are written as alphabetic words.
6 | * __compact__ where ASCII notations represent logical symbols.
7 | * __math__ where (Unicode) mathematical symbols are used.
8 |
9 | > __Note:__ Currently, Razor's parser accepts inputs that are comprised of any combination
10 | of the syntactic variations mentioned above. However, future releases of Razor may restrict
11 | the input to use only one of the variations above.
12 |
13 | The table below shows all syntactic variations of the logical symbols:
14 |
15 | | symbol | alpha | compact | math |
16 | | ------------------------ |:----------:| :-------------------------------:|:------------:|
17 | | _truth_ | `true` | '|' | `⊤` (U+22A4) |
18 | | _falsehood_ | `false` | _|_ | `⟘` (U+27D8) |
19 | | _negation_ | `not` | `~` | `¬` (U+00AC) |
20 | | _conjunction_ | `and` | `&` | `∧` (U+2227) |
21 | | _disjunction_ | `or` | | | `∨` (U+2228) |
22 | | _implication_ | `implies` | `->` | `→` (U+2192) |
23 | | _bi-implication_ | `iff` | `<=>` | `⇔` (U+21D4) |
24 | | _existential quantifier_ | `exists` | `?` | `∃` (U+2203) |
25 | | _universal quantifier_ | `forall` | `!` | `∀` (U+2200) |
--------------------------------------------------------------------------------
/razor-chase/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "razor-chase"
3 | version = "0.1.0"
4 | authors = ["Salman Saghafi "]
5 | edition = "2018"
6 | license = "MIT"
7 | description = "razor-chase implements a variation of the chase algorithm to find models for theories in geometric form."
8 | documentation = "https://salmans.github.io/rusty-razor/intro.html"
9 | homepage = "https://github.com/salmans/rusty-razor"
10 | repository = "https://github.com/salmans/rusty-razor"
11 | keywords = ["razor", "chase", "geometric", "logic", "model-finder"]
12 | categories = ["mathematics"]
13 |
14 | [dependencies]
15 | itertools = "0.7"
16 | criterion = "0.1"
17 | tracing = "0.1"
18 | rand = "0.6.5"
19 | serde = "1.0.93"
20 | serde_json = "1.0.39"
21 | serde_derive = "1.0.93"
22 | either = "1.6.1"
23 | thiserror = "^1.0"
24 | razor-fol = { path = "../razor-fol", version = "0.1.0" }
25 | codd = { version = "^0.1" }
26 | petgraph = "^0.5"
27 |
28 | [dev-dependencies]
29 | codd = { version = "^0.1", features = ["unstable"] }
30 |
31 | [lib]
32 | name = "razor_chase"
33 | path = "src/lib.rs"
34 |
35 | [[bench]]
36 | name = "perf_test"
37 | harness = false
--------------------------------------------------------------------------------
/razor-chase/benches/perf_test.rs:
--------------------------------------------------------------------------------
1 | use criterion::{criterion_group, criterion_main, Bencher, Criterion};
2 | use itertools::Itertools;
3 | use razor_chase::chase::{
4 | bounder::DomainSize,
5 | chase_all,
6 | r#impl::*,
7 | scheduler::FIFO,
8 | strategy::{Bootstrap, Fair},
9 | PreProcessor, Scheduler, Strategy,
10 | };
11 | use razor_fol::syntax::Theory;
12 | use std::{fs, io::Read};
13 |
14 | macro_rules! read_theory {
15 | ($file_name: expr) => {{
16 | let mut f = fs::File::open($file_name).expect("file not found");
17 | let mut contents = String::new();
18 | f.read_to_string(&mut contents)
19 | .expect("something went wrong reading the file");
20 |
21 | contents.parse::().unwrap()
22 | }};
23 | }
24 |
25 | macro_rules! run_unbounded {
26 | (
27 | $name: literal,
28 | pre_processor = $prep: expr,
29 | evaluator = $eval: path,
30 | sequent = $seq: path,
31 | model = $model: path,
32 | $crit: ident
33 | ) => {{
34 | fn solve(theory: &Theory) -> Vec<$model> {
35 | let (sequents, init_model) = $prep.pre_process(theory);
36 | let evaluator = $eval;
37 | let selector: Bootstrap<$seq, Fair<$seq>> = Bootstrap::new(sequents.iter());
38 | let mut strategy = FIFO::new();
39 | let bounder: Option<&DomainSize> = None;
40 | strategy.add(init_model, selector);
41 | chase_all(&mut strategy, &evaluator, bounder)
42 | }
43 |
44 | fn run_bench(b: &mut Bencher) {
45 | let theories = &fs::read_dir("../theories/core")
46 | .unwrap()
47 | .map(|item| read_theory!(item.unwrap().path().display().to_string()).gnf())
48 | .collect_vec();
49 |
50 | b.iter(|| {
51 | for theory in theories {
52 | solve(&theory);
53 | }
54 | })
55 | }
56 | $crit.bench_function(stringify!($name), run_bench);
57 | }};
58 | }
59 |
60 | fn bench_batch(c: &mut Criterion) {
61 | run_unbounded!(
62 | "batch",
63 | pre_processor = collapse::ColPreProcessor,
64 | evaluator = batch::BatchEvaluator,
65 | sequent = collapse::ColSequent,
66 | model = collapse::ColModel,
67 | c
68 | );
69 | }
70 |
71 | fn bench_relational(c: &mut Criterion) {
72 | run_unbounded!(
73 | "relational",
74 | pre_processor = relational::RelPreProcessor::new(false),
75 | evaluator = relational::RelEvaluator,
76 | sequent = relational::RelSequent,
77 | model = relational::RelModel,
78 | c
79 | );
80 | }
81 |
82 | fn bench_relational_memoized(c: &mut Criterion) {
83 | run_unbounded!(
84 | "relational_memoized",
85 | pre_processor = relational::RelPreProcessor::new(true),
86 | evaluator = relational::RelEvaluator,
87 | sequent = relational::RelSequent,
88 | model = relational::RelModel,
89 | c
90 | );
91 | }
92 |
93 | criterion_group!(
94 | benches,
95 | bench_batch,
96 | bench_relational,
97 | bench_relational_memoized
98 | );
99 | criterion_main!(benches);
100 |
--------------------------------------------------------------------------------
/razor-chase/src/chase/bounder.rs:
--------------------------------------------------------------------------------
1 | //! Implements various bounders for bounding the size of models, generated by the chase.
2 | //!
3 | //! The bounders are instances of [`Bounder`].
4 | //!
5 | //! [`Bounder`]: crate::chase::Bounder
6 | use crate::chase::{Bounder, Model, Observation, WitnessTerm};
7 |
8 | /// Bounds the size of a [model] by the number of elements in its [domain].
9 | ///
10 | /// [model]: crate::chase::Model
11 | /// [domain]: crate::chase::Model::domain()
12 | pub struct DomainSize {
13 | /// Is the maximum size of the [domain] of elements for models accepted by this bounder.
14 | ///
15 | /// [domain]: crate::chase::Model::domain()
16 | max_domain_size: usize,
17 | }
18 |
19 | impl From for DomainSize {
20 | fn from(size: usize) -> Self {
21 | Self {
22 | max_domain_size: size,
23 | }
24 | }
25 | }
26 |
27 | impl Bounder for DomainSize {
28 | fn bound(&self, model: &M, observation: &Observation) -> bool {
29 | match observation {
30 | Observation::Fact { relation: _, terms } => {
31 | let model_size = model.domain().len();
32 | let terms: Vec