0

I am trying to add a constraint from within the Z3py code that requires extracting of a value for an ArithRef variable but I'm unable to do so. I have attached the Z3 snippet below:

from z3 import *
import random

R = 3
H = 4

Dest = [[2,3,4], [0,3,2,5], [1,4,5], [1,4,2], [3,1], [2,3,1]]

s = Solver()

T =[[ Int('r%d_t%d' % (k,t))
         for k in range (R)] for t in range (H)]


for t in range(H):
    for r in range(R):
        s.add(If(t==0, If(r==0, T[t][r]==0, T[t][r]==-1),
                 If(T[t-1][r]==0, T[t][r]==random.choice(Dest[0]), T[t][r]==-1)))

Here I get the solution as follows:

[[ 0  2 -1 -1]
 [-1 -1 -1 -1]
 [-1 -1 -1 -1]]

However, I try to generalize the constraint to the following,

for t in range(H):
        for r in range(R):
            s.add(If(t==0, If(r==0, T[t][r]==0, T[t][r]==-1),
                     If(T[t-1][r]==0, T[t][r]==random.choice(Dest[T[t-1][r]]), T[t][r]==-1)))

I get the error:

list indices must be integers or slices, not ArithRef

How can this issue be resolved ?

Mario
  • 17
  • 3

1 Answers1

0

In general, you cannot mix and match indexing into a Python list or calling random on a list when symbolic variables are involved. That is, it is illegal to call A[k] when k is a symbolic integer. That's what the error message is telling you: Until you actually run the solver (via a call to s.check()) and grab the value from a model, you cannot index a list via a symbolic variable. Similarly, calling random.choice isn't going to work either when you need to symbolically pick which list to pick from.

What you need to do, instead, is to create symbolic index values that correspond to your random values, and do a symbolic walk down the list. Something like this:

from z3 import *

R = 3
H = 4

Dest = [[2,3,4], [0,3,2,5], [1,4,5], [1,4,2], [3,1], [2,3,1]]

s = Solver()

T = [[Int('r%d_t%d' % (k,t)) for k in range (R)] for t in range (H)]

# Symbolically walk over a list, grabbing the ith element of it
# The idea here is that i is not just an integer, but can be a symbolic
# integer with other constraitns on it. We also take a continuation, k,
# which we call on the result, for convenience. It's assumed that the
# caller makes sure lst[i] is always within bounds; i.e., i >= 0, and
# i < len(lst).
def SymbolicWalk(i, lst, k):
   if len(lst) == 1:
       return k(lst[0])
   else:
       return If(i == 0, k(lst[0]), SymbolicWalk(i-1, lst[1:], k));

# Pick a random element of the list
# This is the symbolic version of random.choice
def SymbolicChoice(lst):
    i = FreshInt('internal_choice')
    s.add(i >= 0)
    s.add(i <  len(lst))
    return SymbolicWalk(i, lst, lambda x: x)

# Grab the element at given symbolic index, and then call the symbolic choice on it
def index(i, lst):
    return SymbolicWalk(i, lst, SymbolicChoice)

for t in range(H):
    for r in range(R):
        s.add(If(t==0, If(r==0, T[t][r]==0, T[t][r]==-1),
                 If(T[t-1][r]==0, T[t][r]==index(T[t-1][r], Dest), T[t][r]==-1)))

print(s.check())
print(s.model())

I've added some comments above which I hope should help. But the work-horse here is the function SymbolicWalk which achieves the indexing of a list with a symbolic value. Note the creation of "random"-indexes via the call to FreshInt.

When I run the above it produces:

[ r2_t3 = -1,
 r1_t3 = -1,
 r0_t3 = -1,
 r2_t2 = -1,
 r1_t2 = -1,
 r0_t2 = -1,
 r2_t1 = -1,
 r1_t1 = -1,
 r0_t1 = 2,
 r2_t0 = -1,
 r1_t0 = -1,
 r0_t0 = 0]

which should be what you are looking for. Note that I elided the outputs to variables internal_choice!k which are internally generated in calls to FreshInt; you shouldn't refer to those values; they are used internally to get the random value from your Dest list.

alias
  • 28,120
  • 2
  • 23
  • 40
  • Thank you. I have a couple of questions here. 1. Should the call to SymbolicChoice not be SymbolicChoice(lst) ? 2. r0_t1 seems to be always taking the value 2 here. My purpose of intially taking random.choice was to keep changing the values. Which means, r0_t1 could be 2/3/4. – Mario Mar 29 '22 at 14:54
  • (1) No; it's the continuation. The caller invokes it. If you try like you suggested, you should get a run-time error. (2) That's expected. The symbolic program you wrote will "explore" all possible choices to that variable, in a deterministic way. (Assuming you use the same version of z3 etc.). That is, if you want to see other "solutions" to your constraints, you need to add blocking clauses and explore what other possibilities are there. See, for instance, https://stackoverflow.com/questions/11867611/z3py-checking-all-solutions-for-equation – alias Mar 29 '22 at 15:15
  • I actually did use the blocking clauses to find the other solutions but I still ended up getting the value of r0_t1 as 2. The 'internal choice' every single time is 0, whereas it should range between 0 and 2. I'm supposing the issue here is we are passing `Dest` to index method which makes `len(lst)` as 1. `lst` should be `Dest[T[t-1][r]]` such that the `len(lst)` value is 3. I'm hoping there is a way to resolve this? – Mario Mar 29 '22 at 17:40
  • You should be able to explore the entire state space with blocking clauses; so I'm not sure why you don't see that. Try to post a simplified version of the model exhibiting this one issue as a separate question. – alias Mar 29 '22 at 18:04
  • Note that when you add the blocking clauses, you should do it for the internal-choice variables as well. That's what's going to let you explore the whole state space. – alias Mar 29 '22 at 18:40
  • Okay then I should try to explore the blocking clauses for the internal choice variables as well. But before that, could you please confirm if you're getting anything other than `internal_choice!140 = 0, internal_choice!106 = 0, internal_choice!88 = 0, internal_choice!105 = 0, ... ` every time you compile the above code ? This is because I am unable to figure out a simplified version of this issue to post as a separate question. – Mario Mar 30 '22 at 01:30
  • Yeah, you'll get `0`'s as their values, because that's the least they can be and there's no other constraint on them. z3 has a tendency to choose the `low` value when the constraint happens to be `low <= I <= high`, which is the case here. Doing `all_smt` with all the choice variables included should explore the whole search space. – alias Mar 30 '22 at 02:11
  • Oh.. okay. For future reference, is there any book/pdf/online materials available for studying the Z3py ? Like the other programming languages do ? – Mario Mar 30 '22 at 03:28
  • Sure. Start here: https://theory.stanford.edu/~nikolaj/programmingz3.html – alias Mar 30 '22 at 14:01