-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathencoder.py
163 lines (126 loc) · 7.04 KB
/
encoder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
from itertools import product, chain
from dimacs import DimacsMapper
class SudokuMapper(object):
"""Class makes a deterministic mapping for variables in sudokus."""
def __init__(self, square):
self.square = square
self.n = square * square
self.names_ = list(sorted([self.var(i, j, k) for i, j, k in product(range(square * square), repeat=3)]))
def var(self, row, column, value):
"""
Method to obtain a named variable from a given row, column and
value (all starting from zero).
"""
return "%d_%d__%d" % (row, column, value + 1)
def name(self, idx):
"""
Based on the index, return the name.
"""
return self.names_[idx]
def index(self, name):
"""
Method to convert a given variable name (e.g. 1_2__0) into
its index.
"""
return self.names_.index(name)
def inverse(self, name):
"""
Get a tuple with what the original index was in a tuple (row, column, value)
"""
# split it into row_col and value
rc, value = name.split('__')
row, column = rc.split('_')
return (row, column, value)
def size(self):
return len(self.names_)
class Encoder(object):
def __init__(self, mapper):
self.mapper = mapper
self.cnf_ = []
def add_clause(self, clause):
# add the clause to the cnf
self.cnf_.append(clause)
def add_clauses(self, clauses):
# add the clauses
for clause in clauses:
self.add_clause(clause)
def encode(self):
# first off we check if the cnf is non-empty, which would indicate we already did this or loaded something
if len(self.cnf_) != 0:
return self.cnf_
# we are going to actually encode the rules
self.encode_rules()
# return the cnf
return self.cnf_
def encode_rules(self):
raise NotImplementedError('encode_rules should be implemented by subclasses')
class NaiveEncoder(Encoder):
def encode_rules(self):
# http://sat.inesc.pt/~ines/publications/aimath06.pdf
# Minimal encoding: 81 nine-ary encodings, 8748 binary
# 1. at least one number in each entry (Cell defined)
self.add_clauses([[self.mapper.var(x, y, z) for z in range(self.mapper.n)] for x, y in product(range(self.mapper.n), repeat=2)])
# 2. every number appears at most once in each row/column (Row/Col unique)
self.add_clauses([["-" + self.mapper.var(x, y, z), "-" + self.mapper.var(i, y, z)] for y, z, x in product(range(self.mapper.n), range(self.mapper.n), range(self.mapper.n - 1)) for i in range(x + 1, self.mapper.n)])
self.add_clauses([["-" + self.mapper.var(x, y, z), "-" + self.mapper.var(x, i, z)] for x, z, y in product(range(self.mapper.n), range(self.mapper.n), range(self.mapper.n - 1)) for i in range(y + 1, self.mapper.n)])
# 3. each number appears at most once in each 3x3 subgrid (Block unique)
self.add_clauses([["-" + self.mapper.var(self.mapper.square*i + x, self.mapper.square*j + y, z), "-" + self.mapper.var(self.mapper.square*i + x, self.mapper.square*j + k, z)] for z, (i, j, x, y) in product(range(self.mapper.n), product(range(self.mapper.square), repeat=4)) for k in range(y + 1, self.mapper.square)])
self.add_clauses([["-" + self.mapper.var(self.mapper.square*i + x, self.mapper.square*j + y, z), "-" + self.mapper.var(self.mapper.square*i + k, self.mapper.square*j + l, z)] for z, (i, j, l, x, y) in product(range(self.mapper.n), product(range(self.mapper.square), repeat=5)) for k in range(x + 1, self.mapper.square)])
class ExtendedNaiveEncoder(NaiveEncoder):
def encode_rules(self):
# http://sat.inesc.pt/~ines/publications/aimath06.pdf
# first we make the naive encoding
NaiveEncoder.encode_rules(self)
# 1. at most one number in each entry (cell unique)
self.add_clauses([["-" + self.mapper.var(x, y, z), "-" + self.mapper.var(x, y, i)] for x, y, z in product(range(self.mapper.n), range(self.mapper.n), range(self.mapper.n - 1)) for i in range(z + 1, self.mapper.n)])
# 2. every number appears at least once in each row/column (row/col defined)
self.add_clauses([[self.mapper.var(x, y, z) for x in range(self.mapper.n)] for y, z in product(range(self.mapper.n), repeat=2)])
self.add_clauses([[self.mapper.var(x, y, z) for y in range(self.mapper.n)] for x, z in product(range(self.mapper.n), repeat=2)])
# 3. each number appears at least once in each 3x3 subgrid (block defined)
self.add_clauses([[self.mapper.var(self.mapper.square*i + x, self.mapper.square*j + y, z) for z in range(self.mapper.n)] for i, j, x, y in product(range(self.mapper.square), repeat=4)])
class EfficientEncoder(NaiveEncoder):
def encode_rules(self):
# [Weber, 2005] thought of this encoding, it's in-between
#: cell defined, cell unique, row unique, column unique, block unique
# first we make the naive encoding
NaiveEncoder.encode_rules(self)
# we add clauses to make the cell unique
self.add_clauses([["-" + self.mapper.var(x, y, z), "-" + self.mapper.var(x, y, i)] for x, y, z in product(range(self.mapper.n), range(self.mapper.n), range(self.mapper.n - 1)) for i in range(z + 1, self.mapper.n)])
class GivensEncoder(Encoder):
def __init__(self, mapper, sudoku):
# may be mismatch
if len(sudoku) != mapper.n * mapper.n:
raise ValueError('mismatch for the sudoku size with the mapper')
# initialize the base
Encoder.__init__(self, mapper)
# and we finally loop over the sudoku
for i, value in enumerate(sudoku):
# nothing to do if there is no given here
if value == 0:
continue
# get the column and row
column = i % mapper.n
row = int((i - column) / mapper.n)
# now we add the clause
self.add_clause([self.mapper.var(row, column, value - 1)])
if __name__=="__main__":
# we need the argument parser
import argparse
# make the encoders
encoders = {
'naive': NaiveEncoder,
'extended': ExtendedNaiveEncoder,
'efficient': EfficientEncoder
}
# parse arguments
parser = argparse.ArgumentParser(description='Encode the rules for an NxN sudoku.')
parser.add_argument('--square', type=int, default=3, help='Square to use, e.g. 2 is for a 4x4 sudoku and 3 is for a 3^2 = 9x9 sudoku, etc.')
parser.add_argument('--encoder', type=str, default='naive', choices=list(encoders.keys()))
args = parser.parse_args()
# get the square for the mapper and startup the dimacs
mapper = SudokuMapper(args.square)
dimacs = DimacsMapper(mapper)
# get the encoder
encoder = encoders[args.encoder](mapper)
# now we actually encode the rules
print(dimacs.encode(encoder.encode()))