简体   繁体   English

使用 Z3 求解器寻路

[英]Path hunting with Z3 solver

I am modeling below problem in Z3.我在 Z3 中对以下问题进行建模。 The aim is to find the path for Agent to reach the coin avoiding obstacles.目的是找到 Agent 避开障碍物到达硬币的路径。

Initial_grid =[['T' 'T' 'T' 'T' 'T' 'T' 'T']
              ['T' ' ' ' ' ' ' ' ' ' ' 'T']
              ['T' ' ' 'A' 'O' ' ' 'O' 'T']
              ['T' 'O' ' ' ' ' ' ' ' ' 'T']
              ['T' ' ' ' ' 'O' 'O' 'C' 'T']
              ['T' ' ' ' ' ' ' ' ' ' ' 'T']
              ['T' 'T' 'T' 'T' 'T' 'T' 'T']]

x, y = Ints('x y')
x = agent_loc[0]
y = agent_loc[1]

xc, yc = Ints('xc yc')
xc = coin_loc[0]
yc = coin_loc[1]

s = Solver()
s.add(x,y = (Or(move_right(),move_left(),move_top(),move_bottom())))
solve(And (x = xc) (y = yc))
if s.check() == unsat:
      print('Problem not solvable')
else:
    m = s.model()

I added constraint for movement function which returns x,y coordinates if the movement is valid (no obstacles and within boundary) and returns false otherwise.我为运动 function 添加了约束,如果运动有效(没有障碍物并且在边界内),它返回 x,y 坐标,否则返回 false。 How can I model the movement constraint as the one in code gives error: add() got an unexpected keyword argument 'y' .我怎么能 model 移动约束,因为代码中的那个给出了错误: add() 得到了一个意外的关键字参数 'y'

One way to think about these sorts of search problems is a two pronged approach:考虑这类搜索问题的一种方法是采用两管齐下的方法:

  • Can I find a path with 1 move?我可以找到 1 步的路径吗? If not, try with 2 moves, 3 moves, etc. till you hit an upper bound and you decide to stop trying.如果没有,请尝试 2 步、3 步等,直到达到上限并决定停止尝试。

  • Instead of "searching," imagine a path is given to you;与其“搜索”,不如想象给你一条路径; how would you check that it's a good path?您将如何检查它是否是一条好路? The magic of SMT solving is that if you can write a program that verifies a given "alleged" solution is good, it can find you one that is indeed good. SMT 解决方案的神奇之处在于,如果您可以编写一个程序来验证给定的“所谓”解决方案是好的,它可以为您找到一个确实好的解决方案。

The following is a solution to your problem following these lines of thought.以下是按照这些思路解决您的问题的方法。

from z3 import *

Grid = [ ['T', 'T', 'T', 'T', 'T', 'T', 'T']
       , ['T', ' ', ' ', ' ', ' ', ' ', 'T']
       , ['T', ' ', 'A', 'O', ' ', 'O', 'T']
       , ['T', 'O', ' ', ' ', ' ', ' ', 'T']
       , ['T', ' ', ' ', 'O', 'O', 'C', 'T']
       , ['T', ' ', ' ', ' ', ' ', ' ', 'T']
       , ['T', 'T', 'T', 'T', 'T', 'T', 'T']
       ]

Cell, (Wall, Empty, Agent, Obstacle, Coin) = EnumSort('Cell', ('Wall', 'Empty', 'Agent', 'Obstacle', 'Coin'))

def mkCell(c):
    if c == 'T':
        return Wall
    elif c == ' ':
        return Empty
    elif c == 'A':
        return Agent
    elif c == 'O':
        return Obstacle
    else:
        return Coin

def grid(x, y):
    result = Wall
    for i in range (len(Grid)):
        for j in range (len(Grid[0])):
            result = If(And(x == IntVal(i), y == IntVal(j)), mkCell(Grid[i][j]), result)
    return result

def validStart(x, y):
    return grid(x, y) == Agent

def validEnd(x, y):
    return grid(x, y) == Coin

def canMoveTo(x, y):
    n = grid(x, y)
    return Or(n == Empty, n == Coin, n == Agent)

def moveLeft(x, y):
    return [x, If(canMoveTo(x, y-1), y-1, y)]

def moveRight(x, y):
    return [x, If(canMoveTo(x, y+1), y+1, y)]

def moveUp(x, y):
    return [If(canMoveTo(x-1, y), x-1, x), y]

def moveDown(x, y):
    return [If(canMoveTo(x+1, y), x+1, x), y]

Dir, (Left, Right, Up, Down) = EnumSort('Dir', ('Left', 'Right', 'Up', 'Down'))

def move(d, x, y):
    xL, yL = moveLeft (x, y)
    xR, yR = moveRight(x, y)
    xU, yU = moveUp   (x, y)
    xD, yD = moveDown (x, y)
    xN     = If(d == Left, xL, If (d == Right, xR, If (d == Up, xU, xD)))
    yN     = If(d == Left, yL, If (d == Right, yR, If (d == Up, yU, yD)))
    return [xN, yN]

def solves(seq, x, y):
    def walk(moves, curX, curY):
        if moves:
            nX, nY = move(moves[0], curX, curY)
            return walk(moves[1:], nX, nY)
        else:
            return [curX, curY]

    xL, yL = walk(seq, x, y)
    return And(validStart(x, y), validEnd(xL, yL))

pathLength = 0

while(pathLength != 20):
    print("Trying to find a path of length:", pathLength)

    s    = Solver()
    seq  = [Const('m' + str(i), Dir)  for i in range(pathLength)]
    x, y = Ints('x y')
    s.add(solves(seq, x, y))

    if s.check() == sat:
        print("Found solution with length:", pathLength)
        m = s.model()
        print("    Start x:", m[x])
        print("    Start y:", m[y])
        for move in seq:
             print("    Move", m[move])
        break
    else:
        pathLength += 1

When run, this prints:运行时,打印:

Trying to find a path of length: 0
Trying to find a path of length: 1
Trying to find a path of length: 2
Trying to find a path of length: 3
Trying to find a path of length: 4
Trying to find a path of length: 5
Found solution with length: 5
    Start x: 2
    Start y: 2
    Move Down
    Move Right
    Move Right
    Move Right
    Move Down

So, it found a solution with 5 moves;因此,它找到了 5 步的解决方案; you can chase it in your grid to see that it's indeed correct.你可以在你的网格中追逐它,看看它确实是正确的。 (The numbering starts at 0,0 at the top-left corner; increasing as you go to right and down.) Additionally, you're guaranteed that this is a shortest solution (not necessarily unique of course). (编号从左上角的 0,0 开始;随着 go 向右和向下增加。)此外,您可以保证这是一个最短的解决方案(当然不一定是唯一的)。 That is, there are no solutions with less than 5 moves.也就是说,没有少于 5 步的解决方案。

I should add that there are other ways to solve this problem without iterating, by using z3 sequences.我应该补充一点,还有其他无需迭代即可解决此问题的方法,即使用 z3 序列。 However that's even more advanced z3 programming, and likely to be less performant as well.然而,这是更高级的 z3 编程,而且性能也可能较低。 For all practical purposes, the iterative approach presented here is a good way to tackle such search problems in z3.出于所有实际目的,此处介绍的迭代方法是解决 z3 中此类搜索问题的好方法。

An alternative solution:替代解决方案:

from z3 import *
#          1    2    3    4    5    6    7
Grid = [ ['T', 'T', 'T', 'T', 'T', 'T', 'T']  #  1
       , ['T', ' ', ' ', ' ', ' ', ' ', 'T']  #  2
       , ['T', ' ', 'A', 'O', ' ', 'O', 'T']  #  3
       , ['T', 'O', ' ', ' ', ' ', ' ', 'T']  #  4
       , ['T', ' ', ' ', 'O', 'O', 'C', 'T']  #  5
       , ['T', ' ', ' ', ' ', ' ', ' ', 'T']  #  6
       , ['T', 'T', 'T', 'T', 'T', 'T', 'T']  #  7
       ]

rows = len(Grid)
Rows = range(rows)
cols = len(Grid[0])
Cols = range(cols)
Infinity = rows * cols + 1

deltaRow = [-1,  0, +1,  0]
deltaCol = [ 0, -1,  0, +1]
delta    = ['up', 'left', 'down', 'right']
deltaInv = ['down', 'right', 'up', 'left'];
Deltas = range(len(delta))

s = Solver()

#  2D array comprehension: 
#  create matrix of path distances
#  https://stackoverflow.com/a/25345853/1911064
distances = [[Int('d'+str(r)+'_'+str(c)) for c in Cols] for r in Rows]

# http://www.hakank.org/z3/z3_utils_hakank.py
# v is the minimum value of x
def minimum(sol, v, x):
  sol.add(Or([v == x[i] for i in range(len(x))])) # v is an element in x)
  for i in range(len(x)):
    sol.add(v <= x[i]) # and it's the smallest

#  constraints for distances
for row in Rows:
    for col in Cols:
        #  shorthands to reduce typing
        g = Grid[row][col]
        dist = distances[row][col]
        if (g == 'T') or (g == 'O'):
           #  obstacles and walls cannot be part of the path
           s.add(dist == Infinity)
        elif g == 'A':
           #  the path starts here
           s.add(dist == 0)
        else:
           #  array index violations cannot happen
           #  because the wall case is handled above
           minimum(s, dist, [distances[row + deltaRow[i]][col + deltaCol[i]] + 1 for i in Deltas])
           #  remember the coin coordinates
           if g == 'C':
               rowCoin, colCoin = row, col
               #  detect unreachable target as UNSAT
               s.add(dist < Infinity)
                    
if s.check() == sat:
    #  show the resulting path
    m = s.model()
    row, col = rowCoin, colCoin
    #  collect the path in reverse to
    #  avoid dead-ends which don't reach the coin
    path = []
    dir =  []
    while True:
        path.insert(0, [row, col])
        if Grid[row][col] == 'A':
            break
        neighborDistances = [m[distances[row+deltaRow[i]][col+deltaCol[i]]].as_long() 
                             for i in Deltas]
        best = neighborDistances.index(min(neighborDistances))
        #  advance to the direction of the lowest distance
        row += deltaRow[best]
        col += deltaCol[best]
        dir.insert(0, best)

    print('start ' + ' [row ' + str(path[0][0]+1) + '; col ' + str(path[0][1]+1) + ']')
    for i in range(1, len(path)):
        print(deltaInv[dir[i-1]].ljust(6) + ' [row ' + str(path[i][0]+1) + '; col ' + str(path[i][1]+1) + ']')
else:
     print("No path found!")   

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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