尝试用 cvxpy 解决数独问题
Trying to solve Sudoku with cvxpy
我正在尝试使用 cvxpy
优化包解决数独问题。
我对优化和 cvxpy 都很陌生。
约束是:
- 所有值都在 1 到 9 之间
- 所有行的总和 = 45
- 所有列的总和 = 45
- 所有平方和 = 45
- 给定的数字(我正在尝试解决 17 条线索的数独问题)。
所以,这是我的代码:
from cvxpy import *
import numpy as np
x = Variable((9, 9), integer=True)
obj = Minimize(sum(x)) #whatever, if the constrains are fulfilled it will be fine
const = [x >= 1, #all values should be >= 1
x <= 9, #all values should be <= 9
sum(x, axis=0) == 45, # sum of all rows should be 45
sum(x, axis=1) == 45, # sum of all cols should be 45
sum(x[0:3, 0:3]) == 45, sum(x[0:3, 3:6]) == 45, #sum of all squares should be 45
sum(x[0:3, 6:9]) == 45,
sum(x[3:6, 0:3]) == 45, sum(x[3:6, 3:6]) == 45,
sum(x[3:6, 6:9]) == 45,
sum(x[6:9, 0:3]) == 45, sum(x[6:9, 3:6]) == 45,
sum(x[6:9, 6:9]) == 45,
x[0, 7] == 7, #the values themselves
x[0, 8] == 1,
x[1, 1] == 6,
x[1, 4] == 3,
x[2, 4] == 2,
x[3, 0] == 7,
x[3, 4] == 6,
x[3, 6] == 3,
x[4, 0] == 4,
x[4, 6] == 2,
x[5, 0] == 1,
x[5, 3] == 4,
x[6, 3] == 7,
x[6, 5] == 5,
x[6, 7] == 8,
x[7, 1] == 2,
x[8, 3] == 1]
prob = Problem(objective=obj, constraints=const)
prob.solve()
我从cvxpy得到的是
prob.status
Out[2]: 'infeasible_inaccurate'
这肯定是一个有效的数独,我检查了数百万次。
为什么我没有得到答案?
如有任何帮助,我们将不胜感激!
文档 (http://cvxr.com/cvx/doc/solver.html) 表明根本原因可能是一个接近可行的解决方案,但由于浮点误差超出公差而看起来不可行,这与所有等式约束相符。
不过,退一步说,这组约束不足以指定有效的数独解决方案。更好的公式是让 0-1 个变量由(行、列、大方块、数字)和约束索引,对于每个 row/column/square、row/digit、column/digit、square/digit组合,匹配变量之和等于1.
这是您默认使用的 ECOS_BB 问题。它不是一个可靠的整数规划求解器,我建议不要使用它。
其他建议:不要使用import *
。最好使用 import cvxpy as cp
以避免与其他同名函数混淆。另外,顺便说一句,这里不需要numpy。
下面的脚本returns一个GUROBI的可行方案(没有GUROBI许可证也可以使用GLPK):
import cvxpy as cp
x = cp.Variable((9, 9), integer=True)
# whatever, if the constrains are fulfilled it will be fine
objective = cp.Minimize(cp.sum(x))
constraints = [x >= 1, # all values should be >= 1
x <= 9, # all values should be <= 9
cp.sum(x, axis=0) == 45, # sum of all rows should be 45
cp.sum(x, axis=1) == 45, # sum of all cols should be 45
# sum of all squares should be 45
cp.sum(x[0:3, 0:3]) == 45, cp.sum(x[0:3, 3:6]) == 45,
cp.sum(x[0:3, 6:9]) == 45,
cp.sum(x[3:6, 0:3]) == 45, cp.sum(x[3:6, 3:6]) == 45,
cp.sum(x[3:6, 6:9]) == 45,
cp.sum(x[6:9, 0:3]) == 45, cp.sum(x[6:9, 3:6]) == 45,
cp.sum(x[6:9, 6:9]) == 45,
x[0, 7] == 7, # the values themselves
x[0, 8] == 1,
x[1, 1] == 6,
x[1, 4] == 3,
x[2, 4] == 2,
x[3, 0] == 7,
x[3, 4] == 6,
x[3, 6] == 3,
x[4, 0] == 4,
x[4, 6] == 2,
x[5, 0] == 1,
x[5, 3] == 4,
x[6, 3] == 7,
x[6, 5] == 5,
x[6, 7] == 8,
x[7, 1] == 2,
x[8, 3] == 1]
prob = cp.Problem(objective, constraints)
prob.solve(solver=cp.GUROBI)
print(x.value)
这就是输出
In [2]: run sudoku.py
[[1. 6. 1. 4. 7. 9. 9. 7. 1.]
[6. 6. 1. 1. 3. 9. 9. 9. 1.]
[8. 7. 9. 1. 2. 9. 1. 7. 1.]
[7. 7. 1. 9. 6. 1. 3. 2. 9.]
[4. 9. 5. 9. 5. 1. 2. 1. 9.]
[1. 2. 9. 4. 9. 1. 9. 1. 9.]
[8. 1. 1. 7. 8. 5. 2. 8. 5.]
[9. 2. 9. 9. 4. 1. 1. 1. 9.]
[1. 5. 9. 1. 1. 9. 9. 9. 1.]]
Sum == 45 不是一个好的约束,因为它不能防止重复值。您不需要 numpy 或 cvxpy 来解决这个问题,因为一个简单的回溯算法就可以解决它。
count=0
def findnext():
global x
for row in range(0,9):
for col in range(0, 9):
if x[row][col] == 0:
return (row, col)
return (-1,-1)
def colok(row, col):
global x
for c in range(0,9):
if c != col and x[row][c] == x[row][col]:
# print "x[%d][%d] == x[%d][%d]" % (row,c,row,col)
return False
return True
def rowok(row, col):
global x
for r in range(0,9):
if r != row and x[r][col] == x[row][col]:
return False
return True
def sqok(row, col):
global x
sqy = int(row/3)*3
sqx = int(col/3)*3
for r in range(0,3):
for c in range(0, 3):
if (sqx+c != col or sqy+r != row) and x[sqy+r][sqx+c] == x[row][col]:
print "x[%d][%d] == x[%d][%d] = %d" % (row,c,row,col,x[row][col])
return False
return True
def backtrack():
global x
global count
(row, col) = findnext()
if row < 0:
return True
for v in range(1,10):
x[row][col] = v
if rowok(row, col) and colok(row,col) and sqok(row,col):
count += 1
if backtrack():
return True
x[row][col] = 0
return False
结果:
ending after calling backtrack 98248 times
2 8 3 6 5 4 9 7 1
5 6 1 9 3 7 4 2 8
9 7 4 8 2 1 5 6 3
7 5 8 2 6 9 3 1 4
4 3 6 5 1 8 2 9 7
1 9 2 4 7 3 8 5 6
3 1 9 7 4 5 6 8 2
8 2 7 3 9 6 1 4 5
6 4 5 1 8 2 7 3 9
我正在尝试使用 cvxpy
优化包解决数独问题。
我对优化和 cvxpy 都很陌生。
约束是:
- 所有值都在 1 到 9 之间
- 所有行的总和 = 45
- 所有列的总和 = 45
- 所有平方和 = 45
- 给定的数字(我正在尝试解决 17 条线索的数独问题)。
所以,这是我的代码:
from cvxpy import *
import numpy as np
x = Variable((9, 9), integer=True)
obj = Minimize(sum(x)) #whatever, if the constrains are fulfilled it will be fine
const = [x >= 1, #all values should be >= 1
x <= 9, #all values should be <= 9
sum(x, axis=0) == 45, # sum of all rows should be 45
sum(x, axis=1) == 45, # sum of all cols should be 45
sum(x[0:3, 0:3]) == 45, sum(x[0:3, 3:6]) == 45, #sum of all squares should be 45
sum(x[0:3, 6:9]) == 45,
sum(x[3:6, 0:3]) == 45, sum(x[3:6, 3:6]) == 45,
sum(x[3:6, 6:9]) == 45,
sum(x[6:9, 0:3]) == 45, sum(x[6:9, 3:6]) == 45,
sum(x[6:9, 6:9]) == 45,
x[0, 7] == 7, #the values themselves
x[0, 8] == 1,
x[1, 1] == 6,
x[1, 4] == 3,
x[2, 4] == 2,
x[3, 0] == 7,
x[3, 4] == 6,
x[3, 6] == 3,
x[4, 0] == 4,
x[4, 6] == 2,
x[5, 0] == 1,
x[5, 3] == 4,
x[6, 3] == 7,
x[6, 5] == 5,
x[6, 7] == 8,
x[7, 1] == 2,
x[8, 3] == 1]
prob = Problem(objective=obj, constraints=const)
prob.solve()
我从cvxpy得到的是
prob.status
Out[2]: 'infeasible_inaccurate'
这肯定是一个有效的数独,我检查了数百万次。
为什么我没有得到答案?
如有任何帮助,我们将不胜感激!
文档 (http://cvxr.com/cvx/doc/solver.html) 表明根本原因可能是一个接近可行的解决方案,但由于浮点误差超出公差而看起来不可行,这与所有等式约束相符。
不过,退一步说,这组约束不足以指定有效的数独解决方案。更好的公式是让 0-1 个变量由(行、列、大方块、数字)和约束索引,对于每个 row/column/square、row/digit、column/digit、square/digit组合,匹配变量之和等于1.
这是您默认使用的 ECOS_BB 问题。它不是一个可靠的整数规划求解器,我建议不要使用它。
其他建议:不要使用import *
。最好使用 import cvxpy as cp
以避免与其他同名函数混淆。另外,顺便说一句,这里不需要numpy。
下面的脚本returns一个GUROBI的可行方案(没有GUROBI许可证也可以使用GLPK):
import cvxpy as cp
x = cp.Variable((9, 9), integer=True)
# whatever, if the constrains are fulfilled it will be fine
objective = cp.Minimize(cp.sum(x))
constraints = [x >= 1, # all values should be >= 1
x <= 9, # all values should be <= 9
cp.sum(x, axis=0) == 45, # sum of all rows should be 45
cp.sum(x, axis=1) == 45, # sum of all cols should be 45
# sum of all squares should be 45
cp.sum(x[0:3, 0:3]) == 45, cp.sum(x[0:3, 3:6]) == 45,
cp.sum(x[0:3, 6:9]) == 45,
cp.sum(x[3:6, 0:3]) == 45, cp.sum(x[3:6, 3:6]) == 45,
cp.sum(x[3:6, 6:9]) == 45,
cp.sum(x[6:9, 0:3]) == 45, cp.sum(x[6:9, 3:6]) == 45,
cp.sum(x[6:9, 6:9]) == 45,
x[0, 7] == 7, # the values themselves
x[0, 8] == 1,
x[1, 1] == 6,
x[1, 4] == 3,
x[2, 4] == 2,
x[3, 0] == 7,
x[3, 4] == 6,
x[3, 6] == 3,
x[4, 0] == 4,
x[4, 6] == 2,
x[5, 0] == 1,
x[5, 3] == 4,
x[6, 3] == 7,
x[6, 5] == 5,
x[6, 7] == 8,
x[7, 1] == 2,
x[8, 3] == 1]
prob = cp.Problem(objective, constraints)
prob.solve(solver=cp.GUROBI)
print(x.value)
这就是输出
In [2]: run sudoku.py
[[1. 6. 1. 4. 7. 9. 9. 7. 1.]
[6. 6. 1. 1. 3. 9. 9. 9. 1.]
[8. 7. 9. 1. 2. 9. 1. 7. 1.]
[7. 7. 1. 9. 6. 1. 3. 2. 9.]
[4. 9. 5. 9. 5. 1. 2. 1. 9.]
[1. 2. 9. 4. 9. 1. 9. 1. 9.]
[8. 1. 1. 7. 8. 5. 2. 8. 5.]
[9. 2. 9. 9. 4. 1. 1. 1. 9.]
[1. 5. 9. 1. 1. 9. 9. 9. 1.]]
Sum == 45 不是一个好的约束,因为它不能防止重复值。您不需要 numpy 或 cvxpy 来解决这个问题,因为一个简单的回溯算法就可以解决它。
count=0
def findnext():
global x
for row in range(0,9):
for col in range(0, 9):
if x[row][col] == 0:
return (row, col)
return (-1,-1)
def colok(row, col):
global x
for c in range(0,9):
if c != col and x[row][c] == x[row][col]:
# print "x[%d][%d] == x[%d][%d]" % (row,c,row,col)
return False
return True
def rowok(row, col):
global x
for r in range(0,9):
if r != row and x[r][col] == x[row][col]:
return False
return True
def sqok(row, col):
global x
sqy = int(row/3)*3
sqx = int(col/3)*3
for r in range(0,3):
for c in range(0, 3):
if (sqx+c != col or sqy+r != row) and x[sqy+r][sqx+c] == x[row][col]:
print "x[%d][%d] == x[%d][%d] = %d" % (row,c,row,col,x[row][col])
return False
return True
def backtrack():
global x
global count
(row, col) = findnext()
if row < 0:
return True
for v in range(1,10):
x[row][col] = v
if rowok(row, col) and colok(row,col) and sqok(row,col):
count += 1
if backtrack():
return True
x[row][col] = 0
return False
结果:
ending after calling backtrack 98248 times
2 8 3 6 5 4 9 7 1
5 6 1 9 3 7 4 2 8
9 7 4 8 2 1 5 6 3
7 5 8 2 6 9 3 1 4
4 3 6 5 1 8 2 9 7
1 9 2 4 7 3 8 5 6
3 1 9 7 4 5 6 8 2
8 2 7 3 9 6 1 4 5
6 4 5 1 8 2 7 3 9