N-Queens Symmetry打破Google OR Tools

时间:2016-12-13 22:28:12

标签: python n-queens symmetry or-tools

One of the samples for the Google or-tools is a solver for the n-queens problem.在底部,它表示可以通过向约束求解器添加对称破坏约束来改进实现。

环顾互联网,I found the symmetry breaking constraints for the n-queens problem,但我不能为我的生活找出如何将这些转换为约束来实现它们的python代码。

编辑:这是一个糟糕的问题,让我们更新......

我尝试了什么?

以下是上面第一个链接的设置:

from ortools.constraint_solver import pywrapcp

N = 8
solver = pywrapcp.Solver("n-queens")
# Creates the variables.
# The array index is the column, and the value is the row.
queens = [solver.IntVar(0, N - 1, "x%i" % i) for i in range(N)]
# Creates the constraints.
# All rows must be different.
solver.Add(solver.AllDifferent(queens))
# All columns must be different because the indices of queens are all different.
# No two queens can be on the same diagonal.
solver.Add(solver.AllDifferent([queens[i] + i for i in range(N)]))
solver.Add(solver.AllDifferent([queens[i] - i for i in range(N)]))

# TODO: add symmetry breaking constraints

db = solver.Phase(queens, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)
solver.NewSearch(db)
num_solutions = 0
while solver.NextSolution():
  num_solutions += 1
solver.EndSearch()
print()
print("Solutions found:", num_solutions)
print("Time:", solver.WallTime(), "ms")

我知道我可以成功实现简单约束。如果我想确保解决方案总是在第一行的第一列中有一个女王,我可以这样实现:

solver.Add(queens[0] == 0)

queens[0]变量表示第一列中的皇后位置,只有当第一列在第一行中有一个皇后时,才会满足此约束。这当然不是我想要做的,因为解决方案可能不包括任何角落单元。

对于n皇后问题的对称性破坏约束如下。它们是直接从第二段中的链接中提取的。

n-queens symmetry breaking constraints

我理解这些约束是如何工作的。这个想法是你可以将这个函数应用到n-queens板上的每个单元格,以便将状态转换为等效状态。其中一种状态将是该州的规范表示。这用作通过消除重复评估来修剪未来处理的方法。

如果我只是按照事实的方式实现这个,我会完全按照上面描述的那样,使用每个可能的对称破坏函数转换状态,计算某种状态散列(例如,所选行中的一个字符串)每列)并选择每个建议解决方案中最低的那个。跳过我们之前见过的未来处理。

我的问题是,我不知道如何将这些转换转换为Google或工具约束编程解算器的约束。

让我们来看看最简单的一个d1(r[i] = j) => r[j] = i,关于主对角线的反射。我所知道的是,转换需要应用于所有单元格,然后与当前状态进行比较,以防止扩展。我不太了解python,无法理解这里的变换是什么样的表达式,我只是无法弄清楚如何创建约束,将变换与此特定求解器的当前状态进行比较。

state = [queens[i].Value() for i in range(N)]
symX    = [state[N - (i + 1)] for i in range(N)]
symY    = [N - (state[i] + 1) for i in range(N)]
symD1   = [state.index(i) for i in range(N)]
symD2   = [N - (state.index(N-(i+1)) + 1) for i in range(N)]
symR90  = [N - (state.index(i) + 1) for i in range(N)]
symR180 = [N - (state[N-(i+1)] + 1) for i in range(N)]
symR270 = [state.index(N-(i+1)) for i in range(N)]

2 个答案:

答案 0 :(得分:1)

我尝试使用自定义DecisionBuilder来修剪搜索树,使用对称作为新约束,但我无法使其工作。

相反,我必须使用SearchMonitor捕获每个解决方案的事件,并检查该解决方案是否与前一个解决方案对称。

在这里,我添加了SearchMonitor的代码,覆盖了覆盖" AcceptSolution"函数和gen_symetries函数用于计算和检查所有可能的对称性。

    class SearchMonitor(pywrapcp.SearchMonitor):
    def __init__(self, solver, q):
        pywrapcp.SearchMonitor.__init__(self, solver)
        self.q = q
        self.all_solutions = []
        self.unique_solutions = []
        self.n = len(self.q)

    def AcceptSolution(self):
        qval = [self.q[i].Value() for i in range(self.n)]
        self.all_solutions.append(qval)

        symmetries = [vv in self.unique_solutions for vv in gen_symmetries(self.n, qval)]

        if sum(symmetries) == 0:
            self.unique_solutions.append(qval)

        return False

def gen_symmetries(n, solution):
    symmetries = []

    #x(r[i]=j) → r[n−i+1]=j
    x = list(range(n))
    for index in range(n):
        x[n - 1 - index] = solution[index]

    symmetries.append(x)

    #y(r[i]=j) → r[i]=n−j+1
    y = list(range(n))
    for index in range(n):
        y[index] = (n - 1 - solution[index])

    symmetries.append(y)

    #d1(r[i]=j) → r[j]=i
    d1 = list(range(n))
    for index in range(n):
        d1[solution[index]] = index

    symmetries.append(d1)

    # d2(r[i]=j) → r[n−j+1]=n−i+1
    d2 = list(range(n))
    for index in range(n):
        d2[n - 1 - solution[index]] = (n - 1 - index)

    symmetries.append(d2)

    # r90(r[i]=j) → r[j] = n−i+1
    r90 = list(range(n))
    for index in range(n):
        r90[solution[index]] = (n - 1 - index)

    symmetries.append(r90)

    # r180(r[i]=j) → r[n−i+1]=n−j+1
    r180 = list(range(n))
    for index in range(n):
        r180[n - 1 - index] = (n - 1 - solution[index])

    symmetries.append(r180)

    # r270(r[i]=j) → r[n−j+1]=i
    r270 = list(range(n))
    for index in range(n):
        r270[n - 1 - solution[index]] = index

    symmetries.append(r270)

    return symmetries

稍后您只需将监视器添加到您的求解器中即可。

monitor = SearchMonitor(solver, queens)
solver.Solve(db, monitor)
solver.NewSearch(db)

最后只打印所有独特的解决方案

print("Unique Solutions:", len(monitor.unique_solutions), monitor.unique_solutions)

您可以在要点中看到完整的工作示例。

https://gist.github.com/carlgira/7a4e6cf0f7b7412762171015917bccb4

答案 1 :(得分:0)

您必须使用所寻求解决方案之间的已知对称关系来识别将消除等效解决方案的约束。

  1. 对于queens[0] >= N/2的每个解决方案,还有另一个垂直镜像的解决方案queens[0] <= N/2。因此,我们可以寻找具有较小值queens[0]的解决方案并添加以下约束:

    solver.Add(queens[0] < (N+1)//2) # Handle both even and odd values of N
    
  2. 然后,满足条件queens[0] < queens[N-1]的解决方案具有满足queens[0] > queens[N-1]的等效水平镜像解决方案。您可以告诉求解器只查找那些解决方案,其中最左侧列中的后置位于最右侧列中的后置位置下方:

    solver.Add(queens[0] < queens[N-1])
    
  3. 我不能轻易地制定反映旋转对称性的约束,但我相信这是可能的。