-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from elsoroka/dev
Uninterpreted functions and interactive solving
- Loading branch information
Showing
57 changed files
with
11,341 additions
and
459 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
python3 -m http.server --bind localhost |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Pushing and popping assertions | ||
In this problem we have some expressions we need to satisfy, and some that we would like to satisfy (but we can't satisfy them all). | ||
We want to figure out which expressions we can satisfy using push() and pop() to assert and remove them as necessary. | ||
|
||
```julia | ||
using Satisfiability | ||
@satvariable(x, Bool) | ||
@satvariable(y, Bool) | ||
@satvariable(z, Bool) | ||
necessary_exprs = or(and(not(x), y, z), and(not(y), x, z)) | ||
|
||
interactive_solver = open(Z3()) | ||
``` | ||
We assert this at the first level, since we always have to have it. | ||
```julia | ||
assert!(interactive_solver, necessary_exprs) | ||
``` | ||
|
||
Here are some conflicting expressions. One of them is satisfiable when `necessary_exprs` is true; the others are not. | ||
```julia | ||
conflicting_exprs = [ | ||
not(z), | ||
and(not(x), not(y)), | ||
not(x), | ||
and(x,y), | ||
] | ||
``` | ||
|
||
We'll use `push` and `pop` to add and remove them one at a time. | ||
```julia | ||
for e in conflicting_exprs | ||
# Push one assertion level on the stack | ||
push!(interactive_solver, 1) | ||
|
||
# Now assert an expression that might make the problem unsatisfiable | ||
assert!(interactive_solver, e) | ||
status, assignment = sat!(interactive_solver) | ||
|
||
if status == :SAT | ||
println("We found it! Expr \n$e \nis satisfiable.") | ||
assign!(necessary_exprs, assignment) | ||
assign!(conflicting_exprs, assignment) | ||
else | ||
# Pop one level off the stack, removing the problematic assertion. | ||
pop!(interactive_solver, 1) | ||
end | ||
end | ||
``` | ||
|
||
### Another way to do this. | ||
Let's reset the solver so we can try another way to do the same thing. This command clears all assertions, including the first one we made at level 1. | ||
```julia | ||
reset_assertions!(interactive_solver) | ||
``` | ||
|
||
This time, we use `sat!(solver, exprs...)` which is equivalent to the SMT-LIB command `(check-sat-assuming exprs...)`. Thus the expression is not asserted but is assumed within the scope of the `sat!` call. | ||
```julia | ||
assert!(interactive_solver, necessary_exprs) | ||
# Here's an equivalent way to do this by passing exprs into sat!. This is equivalent to the SMT-LIB syntax "(check-sat-assuming (exprs...))", which does not (assert) the expressions but assumes they should be satisfied. | ||
for e in conflicting_exprs | ||
status, assignment = sat!(interactive_solver, e) | ||
println("status = $status") | ||
if status == :SAT | ||
println("We found it! Expr \n$e \nis satisfiable.") | ||
assign!(necessary_exprs, assignment) | ||
assign!(conflicting_exprs, assignment) | ||
end | ||
end | ||
|
||
# We're done, so don't forget to clean up. | ||
close(interactive_solver) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# Graph coloring | ||
A classic problem in graph theory is figuring out how to color nodes of a graph such that no two adjacent nodes have the same color. | ||
This is useful for things like mapmaking (imagine if your map had two adjacent countries sharing a color!) | ||
The [chromatic polynomial](https://en.wikipedia.org/wiki/Graph_coloring) counts the number of ways a graph can be colored using n colors. For example, this graph | ||
``` | ||
(a) | ||
| \ | ||
| (c)--(d) | ||
| / | ||
(b) | ||
``` | ||
can be colored using exactly 3 colors in 12 different ways. Let's use SMT to find all 12 ways to color this graph. | ||
|
||
```julia | ||
using Satisfiability | ||
@satvariable(nodes[1:4], Int) | ||
|
||
# "There are 3 colors available" | ||
limits = and.(nodes .>= 1, nodes .<= 3) | ||
|
||
# "No adjacent nodes can share a color" | ||
(a, b, c, d) = nodes | ||
connections = and(a != b, a != c, b != c, c != d) | ||
|
||
# "All 3 colors must be used" | ||
# (If you leave this out you can find 24 colorings. But 12 of them will use only 2 colors.) | ||
all3 = and(or(nodes .== i) for i=1:3) | ||
``` | ||
|
||
To find **all** the solutions, we have to exclude solutions as we find them. Suppose we find a satisfying assignment `[vars] = [values]`. Adding the negation `not(and(vars .== values))` to the list of assertions excludes that specific assignment from being found again. Remember: when excluding solutions, negate the whole expression. An easy mistake is `and(not(nodes .== value(nodes)))`, which excludes each node from taking on the particular value we just found (for example, saying "a cannot be 1", "b cannot be 2"...) instead of excluding the specific combination of all 4 values ("a cannot be 1 when b is 2,..."). | ||
|
||
```julia | ||
function findall() | ||
|
||
solutions = [] | ||
open(Z3()) do interactive_solver # the do syntax closes the solver | ||
assert!(interactive_solver, limits, connections, all3) | ||
i = 1 | ||
status, assignment = sat!(interactive_solver) | ||
while status == :SAT | ||
# Try to solve the problem | ||
push!(solutions, assignment) | ||
println("i = $i, status = $status, assignment = $assignment") | ||
assign!(nodes, assignment) | ||
|
||
# Use assert! to exclude the solution we just found. | ||
assert!(interactive_solver, not(and(nodes .== value(nodes)))) | ||
status, assignment = sat!(interactive_solver) | ||
i += 1 | ||
end | ||
end | ||
println("Found them all!") | ||
end | ||
|
||
findall() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Uninterpreted Functions | ||
|
||
|
||
An uninterpreted function is a function where the mapping between input and output is not known. The task of the SMT solver is then to determine a mapping such that some SMT expression holds true. | ||
|
||
Satisfiability.jl represents uninterpreted functions as callable structs. This enables the simple syntax: | ||
```julia | ||
@uninterpreted(myfunc, Int, Int) | ||
|
||
# we can call myfunc on an Int constant or variable | ||
@satvariable(a, Int) | ||
myfunc(a) | ||
myfunc(-2) # returns | ||
|
||
# we cannot call myfunc on the wrong type | ||
# myfunc(true) yields an error | ||
# myfunc(1.5) yields an error | ||
``` | ||
|
||
As a small example, we can ask whether there exists a function `f(x)` such that `f(f(x)) == x`, `f(x) == y` and `x != y`. | ||
|
||
```julia | ||
@satvariable(x, Bool) | ||
@satvariable(y, Bool) | ||
@uninterpreted(f, Bool, Bool) | ||
|
||
status = sat!(distinct(x,y), f(x) == y, f(f(x)) == x, solver=Z3()) | ||
println("status = \$status") | ||
``` | ||
|
||
It turns out there is. Since the satisfying assignment for an uninterpreted function is itself a function, Satisfiability.jl represents this by setting the value of `f` to this function. Now calling `f(value)` will return the value of this satisfying assignment. | ||
|
||
```julia | ||
println(f(x.value)) # prints 0 | ||
println(f(x.value) == y.value) # true | ||
println(f(f(x.value)) == x.value) # true | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.