Skip to content

Commit

Permalink
Fix Context::forall and Context::exists quantifiers (#27)
Browse files Browse the repository at this point in the history
The `Context::forall` and `Context::exists` functions were not
structuring the S-expressions for variables correctly. This PR fixes
that, and adds an example that uses quantifiers as a means of testing.

To demonstrate the problem, run the new example without the changes to
`src/context.rs`. The example uses the following `forall` quantifier:

```
ctx.assert(
    // For any pair of sets,
    ctx.forall(
        [("s1", ctx.atom("MySet")), ("s2", ctx.atom("MySet"))],
        // One of the following is true:
        ctx.or(
            ...
        )
    )
)
```

which produces the following trace:

```
$ RUST_LOG="easy_smt=trace" cargo run --example quantifiers
...
[_] -> (declare-fun subset (MySet MySet) Bool)
[_] <- success
[_] -> (assert (forall (s1 MySet) (s2 MySet) (or (subset s1 s2) (exists (x MyElement) (and (member x s1) (not (member x s2)))))))
[_] <- (error "line 8 column 17: invalid sorted variable, '(' expected got s1")
```

The problem is that the variable declarations `(s1 MySet)` and `(s2
MySet)` are not grouped together as a sub-list. The forall expression is
supposed to have the shape:

```
(forall ((var1 Sort1) ... (varN SortN)) (body))
```

The same problem occurs for the `exists` function: the variable
declaration `(x MyElement)` in the above trace should be a singleton
list `((x MyElement))`.

With the changes to `src/context.rs`, the example is fixed:

```
$ RUST_LOG="easy_smt=trace" cargo run --example quantifiers
...
[_] -> (declare-fun subset (MySet MySet) Bool)
[_] <- success
[_] -> (assert (forall ((s1 MySet) (s2 MySet)) (or (subset s1 s2) (exists ((x MyElement)) (and (member x s1) (not (member x s2)))))))
[_] <- success
...
```

Thanks for writing this library! Aside from this bug, I've found it easy
to get up and running with it.
  • Loading branch information
octalsrc authored Aug 2, 2024
1 parent 17c3df0 commit b870aeb
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 18 deletions.
125 changes: 125 additions & 0 deletions examples/quantifiers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// In this example, we assert quantified formulas over uninterpreted
// sorts and functions.

fn main() -> std::io::Result<()> {
env_logger::init();
let mut ctx = easy_smt::ContextBuilder::new()
.solver("z3", ["-smt2", "-in"])
.build()?;

// Declare an uninterpreted representation for sets.
ctx.declare_sort("MySet", 0)?;
// Declare representation for elements of those sets.
ctx.declare_sort("MyElement", 0)?;

// Our sets have a simple interface of predicates: empty(s),
// member(x,s), and subset(s1,s2).
ctx.declare_fun(
"empty",
vec![ctx.atom("MySet")],
ctx.bool_sort(),
)?;
ctx.declare_fun(
"member",
vec![ctx.atom("MyElement"), ctx.atom("MySet")],
ctx.bool_sort(),
)?;
ctx.declare_fun(
"subset",
vec![ctx.atom("MySet"), ctx.atom("MySet")],
ctx.bool_sort(),
)?;

// We assert some simple axioms on this interface.

// First, a set s1 is either a subset of s2, or a member of s1
// exists which is not a member of s2.
ctx.assert(
// For any pair of sets,
ctx.forall(
[("s1", ctx.atom("MySet")), ("s2", ctx.atom("MySet"))],
// One of the following is true:
ctx.or(
// (1) s1 is a subset of s2, or
ctx.list(vec![
ctx.atom("subset"),
ctx.atom("s1"),
ctx.atom("s2"),
]),
// (2) an element exists which is a member of s1, and
// not a member of s2.
ctx.exists(
[("x", ctx.atom("MyElement"))],
ctx.and(
ctx.list(vec![
ctx.atom("member"),
ctx.atom("x"),
ctx.atom("s1"),
]),
ctx.not(ctx.list(vec![
ctx.atom("member"),
ctx.atom("x"),
ctx.atom("s2"),
])),
),
),
),
)
)?;

// Second, a set is empty iff no member exists.
ctx.assert(
// For any set,
ctx.forall(
[("s1", ctx.atom("MySet"))],
// The following formulas are equivalent:
ctx.eq(
// (1) the set is not empty, and
ctx.not(ctx.list(vec![ctx.atom("empty"), ctx.atom("s1")])),
// (2) an element exists that is a member of the set.
ctx.exists(
[("x", ctx.atom("MyElement"))],
ctx.list(vec![
ctx.atom("member"),
ctx.atom("x"),
ctx.atom("s1"),
]),
),
),
),
)?;

// Now, let's check whether a relationship between two sets is
// possible.
ctx.declare_const("s1", ctx.atom("MySet"))?;
ctx.declare_const("s2", ctx.atom("MySet"))?;

// Is it possible for s1 to be empty,
ctx.assert(
ctx.list(vec![ctx.atom("empty"), ctx.atom("s1")])
)?;
// while also s1 is not a subset of s2?
ctx.assert(
ctx.not(
ctx.list(vec![
ctx.atom("subset"),
ctx.atom("s1"),
ctx.atom("s2"),
])
)
)?;

// We expect the answer to be no (UNSAT).
match ctx.check()? {
easy_smt::Response::Sat => {
println!("Solver returned SAT. This is unexpected!");
},
easy_smt::Response::Unsat => {
println!("Solver returned UNSAT. This is expected.");
},
easy_smt::Response::Unknown => {
println!("Solver returned UNKNOWN. This is unexpected!");
},
}
Ok(())
}
36 changes: 18 additions & 18 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,15 +655,15 @@ impl Context {
I: IntoIterator<Item = (N, SExpr)>,
N: Into<String> + AsRef<str>,
{
let args: Vec<_> = std::iter::once(self.atoms.forall)
.chain(
vars.into_iter()
.map(|(n, s)| self.list(vec![self.atom(n), s])),
)
.chain(std::iter::once(body))
.collect();
assert!(args.len() >= 3);
self.list(args)
let vars_iter =
vars
.into_iter()
.map(|(n, s)| self.list(vec![self.atom(n), s]));
self.list(vec![
self.atoms.forall,
self.list(vars_iter.collect()),
body,
])
}

/// Existentially quantify sorted variables in an expression.
Expand All @@ -672,15 +672,15 @@ impl Context {
I: IntoIterator<Item = (N, SExpr)>,
N: Into<String> + AsRef<str>,
{
let args: Vec<_> = std::iter::once(self.atoms.exists)
.chain(
vars.into_iter()
.map(|(n, s)| self.list(vec![self.atom(n), s])),
)
.chain(std::iter::once(body))
.collect();
assert!(args.len() >= 3);
self.list(args)
let vars_iter =
vars
.into_iter()
.map(|(n, s)| self.list(vec![self.atom(n), s]));
self.list(vec![
self.atoms.exists,
self.list(vars_iter.collect()),
body,
])
}

/// Perform pattern matching on values of an algebraic data type.
Expand Down

0 comments on commit b870aeb

Please sign in to comment.