0

I am trying to specify a concrete formula in Z3-Python.

Exists i::Integer s.t. (0<=i<|arr|) & (avg(arr)+t<arr[i])

This means: whether there is a position i::0<i<|arr| in the array whose value a[i] is greater than the average of the array avg(arr) plus a given threshold t.

I posted at Theory of arrays in Z3: (1) model is difficult to understand, (2) do not know how to implement functions and (3) difference with sequences how I approached this using arrays. Among others, I need to define a function for getting the average of the array, and another one for its length; however, approaching this problem using Z3's arrays does not seem to be a good idea.

Thus, I 'emulated' it using sequences:

t = Int('t')
avg_seq = Int('avg_seq')
i = Int('i')

seq = Const('seq', SeqSort(IntSort()))

phi = Exists(i, And([(And(2<t, t<10)), (And(0 <= i, i< Length(seq))), ((t+avg_seq<seq[i]))]))

s = Solver()
s.add(phi)
print(s.check())
print(s.model())

Where one model I got is the following:

sat
[avg_seq = 7715,
 t = 3,
 seq = Unit(7719),
 seq.nth_u = [else -> 4]]

As you can see, I solved the problem of how to calculate Length, since it is a primitive in Z3.

However, how can I create an average function? Is there any primitives for it? I guess there is not, so I planned to create a recursive version of it. The pseudo-code of my idea is as follows:

Avg(Seq) = Sum(Seq)/Length(Seq)
Sum(Seq) = Head(Seq) + Sum(Tail(Seq))

Is this idea right? How can I implement this? I was recommended to read the example of RecAddDefinition at Converting `appendo` relation from smt2 to python, but I feel pretty lost, specially when I see constructions like Const.

I also have one extra doubt: I understood that when defininf a Exists(i, ...), the top-level declaration of i is unused; however, if I do not declare it, I receive the error: name 'i' is not defined.

1 Answer 1

1

This should get you started:

from z3 import *

IntSeqSort = SeqSort(IntSort())

sumArray    = RecFunction('sumArray', IntSeqSort, IntSort())
sumArrayArg = FreshConst(IntSeqSort)

RecAddDefinition( sumArray
                , [sumArrayArg]
                , If(Length(sumArrayArg) == 0
                    , 0
                    , sumArrayArg[0] + sumArray(SubSeq(sumArrayArg, 1, Length(sumArrayArg) - 1))
                    )
                )

def avgArray(arr):
    return ToReal(sumArray(arr)) / ToReal(Length(arr))

In the above, we have defined the recursive function to sum an array, and then avgArray which returns the average. Here's a use case. Let's ask it to find a sequence of length 4, whose average is 3:

arr = Const('arr', IntSeqSort)

s = Solver()
s.add(Length(arr) == 4)
s.add(avgArray(arr) == 3)

if s.check() == sat:
    print(s.model()[arr])
else:
    print("Not sat")

When I run this, I get:

Concat(Unit(-10451),
       Concat(Unit(31365),
              Concat(Unit(-10451), Unit(-10451))))

In more familiar notation, z3 found us:

[-10451, 31365, -10451, -10451]

You can easily see that it satisfies the constraints, it has length 4 and the average of the numbers is 3.

You can take this and convert it to model your original formula in the usual way.

Regarding the existential: This is a z3 hack: To use Exists and Forall you have to define the bound-variables at the top-level, but otherwise what you defined at top and what is used locally is unrelated. Think of it as a way of telling z3 that this variable exists just for internal bookkeeping purposes, but otherwise is irrelevant. If it helps, you can give it a really unique name (like localExistsVariable) so it doesn't confuse you in any other way. (And don't use it anywhere else!)

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks a lot! I tested it and definitively seems to work. However, I need to read it, since there are several implementation points I do not get (going to ask below). But first, should it not be sumSeq (and avgSeq) instead of sumArray? Or am I missing something where you are using arrays?
Sure, sumSeq is a better name. I was alluding to the array as in the regular programming language sense, not the SMTLib sense.
Perfect! As for the implementation, what I understand is that If(Length(sumArrayArg) == 0, 0 models the part when there the list is empty; whereas sumArrayArg[0] + sumArray(SubSeq(sumArrayArg, 1, Length(sumArrayArg) - 1)) models the contrary, since SubSeq(sumArrayArg, 1, Length(sumArrayArg) - 1) takes the subsequence of Seq from positions 1 to L-1. However, I do not understand why use sumArrayArg[0], instead of Seq[0]? Why put sumArray, [sumArrayArg] at the beginning? Also, what does sumArrayArg = FreshConst(IntSeqSort) mean? I am confused, sorry :)
It's just the syntax; think of it defining it as a function, where sumArrayArg is the argument. If you want, you can insert print(s.sexpr()) before you call check() to see the kind of SMTLib it generates.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.