简体   繁体   中英

z3py: Symmetry breaking constraint by lexicographic order

I have two arrays of integer variables (they represent 2D spatial coordinates) and one is a symmetric disposition of the other. To break simmetry I want to check that the found solution described by the first array (let's call it P) is in lexicographic order with respect to the symmetric solution (so I can discard the symmetric solution). Anyway, I'm having trouble writing this a function that checks the lexicographic order:

So far (but it is incorrect), I came up with this:

from z3 import *
from math import ceil

width = 8
blocks = 4
x = [3, 3, 5, 5]
y = [3, 5, 3, 5]

# [blocks x 2] list of integer variables
P = [[Int("x_%s" % (i + 1)), Int("y_%s" % (i + 1))]
     for i in range(blocks)]

# value/ domain constraint
values = [And(0 <= P[i][0], P[i][0] + x[i] <= width, 0 <= P[i][1])  # , P[i][1] + y[i] <= height)
          for i in range(blocks)]

# no overlap constraint
no_overlap = [Or(i == j,  # indices must be different
                 Or(P[j][0] + x[j] <= P[i][0],  # left
                    P[j][0] >= P[i][0] + x[i],  # right
                    P[j][1] + y[j] <= P[i][1],  # down
                    P[j][1] >= P[i][1] + y[i]  # up
                    ))
              for j in range(blocks) for i in range(blocks)]


# Return maximum of a vector; error if empty
def symMax(vs):
    maximum = vs[0]
    for value in vs[1:]:
        maximum = If(value > maximum, value, maximum)
    return maximum


obj = symMax([P[i][1] + y[i] for i in range(blocks)])


def symmetric(on_x, on_y):
    if on_x and on_y:
        return [[width - P[block][0] - x[block], obj - P[block][1] - y[block]] for block in range(blocks)]
    elif on_x:
        return [[width - P[block][0] - x[block], P[block][1]] for block in range(blocks)]
    elif on_y:
        return [[P[block][0], obj - P[block][1] - y[block]] for block in range(blocks)]

#function I want to emulate
def lexicographic_order(a1, a2):
    for a, b in zip(a1, a2):
        if a < b:
            return True
        if a > b:
            return False
    return True

# my attempt: raise error "Z3 AST expected"
def lexSymb(a1, a2):
    for a, b in zip(a1, a2):
        tmp = If(a < b, True, False)
    if tmp.eq(True):
        return True
    tmp = If(a > b, False, True)
    if tmp.eq(False):
        return False
return True

lex_x = lexSymb1(P, symmetric(True, False))
lex_y = lexSymb1(P, symmetric(False, True))
lex_xy = lexSymb1(P, symmetric(True, True))

board_problem = values + no_overlap + lex_x + lex_y + lex_xy

o = Optimize()
o.add(board_problem)
o.minimize(obj)

if o.check() == sat:
    print("Solved")

elif o.check() == unsat:
    print("The problem is unsatisfiable")
else:
    print("Unexpected behaviour")

Let me know if it's possible to modify "lexicographic_order()" in such way that works with Symbolic expressions, or if there's another way around I've not seen.

You say you want to have a symbolic version of:

def lexicographic_order(a1, a2):
    for a, b in zip(a1, a2):
        if a < b:
            return True
        if a > b:
            return False
    return True

I don't think this is what you wanted. Why? Note that in Python, return immediately exits the function. So, this will merely compare the very first elements of these lists (assuming they are non-empty), and return immediately. I don't think this is what you intended.

A lexicographic ordering usually means you have to compare the whole list. This is how you'd code that symbolically:

# Does a1 come before a2 lexicographically?
# We assume a1 != a2. If same, the algorithm will produce True.
def precedes(a1, a2):
    if not a1:
        return True
    if not a2:
        return False
    return Or(a1[0] < a2[0], And(a1[0] == a2[0], precedes(a1[1:], a2[1:])))

It's instructive to look at how this function works. First on a concrete value:

> print(precedes([1,2,3],[4,5,6]))
Or(True,
   And(False,
       Or(True, And(False, Or(True, And(False, True))))))

Note we build the AST that "computes" if the first list is lexicographically before the second. The above expression reduces to True as you can easily verify yourself, but we don't compute True , we build the expression whose value will be True . The importance of this construction becomes obvious when we run it on symbolic values:

> print(precedes([Int('a'), Int('b')], [Int('c'), Int('d')]))
Or(a < c, And(a == c, Or(b < d, And(b == d, True))))

Now you see how the value is "symbolically" computed and does what you want it to do: It will evaluate to True exactly when the first list lexicographically-precedes the second. And when you assert this as a fact to the solver, it'll force that constraint on the model.

Hope that gets you going. You need to think in terms of "building" symbolic expressions, and thus Python's internal if-then-else is not something you can use, since it does not work correctly with symbolic values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM