"...it is a constant source of annoyance when you come up with a clever special algorithm which then gets beat by translation to SAT." –Chris Jefferson
SAT solvers are programs that can solve the satisfiability problem from Boolean logic. Given a logical expression, they determine if the expression can be made true.
For example,
can be made true by setting to false, to true, and to false.
The -queens problem is place queens on an chessboard so that no two queens attack each other.
# Initialize the solver
solver = SAT()
# Size of board
n = 8
Let denote that square contains a queen.
# Q[i,j] will denote there is a queen on square (i,j)
Q = [[0 for j in range(n)] for i in range(n)]
Give each square a unique index:
# Give each variable a unique label in {1,...,n^2}
c = 1
for i in range(n):
for j in range(n):
Q[i][j] = c
c += 1
# varsInCol returns the set of variables in the same column as y
def varsInCol(y):
return {Q[i][y] for i in range(n)}
# varsInRow returns the set of variables in the same row as x
def varsInRow(x):
return {Q[x][i] for i in range(n)}
# varsInDiag returns the set of variables in the same diagonal as (x,y)
def varsInDiag(x, y):
diagVars = {Q[x][y]}
for i in range(n):
if x+i in range(n) and y+i in range(n):
diagVars.add(Q[x+i][y+i])
if x-i in range(n) and y-i in range(n):
diagVars.add(Q[x-i][y-i])
return diagVars
# varsinAntiDiag returns the set of variables in the same anti-diagonal as (x,y)
def varsInAntiDiag(x, y):
antiDiagVars = {Q[x][y]}
for i in range(n):
if x+i in range(n) and y-i in range(n):
antiDiagVars.add(Q[x+i][y-i])
if x-i in range(n) and y+i in range(n):
antiDiagVars.add(Q[x-i][y+i])
return antiDiagVars
# attackedVars returns the set of variables attacked by a queen at (x,y)
def attackedVars(x, y):
s = varsInRow(x)
s.update(varsInCol(y))
s.update(varsInDiag(x, y))
s.update(varsInAntiDiag(x, y))
s.remove(Q[x][y])
return s
# There must be a queen in each row and column
for i in range(n):
solver.add_clause(varsInRow(i))
solver.add_clause(varsInCol(i))
If there is a queen on square (0,0), then there cannot be a queen on any square attackable by (0,0) such as (1,1):
This is logically equivalent to
# No two queens can attack each other
for i in range(n):
for j in range(n):
for attacked in attackedVars(i, j):
solver.add_clause([-Q[i][j], -attacked])
# Find a satisfying assignment of the generated instance
sat_assignment = solver()
for i in range(n):
s = ""
for j in range(n):
if sat_assignment[j*n+i+1] == True:
s += "Q"
else:
s += "."
print(s)