Handling quoted expressions in Z3 - z3

I'm currently working with Z3's unsat_core functionality in the context of a model checking class at university.
The issues arises when trying to work with the assumptions that Z3 provides me with in the core:
E.g.:
z3::expr_vector unsat_core = exp_solver.unsat_core();
for(auto const c : unsat_core) {
DEBUG << "\n" << c;
}
prints a list containing, for example, such an expression:
|(let ((a!1 (bvnot (ite (= current_state_V0 ((_ zero_extend 1) #b11)) #b1 #b0))))
(let ((a!2 (bvnot (bvand a!1 (bvnot ((_ extract 2 2) current_state_V0))))))
(let ((a!3 (bvor a!2
(ite (= next_state_V0 (bvadd current_state_V0 #b001)) #b1 #b0)
(bvnot #b1))))
(= a!3 #b1))))|
We add and track expressions via this wrapper:
void add_and_track(z3::solver& s, z3::expr_vector const &v)
{
check_context(s, v);
for (unsigned i = 0; i < v.size(); ++i)
{
s.add(v[i], v[i].to_string().c_str());
}
}
I now need to encode these assumptions with my primed variables and I intend to use z3::expr::substitute for that, but that does not seem to work.
DEBUG << unsat_core[0];
z3::expr foo = unsat_core[0].substitute(V0, V1);
DEBUG << unsat_core[0];
DEBUG << foo;
all DEBUG calls print out the exact same (quoted) expression from above.
V0 and V1 are z3::expr_vectors containing the variables and the primed variables, and DEBUG is a wrapper around std::cout.
My best guess is that these expressions are 'quoted' https://www.lri.fr/~conchon/TER/2013/2/SMTLIB2.pdf#page=22 Is this correct?
What I would like to achieve is either to be able to substitute or the extract the expression that is, as I am guessing, quoted.

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!".

Understanding the index of all constants of a model

I guess my issue is linked with Read func interp of a z3 array from the z3 model, but still I can't understand how to fix it.
Edit: I think it is also linked with de bruijn index: Understanding the indexing of bound variables in Z3
Here is the small example I have built to explain the problem:
#include <iostream>
#include <sstream>
#include <cassert>
#include "z3++.h"
using namespace z3;
int main(void)
{
context ctx;
params p(ctx);
p.set("MACRO_FINDER",true);
expr_vector v(ctx);
sort_vector sv(ctx);
for(int i = 0; i < 3; i++)
{
std::ostringstream o;
o << "c[" << i << "]";
expr c = ctx.bv_const(o.str().c_str(),1);
v.push_back(c);
sv.push_back(ctx.bv_sort(1));
}
expr x = ctx.bv_const("x",8);
v.push_back(x);
sv.push_back(ctx.bv_sort(8));
expr one_bit = ctx.bv_val(1,1);
expr two = ctx.bv_val(2,8);
expr one = ctx.bv_val(1,8);
expr zero = ctx.bv_val(0,8);
expr fcore = x + ite(v[1] == one_bit , one, zero) + ite(v[2] == one_bit, two, zero);
func_decl f = ctx.function("f",sv,ctx.bv_sort(8));
solver s(ctx);
s.set(p);
s.add(forall(v,f(v) == fcore));
expr_vector t1(ctx);
expr_vector t2(ctx);
t1.push_back(v[0]); t1.push_back(v[1]); t1.push_back(v[2]); t1.push_back(ctx.bv_val(0,8));
t2.push_back(v[0]); t2.push_back(v[1]); t2.push_back(v[2]); t2.push_back(ctx.bv_val(1,8));
expr constraints = (f(t1) == ctx.bv_val(1,8)) && (f(t2) == ctx.bv_val(2,8));
s.add(exists(v[0],v[1],v[2],constraints));
std::cout << "Solver: " << s << "\n\n";
if(s.check()==sat)
{
model m = s.get_model();
std::cout << "Model: " << m << "\n\n";
std::cout << "Number of constants: " << m.num_consts() << "\n";
expr F = m.eval(f(v),true);
for(size_t i = 0; i < m.num_consts(); ++i)
std::cout << "\t constant " << i << ": " << "(" << m.get_const_decl(i).name() << ") " << m.get_const_interp(m.get_const_decl(i)) << "\n";
std::cout << "Number of functions: " << m.num_funcs() << "\n";
std::cout << "\t" << F << "\n";
}
else
std::cout << "unsat\n";
return 0;
}
By runing this program, I get the following output:
Solver: (solver
(forall ((|c[0]| (_ BitVec 1))
(|c[1]| (_ BitVec 1))
(|c[2]| (_ BitVec 1))
(x (_ BitVec 8)))
(= (f |c[0]| |c[1]| |c[2]| x)
(bvadd x (ite (= |c[1]| #b1) #x01 #x00) (ite (= |c[2]| #b1) #x02 #x00))))
(exists ((|c[0]| (_ BitVec 1)) (|c[1]| (_ BitVec 1)) (|c[2]| (_ BitVec 1)))
(and (= (f |c[0]| |c[1]| |c[2]| #x00) #x01)
(= (f |c[0]| |c[1]| |c[2]| #x01) #x02))))
Model: (define-fun |c[2]!0| () (_ BitVec 1)
#b0)
(define-fun |c[1]!1| () (_ BitVec 1)
#b1)
(define-fun f ((x!1 (_ BitVec 1))
(x!2 (_ BitVec 1))
(x!3 (_ BitVec 1))
(x!4 (_ BitVec 8))) (_ BitVec 8)
(bvadd x!4 (ite (= #b1 x!3) #x02 #x00) (ite (= #b1 x!2) #x01 #x00)))
Number of constants: 2
constant 0: (c[2]!0) #b0
constant 1: (c[1]!1) #b1
constant 2: (c[0]) #b0
constant 3: (c[1]) #b0
constant 4: (c[2]) #b0
constant 5: (x) #x00
Number of functions: 1
#x00
I don't get:
Why it enumerates 6 constants and not 3 ?
How to retrieve the value of the nth constant of vector "v" without parsing their name, which is not the nth constant of the model "m" ?
Why c[1] evaluates to 0 while I would have expected it to evaluate to 1 ?
What "!x" means in the name of the constant c[2]!0 and c[1]!1 ?
I would like to re-inject evaluations of c[0], c[1] and c[2] into the function f() in order to simplify its expression (I expect to get x+1)
Note: c[0] is not used on purpose...
Thanks Tushar for answering the post. You are right that the additional variables come from the existential quantifier. Z3 will skolemize these variables and as it currently stands, the model returned by Z3 includes constants from skolemized existential quantifiers.
This is evidently confusing and we may in the future filter
such variables (and functions) away from the model construction. On the other hand, the naming conventions used for the existential variables retain the names from the quantifiers so that it may be possible, at least manually, to track back the origin of those extra variables.

function to get nibbles using Z3 and bitvector theory

I'm trying to learn a little about z3 and the bit vector theory.
My intention is to make a function to get the nibble from a position of the bitvector
This code returns the nibble:
(define-fun g_nibble(
(l ( _ BitVec 12))
(idx (Int))
) ( _ BitVec 4)
(ite
(= idx 1) ((_ extract 11 8) l)
(ite
(= idx 2) ((_ extract 7 4) l)
(ite
(= idx 3) ((_ extract 3 0) l)
(_ bv0 4)
)
)
))
the problem is that i want avoid the multiples ite calls.
I tried to replace ((_ extract 3 0) l) for somthing like ((_ extract (+ 4 idx) idx l) but it doesn't work.
Thanks
P.S: The idea is use z3 from command line (without using any library).
The extract function only takes numerals as arguments, not arbitrary expressions. However, we can shift the expression to one direction and then extract the first or last four bits, e.g. along the lines of
((_ extract 11 8) (bvshl l (bvmul idx four)))
(where idx and four are bit-vector expressions of size 12).

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

unsat core function in z3

Suppose I read the SMTLIB formula using the API:
context ctx;
...
expr F = to_expr(ctx, Z3_parse_smtlib2_file(ctx,argv[1],0,0,0,0,0,0));
The expression F is a conjunction of assertions of the form:
(and (< (+ x y) 3)
(> (- x1 x2) 0)
(< (- x1 x2) 4)
(not (= (- x1 x2) 1))
(not (= (- x1 x2) 2))
(not (= (- x1 x2) 3)))
I'd like to extract each individual assertion from this conjunction using the following code fragment from post: How to use z3 split clauses of unsat cores & try to find out unsat core again
F = F.simplify();
for (unsigned i = 0; i < F.num_args(); i++) {
expr Ai = F.arg(i);
// ... Do something with Ai, just printing in this example.
std::cout << Ai << "\n";
}
After utilizing the F.arg(i), the original clause (< (+ x y) 3) has been changed into (not (<= 3 (+ x y))). Here is my
a) question : How can I place the clause (not (<= 3 (+ x y))) to (< (+ x y) 3) ?
b) question : I consider the symbol <= mean to imply in this case, not mean to less than. Am I right?
c) question : Because the clause (not (<= 3 (+ x y))) model is true or false, how can I get arithmetic values such as x = 1, y = -1?
It's very grateful for any suggestion.
Thank you very much.
The expression (< (+ x y) 3) is transformed into (not (<= 3 (+ x y))) when F = F.simplify().
In the code fragment you used, the method simplify() is used to "flat" nested "and"s. That is, a formula (and (and A B) (and C (and D E))) is flattened into (and A B C D E). Then, all conjuncts can be easily traversed using the for-loop. However, the simplify() will also perform other transformations in the input formula. Keep in mind, that all transformation preserve equivalence. That is, the input and output formula are logically equivalent. If the transformations applied by simplify() are not desirable, I suggest you avoid this method. If you still want to traverse nested "and"s, you can use an auxiliary todo vector. Here is an example:
expr_vector todo(c);
todo.push_back(F);
while (!todo.empty()) {
expr current = todo.back();
todo.pop_back();
if (current.decl().decl_kind() == Z3_OP_AND) {
// it is an AND, then put children into the todo list
for (unsigned i = 0; i < current.num_args(); i++) {
todo.push_back(current.arg(i));
}
}
else {
// do something with current
std::cout << current << "\n";
}
}

Resources