Solving SAT problem using Z3 without repeating Boolean expression - z3

Let's look at the following problem: f(a, b, c, x, y, z) is a Boolean function where a, b, c, x, y and z are Boolean values, and the output of f is a boolean value. The definition of f consists of a number of and/or/nor operators. I want to find a set of three Boolean values x0, y0, and z0, such that:
f(0, 0, 0, x0, y0, z0) = 0 AND
f(0, 0, 1, x0, y0, z0) = 1 AND
f(0, 1, 0, x0, y0, z0) = 1 AND
f(0, 1, 1, x0, y0, z0) = 0 AND
...
f(1, 1, 1, x0, y0, z0) = 1 # A total of 8 constrains. Each of them is from an entry in the truth table.
A naive approach is to define 3 Boolean variables x, y, z, and repeatedly define the function f 8 times. However f consists of complex boolean expressions. Defining it 8 times can blow up the model. Moreover, in fact, I have 7 variables: a, b, c, d, e, f, g. The truth table has 128 entries.
Is there a way to define something like a "placeholder" variables a, b, c which are not part of the solution? I can then define f on a, b, c, x, y, z only once, and later somehow "assign" a, b, c to Boolean constants.
I'm a newbie on SMT solvers, the placeholder idea might be totally irrelevant. Other solutions are appreciated as well.

It's hard to decipher exactly what you are trying to do. But it seems to me that your concern is about how to code this easily, as opposed to anything else. If so, I'd recommend programming in a language that provides a higher-level API than SMTLib. You can use many of the interfaces z3 supports: C, C++, Java, etc., to simplify the programming task.
For instance, here's how one can use the Python interface to code an instance of your problem:
from z3 import *
def fOriginal(a, b, c, x, y, z):
return Or([a, b^c, x, y, z])
x0, y0, z0 = Bools("x0 y0 z0")
def f(a, b, c):
return fOriginal(a == 1, b == 1, c == 1, x0, y0, z0)
s = Solver()
s.add(f(0, 0, 0) == False)
s.add(f(0, 0, 1) == True)
s.add(f(0, 1, 0) == True)
s.add(f(0, 1, 1) == False)
s.add(f(1, 1, 1) == True)
print(s.check())
print(s.model())
When run, this prints:
sat
[y0 = False, z0 = False, x0 = False]
Giving you an assignment to x0, y0, and z0 as you're trying to find.
The idea here is to code f as a regular function, using z3's interface. I called this function fOriginal in the python code. We then define a version of fOriginal, which I called f in the code, which passes symbolic values for the last three arguments, but expects constants for the first three.
We then simply add the constraints for each of your cases. I added only 5 above following your example; you can add all 8 of course.
I hope this gets you started!

It's not clear to me what you need the model for and how much flexibility you need, but here is an idea: if it is acceptable to constrain the function on all argument values, via a single else-case, then the following might work:
(declare-fun f (Bool Bool Bool Bool) Bool)
(assert (forall ((x Bool) (y Bool) (z Bool) (p Bool)) (!
(=
(f x y z p)
(ite
(and (not x) y) true (ite ; 1. case
(and (not x) (not y) z) true (ite ; 2. case
(and x (not y) z) true ; 3. case
false)))) ; else-case
:pattern ((f x y z p)))))
At least for this simple case, Z3's model effectively ignored the irrelevant 4th parameter of function f:
(check-sat) ;; SAT --> good
(get-model)
; NOTE: 4th parameter x!3 is declared, but not used
; (model
; (define-fun p () Bool
; false)
; (define-fun f ((x!0 Bool) (x!1 Bool) (x!2 Bool) (x!3 Bool)) Bool
; (or (not (or x!0 (not x!1)))
; (not (or x!1 (not x!2) (not x!0)))
; (not (or x!1 x!0 (not x!2)))))
; )

Related

How to model variable swap in SMT (Z3)?

I have a program that sorts variables, and I'm trying to check its validity with Z3, but I have one code segment where the variables are being swapped and I don't know how to model it in SMT syntax. Here is the original code segment:
if (x > y) {
temp = x;
x = y;
y = temp;
}
And regarding the SMT I have written an assertion, but I guess it is not exactly the correct thing:
(declare-fun x () Int)
(declare-fun y () Int)
(declare-fun temp () Int)
(assert (=> (> s1 s2) (and (= tmp s1) (= s1 s2) (= s2 tmp))))
Any ideas how to do variable assignment in SMT?
You should look into Single Static Assignment [1]. In this way you can rewrite your original code as follows.
if (x0 > y0) {
temp0 = x0;
x1 = y0;
y1 = temp0;
}
It thus becomes clear that you have two different instances of x and y. The first (x0, y0) is the one you are comparing in the if condition. The second (x1, y1) is the result of the operation.
This introduces an implicit notion of time that makes it also easier to write properties about your code. E.g.,
((x1 = x0) & (y1 = y0)) | ((x1 = y0) | (y1 = x0))
Of course, this might require adjusting other parts of your code, so that you are using the right variables.
[1] https://en.wikipedia.org/wiki/Static_single_assignment_form
We can rewrite what you want using a single expression as a tuple:
(result1, result2) = x > y ? (x, y) : (y, x)
Z3 supports tuples but I'm less experienced with that. It's probably easier to blast this into parts:
result1 = x > y ? x : y
result2 = x > y ? y : x
And the ?: operator maps to ITE in Z3.
You don't even need "temp variables" for this (but clearly you can).
(assert (=> (> s1 s2) (and (= tmp s1) (= s1 s2) (= s2 tmp))))
I think this is revealing that you don't understand that Z3 "variables" are actually constants and you cannot actually swap them. In each model they take on a single value only. There is no temporal component to constants. = means "is equal?" and not "make it equal!".

Implicit vs explicit existential quantification in SMT formulas

Given the following code:
from z3 import *
a,b,c = BitVecs('a b c', 32)
f1 = Exists([a, b, c], And(a + b == c, a < b, c == 1337))
f2 = And(a + b == c, a < b, c == 1337)
prove(f1 == f2)
I would assume that z3 implicitly existential quantifies a, b and c, in this example. Why aren't the two formulas equal, what is the difference?
The way you formulated your query doesn't really check whether f1 equals f2. Your query is essentially asking the solver to find a, b, c such that the following fails to hold:
Exists([a, b, c], And(a + b == c, a < b, c == 1337))
=
And(a + b == c, a < b, c == 1337))
And indeed, you can instantiate the outer a, b, and c such that the right hand-side is false; but the left hand side is an existential which is true; thus failing the equivalence you asked for.
It might be easier to see this with a simpler example; with just one boolean variable. You're essentially asking:
x == (Exists [x], x)
You see that those xs are actually different, so we can rename the inner one to (say) y; we get:
x == (Exist [y]. y)
Now, the right-hand-side is clearly true since there is a y that makes (Exist [y]. y) true. So, you are essentially asking the prover to establish that no matter what x you pick, it is true. Which is definitely not the case when you pick x to be false.
Indeed, you can ask Z3 to give you the formula it's trying to prove, and this is what it returns for your original query:
(set-info :status unknown)
(declare-fun c () (_ BitVec 32))
(declare-fun b () (_ BitVec 32))
(declare-fun a () (_ BitVec 32))
(assert
(let (($x24 (exists ((a (_ BitVec 32))
(b (_ BitVec 32))
(c (_ BitVec 32)) )
(and (= (bvadd a b) c) (bvslt a b) (= c (_ bv1337 32))))))
(let (($x57 (= $x24 (and (= (bvadd a b) c) (bvslt a b) (= c (_ bv1337 32))))))
(and $x57))))
(check-sat)
Which is clearly satisfiable, by the above reasoning.
(See Z3: convert Z3py expression to SMT-LIB2 from Solver object for the code that converts a z3-python query to smt-lib.)

Z3 / c++ API for quantifiers

I am using z3 with the C++ API for the example:
context c;
sort I = c.int_sort();
sort B = c.bool_sort();
expr x = c.int_const("x");
expr x1 = c.int_const("x1");
func_decl p1 = function("p1", I, B);
func_decl p2 = function("p2", I, B);
solver s(c);
s.add(forall(x, (implies(p1(x), ((p2(x)))))));
s.add(p1(x1));
The generated model is:
sat
(define-fun x1 () Int
0)
(define-fun p1 ((x!1 Int)) Bool
true)
(define-fun p2 ((x!1 Int)) Bool
true)
The imagined to have the model p1(x1) and p2(x1). I tried also with the options:
p.set("mbqi", true);
p.set("smt.mbqi.max_iterations", "10000000");
p.set("auto-config", true);
But, I have the same result. Am I missing something?
Thank you.
Z3 produces a correct model, so I'm not exactly sure what the problem is. I can only imagine that there is some confusion about the argument names of the functions.
Per declaration, p1 is a function that takes an int and returns a Boolean. When Z3 builds an interpretation for this function, it names the first argument x!1 which has nothing to do with the constant function x1 (no bang). The model returned states
(define-fun x1 () Int
0)
(define-fun p1 ((x!1 Int)) Bool
true)
which means x1 is a constant function that always returns zero, i.e., x1() := 0. Additionally, p1 is a function with one argument (called x!1) and which returns true regardless of the input, i.e., for all x!1, p1(x!1) := true.

Validation query formulation, SMT solver, Z3, STP

I have a boolean formula f(a, b, x, y). Where a and b are boolean expressions and x and y are bit vector expressions. a and b are boolean expressions possibly using expressions a, b, x and y.
I want to define the following query for validity:
f(a, b, x, y)* such that *a = false && b = false
f(a, b, x, y)* such that *a = true && b = false
In a way, I need to substitute the values of a and b in the formula on both the sides of implication.
Please advise how to create such a query.
Why not create a fresh variable for a?
you can then assert the following:
(assert (not a))
(assert (not b))
(assert (f a b x y))
(assert a2)
(assert (not (f a2 b x y)))
(check-sat)
your query is valid if and only if the query above is unsatisfiable because the bindings are represented as assertions and the implication was negated ( and flattened into two assertions)

Skolemization in Z3

I am trying to remove existential quantifiers in my theory using Skolemization. This means that I replace existential quantifiers with functions that are parameterized by the universally quantified variables in the scope of the existential quantifiers.
Here I found an explanation how to do this in Z3, but I am still having troubles doing it. Suppose the following two functions:
(define-fun f1 ((t Int)) Bool (= t 3))
(define-fun f2 () Bool (exists ((t Int)) (f1 t)))
I believe that f2 should be true, because there exists an integer t such that (f1 t) is true, namely t=3. I apply Skolemization by introducing a constant for the existentially quantified formula:
(define-const c Int)
Then the formula with the existential quantifier is rewritten to:
(define-fun f2 () Bool (f1 c))
This does not work, that is, the constant c does not have the value 3. I suspect it is because we have not given an interpretation to the constant c, because if we add (assert (= c 3)) it works fine, but this takes away the whole idea of the existential quantifier. Is there a way in which I give a less explicit interpretation to c so that this will work?
So, I think you have it about right actually, here's the script I used with automatic (via Z3's SNF tactic) and manual (via adding the constant c) skolemization, which gave the value 3 in the model for the skolem constant as expected (smt-lib script: http://rise4fun.com/Z3/YJy2 ):
(define-fun f1 ((t Int)) Bool (= t 3))
(define-fun f2 () Bool (exists ((t Int)) (f1 t)))
(declare-const c Int)
(define-fun f2a () Bool (f1 c))
(push)
(assert f2)
(check-sat) ; sat
(get-model) ; model gives t!0 = 3 (automatic skolemization in Z3)
(pop)
(push)
(assert f2a)
(check-sat) ; sat
(get-model) ; model gives c = 3 after manual skolemization
(pop)
Also, note that Z3 has a Skolem normal form (SNF) conversion tactic built in, and here's an example in z3py (link to script: http://rise4fun.com/Z3Py/ZY2D ):
s = Solver()
f1 = Function('f1', IntSort(), BoolSort())
t = Int('t')
f2 = Exists(t, f1(t))
f1p = ForAll(t, f1(t) == (t == 3)) # expanded define-fun macro (define-fun f1 ((t Int)) Bool (= t 3))
s.add(f1p)
s.add(f2)
print f1p
print f2
print s.check()
print s.model() # model has skolem constant = 3
g = Goal()
g.add(f1p)
g.add(f2)
t = Tactic('snf') # use tactic to convert to SNF
res = t(g)
print res.as_expr()
s = Solver()
s.add( res.as_expr() )
print s.check()
print s.model() # model has skolem constant = 3

Resources