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.

Theo Deep
  • 666
  • 4
  • 15

1 Answers1

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!)

alias
  • 28,120
  • 2
  • 23
  • 40
  • 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? – Theo Deep Sep 21 '22 at 21:35
  • 1
    Sure, `sumSeq` is a better name. I was alluding to the array as in the regular programming language sense, not the SMTLib sense. – alias Sep 21 '22 at 21:37
  • 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 :) – Theo Deep Sep 21 '22 at 21:43
  • 1
    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. – alias Sep 21 '22 at 21:45
  • Hmm... will try not using `sumArrayArg` (but just `Seq`) to see what happens. I think that is the way I can understand it :) Thanks again.. as always! – Theo Deep Sep 21 '22 at 21:47
  • 1
    This might help: https://z3prover.github.io/api/html/namespacez3py.html#a7029cec3faf5bd7cdd9d9e0365f8a3b5 – alias Sep 21 '22 at 21:55
  • It does, with the factorial example. Thanks a lot! – Theo Deep Sep 21 '22 at 21:57