I'm in the process of verifying properties about equations written in a synchronous programming language, by compiling said language to Z3, using the PDR engine. I use Z3 4.5.1 compiled from Github's master branch a week ago.
For your curiosity's sake, I use a derivative of the translation from the synchronous language Lustre to Z3 proposed in this paper (but it's not necessary to read it to understand what I want to do).
I have a problem with the following program, that returns sat while I think it should return unsat:
(declare-var out Int)
(declare-var next_out Int)
(declare-var mem Int)
(declare-var next_mem Int)
(declare-var ok Bool)
(declare-var next_ok Bool)
(declare-rel Init (Int Int Bool))
(declare-rel Trans (Int Int Bool Int Int Bool))
(declare-rel Main (Int Int Bool))
(declare-rel Error ())
(rule (=> (= mem 0) (Init out mem ok)))
(rule (=> (and (= next_ok (>= next_out 0))
(= next_mem (+ mem 1))
(= next_out mem))
(Trans out mem ok next_out next_mem next_ok)))
(rule (=> (and (Init out mem ok)
(Trans out mem ok next_out next_mem next_ok))
(Main next_out next_mem next_ok)))
(rule (=> (and (Trans out mem ok next_out next_mem next_ok)
(Main out mem ok))
(Main next_out next_mem next_ok)))
(rule (=> (and (Main out mem ok)
(not ok))
Error))
(query Error :print-certificate true)
I'll try and simplify what's my intention so you don't have to go through the paper. We define three sequences mem, out and ok so that:
forall n > 0. mem(n) = mem(n - 1) + 1 with mem(0) = 0
and out(n) = mem(n - 1)
and ok(n) = out(n) >= 0
We want to prove that forall n > 0. ok(n) = true.
In the Z3 program, you can think of mem, out and ok as the values of the sequences at n - 1 (or the memories containing the previous values of the sequences) and next_* as the values at n.
The Init relation states that the initial equations are correct (that is the equations for n = 0), in our case we just have mem = 0 while out and ok are left free (that's rule 1). The Trans relation establishes that the relation between the n-th values and the n - 1-th values is correct (that's rule 2).
The Main relation states that, for a certain n > 0, the values of the sequences are coherent with the equations given (I'm not sure that's the best way of explaining it, if it's unclear please tell me). Thus rule 3 states that if the relations between the memories at n = 0 and the relation between these memories and the new values at n = 1 are correct, then we get a coherent set of values for n = 1. Rule 4 states that if we have a coherent set of sequence value at n, and that the relation between them and the values at n + 1 is correct, then we get a new coherent set of sequence values at n + 1.
The last rule is the property we want to check: given a coherent set of values, we cannot have that ok is false.
When I run this, I get:
sat
(and (Main!slice!1 false) (not (>= (:var 5) 0)) (Main!slice!1 true))
and I can't understand why. Am I missing something?
EDIT
I continued to work on the problem, trying to understand what Z3 was doing with what I gave him.
I ran it with the option fixedpoint.xform.inline_eager=false which (I suppose) makes it inline less. When running with verbosity level 1, I saw that after the first application of N7datalog15mk_rule_inlinerE I had 5 rules instead of 3 (see below for the full output). I also had the following result :
unsat
(define-fun I ((x!0 Int) (x!1 Int) (x!2 Bool)) Bool
(>= x!1 0))
(define-fun T ((x!0 Int) (x!1 Int) (x!2 Bool) (x!3 Int) (x!4 Int) (x!5 Bool)) Bool
(and (<= (+ x!1 (* (- 1) x!4)) (- 1)) (= x!5 (>= x!1 0))))
(define-fun M ((x!0 Int) (x!1 Int) (x!2 Bool)) Bool
(and x!2 (>= x!1 1)))
which is quite different from the original oneā¦ So unless I'm wrong about what the option above does, I guess it's a bug so I'll open an issue.
Full output
Running with z3 <file> -smt2 -v:1:
(transform N7datalog13mk_coi_filterE...no-op 0s)
(transform N7datalog25mk_interp_tail_simplifierE...6 rules 0s)
(transform N7datalog27mk_quantifier_instantiationE...no-op 0s)
(transform N7datalog8mk_scaleE...no-op 0s)
(transform N7datalog18mk_karr_invariantsE...no-op 0s)
(transform N7datalog14mk_array_blastE...no-op 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog12mk_bit_blastE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...3 rules 0s)
(transform N7datalog13mk_coi_filterE...no-op 0s)
(transform N7datalog25mk_interp_tail_simplifierE...3 rules 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...no-op 0s)
(transform N7datalog13mk_coi_filterE...no-op 0s)
(transform N7datalog25mk_interp_tail_simplifierE...3 rules 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...no-op 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...no-op 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...no-op 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog8mk_sliceE...3 rules 0s)
Entering level 1
Entering level 2
2 query!0 closed
true 1
1 Main!slice!1 closed
(not Main!slice!1_0_n) 182
0 Main!slice!1 closed
true 1
goals 0
sat
(and (Main!slice!1 false) (not (>= (:var 5) 0)) (Main!slice!1 true))
Running with z3 <file> -smt2 -v:1 fixedpoint.xform.inline_eager=false:
(transform N7datalog13mk_coi_filterE...no-op 0s)
(transform N7datalog25mk_interp_tail_simplifierE...6 rules 0s)
(transform N7datalog27mk_quantifier_instantiationE...no-op 0s)
(transform N7datalog8mk_scaleE...no-op 0s)
(transform N7datalog18mk_karr_invariantsE...no-op 0s)
(transform N7datalog14mk_array_blastE...no-op 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog12mk_bit_blastE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...5 rules 0s)
(transform N7datalog13mk_coi_filterE...no-op 0s)
(transform N7datalog25mk_interp_tail_simplifierE...5 rules 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...no-op 0s)
(transform N7datalog13mk_coi_filterE...no-op 0s)
(transform N7datalog25mk_interp_tail_simplifierE...5 rules 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...no-op 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...no-op 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog15mk_rule_inlinerE...no-op 0s)
(transform N7datalog22mk_subsumption_checkerE...no-op 0s)
(transform N7datalog8mk_sliceE...no-op 0s)
Entering level 1
Entering level 2
Entering level 3
(define-fun Init ((x!0 Int) (x!1 Int) (x!2 Bool)) Bool
(>= x!1 0))
(define-fun Main ((x!0 Int) (x!1 Int) (x!2 Bool)) Bool
(and (>= x!1 1) x!2))
(define-fun Trans ((x!0 Int)
(x!1 Int)
(x!2 Bool)
(x!3 Int)
(x!4 Int)
(x!5 Bool)) Bool
(and (<= (+ x!1 (* (- 1) x!4)) (- 1)) (= x!5 (>= x!1 0))))
unsat
(define-fun Init ((x!0 Int) (x!1 Int) (x!2 Bool)) Bool
(>= x!1 0))
(define-fun Main ((x!0 Int) (x!1 Int) (x!2 Bool)) Bool
(and x!2 (>= x!1 1)))
(define-fun Trans ((x!0 Int)
(x!1 Int)
(x!2 Bool)
(x!3 Int)
(x!4 Int)
(x!5 Bool)) Bool
(and (<= (+ x!1 (* (- 1) x!4)) (- 1)) (= x!5 (>= x!1 0))))
This behaviour was due to a bug that has now been fixed. The discussion here contains the commit correcting it.
Related
My goal is to define a function, which takes an input integer sequence and outputs an integer sequence with the same length, but containing only the first element of the input sequence. For example (in pseudo-code):
f([7,5,6]) = [7,7,7]
For this I declare a function in z3 as:
(declare-fun f ((Seq Int)) (Seq Int))
and try to force the expected behavior with the assertion:
(assert
(forall ((arr (Seq Int)))
(and
(=
(seq.len arr)
(seq.len (f arr))
)
(forall ((i Int))
(implies
(and
(>= i 0)
(< i (seq.len arr))
)
(=
(seq.at arr 0)
(seq.at (f arr) i)
)
)
)
)
))
The problem is that the program does not terminate, which I suspect is caused by the all-quantifier. To test whether my conditions are correct I declared two constants and saw that for concrete values the conditions are correct:
(define-const first (Seq Int)
(seq.++ (seq.unit 1) (seq.unit 2))
)
(declare-const second (Seq Int))
(assert
(and
(=
(seq.len first)
(seq.len second)
)
(forall ((i Int))
(implies
(and
(>= i 0)
(< i (seq.len first))
)
(=
(seq.at first 0)
(seq.at second i)
)
)
)
)
)
(check-sat)
(get-model)
My question: How would it be possible to integrate the conditions in the assertions with the expected behavior of the f function? The function should be total, which means it should be defined for all possible input sequences, but this leads me to think that an all quantifier is definitely needed in my case.
Reasoning with such recursive data-types/values is not a strong suit for SMT solvers. Most problems will require induction and SMT-solvers don't do induction out-of-the box.
Having said that, you can code what you want using the new declare-fun-rec construct:
(define-fun-rec copyHeadAux ((x (Seq Int)) (l (Seq Int))) (Seq Int)
(ite (= 0 (seq.len l))
(as seq.empty (Seq Int))
(seq.++ x (copyHeadAux x (seq.extract l 1 (seq.len l))))))
(define-fun copyHead ((l (Seq Int))) (Seq Int)
(ite (= 0 (seq.len l))
(as seq.empty (Seq Int))
(copyHeadAux (seq.at l 0) l)))
(define-fun test () (Seq Int) (seq.++ (seq.unit 7) (seq.unit 5) (seq.unit 6)))
(declare-const out (Seq Int))
(assert (= out (copyHead test)))
(check-sat)
(get-model)
When I run this, I get:
sat
(
(define-fun out () (Seq Int)
(seq.++ (seq.unit 7) (seq.unit 7) (seq.unit 7)))
(define-fun test () (Seq Int)
(seq.++ (seq.unit 7) (seq.unit 5) (seq.unit 6)))
(define-fun copyHead ((x!0 (Seq Int))) (Seq Int)
(ite (= 0 (seq.len x!0))
(as seq.empty (Seq Int))
(copyHeadAux (seq.at x!0 0) x!0)))
)
which is the correct answer you're looking for. But unless your constraints only involve "constant-folding" cases (i.e., where copyHead is always applied to a constant known value), you're likely to get unknown as the answer, or have the solver go into an infinite e-matching loop.
It's best to use a proper theorem prover (Isabelle, HOL, Lean, ACL2 etc.) for reasoning with these sorts of recursive definitions. Of course, SMT solvers get better over time and maybe one day they'll be able to handle more problems like this out-of-the-box, but I wouldn't hold my breath.
some example from the rise4fun-site:
(declare-const x Int)
(declare-const y Int)
(declare-const z Int)
(declare-const a Int) ; this is added
(assert (= a 3 )) ; this is added: a := 3
(assert (< 0 x 10)) ; rewritten, but same constraint
(assert (< 0 y 10))
(assert (< 0 z 10))
(assert (= (+ (* 3 y) (* 2 x)) z)) ; plain function from rise4fun
;(assert (= (+ (* a y) (* 2 x)) z)) ; here literal 3 is replaced by a
(check-sat-using (then (using-params simplify :arith-lhs true :som true)
normalize-bounds
lia2pb
pb2bv
bit-blast
sat))
(get-model)
(get-info :version)
when i comment the plain function from rise4fun and uncomment my function, the solver will fail to produce a result and respond with 'unknown' (tried with 4.8.0). Isn't the solver or some preprocessor smart enough to see that 'a' is just a constant with fixed value 3?
That's right, the simplify tactic is not smart enough to propagate values because it would be too expensive in general. However, ctx-simplify or propagate-values do the job. For instance:
(check-sat-using (then (using-params simplify :arith-lhs true :som true)
propagate-values
normalize-bounds
lia2pb
pb2bv
bit-blast
sat))
I'm trying to get z3 to work (most of the time) for very simple non-linear integer arithmetic problems. Unfortunately, I've hit a bit of a wall with exponentiation. I want to be able handle problems like x^{a+b+2} = (x * x * x^{a} * x{b}). I only need to handle non-negative exponents.
I tried redefining exponentiation as a recursive function (so that it's just allowed to return 1 for any non-positive exponent) and using a pattern to facilitate z3 inferring that x^{a+b} = x^{a} * x^{b}, but it doesn't seem to work - I'm still timing out.
(define-fun-rec pow ((x!1 Int) (x!2 Int)) Int
(if (<= x!2 0) 1 (* x!1 (pow x!1 (- x!2 1)))))
; split +
(assert (forall ((a Int) (b Int) (c Int))
(! (=>
(and (>= b 0) (>= c 0))
(= (pow a (+ b c)) (* (pow a c) (pow a b))))
:pattern ((pow a (+ b c))))))
; small cases
(assert (forall ((a Int)) (= 1 (pow a 0))))
(assert (forall ((a Int)) (= a (pow a 1))))
(assert (forall ((a Int)) (= (* a a) (pow a 2))))
(assert (forall ((a Int)) (= (* a a a) (pow a 3))))
; Our problem
(declare-const x Int)
(declare-const i Int)
(assert (>= i 0))
; This should be provably unsat, by splitting and the small case for 2
(assert (not (= (* (* x x) (pow x i)) (pow x (+ i 2)))))
(check-sat) ;times out
Am I using patterns incorrectly, is there a way to give stronger hints to the proof search, or an easier way to do achieve what I want?
Pattern (also called triggers) may only contain uninterpreted functions. Since + is an interpreted function, you essentially provide an invalid pattern, in which case virtually anything can happen.
As a first step, I disabled Z3's auto-configuration feature and also MBQI-based quantifier instantiation:
(set-option :auto_config false)
(set-option :smt.mbqi false)
Next, I introduced an uninterpreted plus function and replaced each application of + by plus. That sufficed to make your assertion verify (i.e. yield unsat). You can of course also axiomatise plus in terms of +, i.e.
(declare-fun plus (Int Int) Int)
(assert (forall ((a Int) (b Int))
(! (= (plus a b) (+ a b))
:pattern ((plus a b)))))
but your assertion already verifies without the definitional axioms for plus.
Following code I have tried in Online and Offline Z3
(set-option :smt.mbqi true)
(declare-var X Int)
(declare-var X_ Int)
(declare-var a_ Int)
(declare-var su_ Int)
(declare-var t_ Int)
(declare-var N1 Int)
(assert (>= X 0))
(assert (forall ((n1 Int)) (=> (< n1 N1) (>= X (* (+ n1 1) (+ n1 1))))))
(assert (= X_ X))
(assert (= a_ N1))
(assert (= su_ (* (+ N1 1) (+ N1 1))))
(assert (= t_ (* (+ N1 1) 2)))
(assert (< X (* (+ N1 1) (+ N1 1))))
(assert (not (< X (* (+ a_ 1) (+ a_ 1)))))
(check-sat)
Result unsat
Following code I have tried in Z3PY
set_option('smt.mbqi', True)
s=Solver()
s.add(X>=0)
s.add(ForAll(n1,Implies(n1 < N1,((n1+1)**2)<=X)))
s.add(((N1+1)**2)>X)
s.add(X_==X)
s.add(a_==N1)
s.add(su_==((N1+1)**2))
s.add(t_==(2*(N1+1)))
s.add(Not(((a_+1)**2)>X))
result- unknown
Is processing power different?
The reason for the difference in results is because the input is not the same. For instance, the expression
(N1+1)**2
is semantically the same as
(* (+ N1 1) (+ N1 1))
but because of the syntactic difference, Z3 will not simplify the formula to something that it can solve easily. The syntactically equivalent problem in Python is
s.add(X>=0)
s.add(ForAll(n1,Implies(n1 < N1,((n1+1)**2)<=X)))
s.add(((N1+1)*(N1+1)) > X)
s.add(X_==X)
s.add(a_==N1)
s.add(su_==((N1+1)*(N1+1)))
s.add(t_==(2*(N1+1)))
s.add(Not(((a_+1)*(a_+1))>X))
which yields the desired result.
Are the constraints the same?
I don't see the python variant of:
(assert (< X (* (+ N1 1) (+ N1 1))))
After installing Z3 V3.1, following SMT-LIB code is not working. It was quite good in my earlier version (Z3 V2.19).
(define-fun getIP ((o0 Int) (o1 Int) (o2 Int) (o3 Int)) BitVec[32]
(bvor (bvshl (int2bv[32] o0) (int2bv[32] 24))
(bvshl (int2bv[32] o1) (int2bv[32] 16))))
(declare-funs ((dip BitVec[32]) (m BitVec[32])))
(declare-funs ((s Bool) (d Bool) (y Int) (z Int)))
(declare-funs ((r0 Bool) (r1 Bool) (f Bool)))
(declare-funs ((r0do0 Int) (r0do1 Int) (r0do2 Int) (r0do3 Int) (r0m Int) (r0nh Int)))
(declare-funs ((r1do0 Int) (r1do1 Int) (r1do2 Int) (r1do3 Int) (r1m Int) (r1nh Int)))
(declare-funs ((fso0 Int) (fso1 Int) (fso2 Int) (fso3 Int) (fsm Int)))
(declare-funs ((fdo0 Int) (fdo1 Int) (fdo2 Int) (fdo3 Int) (fdm Int) (fp Int) (fnh Int)))
(declare-funs ((so0 Int) (so1 Int) (so2 Int) (so3 Int)))
(declare-funs ((do0 Int) (do1 Int) (do2 Int) (do3 Int)))
(assert (=> f (and (= fso0 172) (= fso1 16) (= fso2 0) (= fso3 0) (= fsm 16)
(= fdo0 150) (= fdo1 96) (= fdo2 1) (= fdo3 0) (= fdm 24)
(= fp 0))))
(assert (=> r0 (and (= r0do0 150) (= r0do1 96) (= r0do2 0) (= r0do3 0) (= r0m 16))))
(assert (=> r1 (and (= r1do0 172) (= r1do1 16) (= r1do2 0) (= r1do3 0) (= r1m 16))))
(assert (=> s (and (= so0 172) (= so1 16) (= so2 0) (= so3 1))))
(assert (=> d (and (= do0 150) (= do1 96) (= do2 1) (= do3 120))))
(assert (= m (int2bv[32] 16)))
(assert ((= dip (getIP so0 so1 so2 so3))))
(check-sat) ; sat
(model)
What do I need to change in the above code to run it in the version 3.1.
Z3 3.x is compliant with the SMT 2.0 standard. Versions 2.x were not. For example, there is no declare-funs command in SMT 2.0; the 32-bitvector sort is (_ BitVec 32) instead of BitVec[32]. Z3 still supports the old non-compliant SMT 2.0 parser. You just have to provide the command line option -smtc. That being said, I suggest you move to SMT 2.0 standard. The SMT 2.0 input language is the official input language for Z3. Moreover, many new features are only available in this frontend (Example: parametric types).