-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBTSolver.py
386 lines (299 loc) · 13.4 KB
/
BTSolver.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
import SudokuBoard
import Variable
import Domain
import Trail
import Constraint
import ConstraintNetwork
import time
import random
class BTSolver:
# ==================================================================
# Constructors
# ==================================================================
def __init__ ( self, gb, trail, val_sh, var_sh, cc ):
self.network = ConstraintNetwork.ConstraintNetwork(gb)
self.hassolution = False
self.gameboard = gb
self.trail = trail
self.varHeuristics = var_sh
self.valHeuristics = val_sh
self.cChecks = cc
# ==================================================================
# Consistency Checks
# ==================================================================
# Basic consistency check, no propagation done
def assignmentsCheck ( self ):
for c in self.network.getConstraints():
if not c.isConsistent():
return False
return True
"""
Part 1 TODO: Implement the Forward Checking Heuristic
This function will do both Constraint Propagation (checking to see if value removed is the only one left) and check
the consistency of the network
(1) If a variable is assigned then eliminate that value from
the square's neighbors.
Note: remember to trail.push variables before you assign them
Return: a tuple of a dictionary and a bool. The dictionary contains all MODIFIED variables, mapped to their MODIFIED domain.
The bool is true if assignment is consistent, false otherwise.
"""
def forwardChecking ( self ):
assignedVars = []
modDict = {}
consistent = True
constraints = [c for c in self.network.constraints]
for c in constraints:
for v in c.vars:
if v.isAssigned():
assignedVars.append(v)
for v in assignedVars:
for neighbor in self.network.getNeighborsOfVariable(v):
if neighbor.getAssignment == v.getAssignment:
consistent = False
break
if neighbor.isChangeable and v.getAssignment() in neighbor.getValues() and not neighbor.isAssigned():
self.trail.push(neighbor)
neighbor.removeValueFromDomain(v.getAssignment())
if neighbor.size() == 0:
consistent = False
break
else:
modDict[neighbor] = neighbor.getDomain()
for constraint in self.network.getModifiedConstraints():
if not constraint.isConsistent():
consistent = False
break
output = (modDict, consistent)
return output
# for c in self.network.constraints:
# for v in c.vars:
# if v.isAssigned():
# assignedVars.append(v)
# while len(assignedVars) != 0:
# av = assignedVars.pop(0)
# for neighbor in self.network.getNeighborsOfVariable(av):
# if neighbor.isChangeable and not neighbor.isAssigned() and neighbor.getDomain().contains(av.getAssignment()):
# self.trail.push(neighbor)
# modDict[neighbor] = neighbor.getDomain()
# neighbor.removeValueFromDomain(av.getAssignment())
# return (modDict,self.network.isConsistent())
# =================================================================
# Arc Consistency
# =================================================================
def arcConsistency( self ):
assignedVars = []
for c in self.network.constraints:
for v in c.vars:
if v.isAssigned():
assignedVars.append(v)
while len(assignedVars) != 0:
av = assignedVars.pop(0)
for neighbor in self.network.getNeighborsOfVariable(av):
if neighbor.isChangeable and not neighbor.isAssigned() and neighbor.getDomain().contains(av.getAssignment()):
neighbor.removeValueFromDomain(av.getAssignment())
if neighbor.domain.size() == 1:
neighbor.assignValue(neighbor.domain.values[0])
assignedVars.append(neighbor)
"""
Part 2 TODO: Implement both of Norvig's Heuristics
This function will do both Constraint Propagation and check
the consistency of the network
(1) If a variable is assigned then eliminate that value from
the square's neighbors.
(2) If a constraint has only one possible place for a value
then put the value there.
Note: remember to trail.push variables before you assign them
Return: a pair of a dictionary and a bool. The dictionary contains all variables
that were ASSIGNED during the whole NorvigCheck propagation, and mapped to the values that they were assigned.
The bool is true if assignment is consistent, false otherwise.
"""
def norvigCheck ( self ):
consistent = True
assigned_dict = {}
for var in self.network.variables:
if var.isAssigned():
for neighbor in self.network.getNeighborsOfVariable(var):
if var.getAssignment() == neighbor.getAssignment():
consistent = False
return (assigned_dict, consistent)
if not neighbor.isAssigned() and (var.getAssignment() in neighbor.getValues()):
self.trail.push(neighbor)
neighbor.removeValueFromDomain(var.getAssignment())
if neighbor.size() == 0:
consistent = False
return (assigned_dict, consistent)
if neighbor.size() == 1:
# self.trail.push(neighbor) <-- lower trail pushes when ##, backtracks are reaching targeted output for MAD/MRV
neighbor.assignValue(neighbor.getDomain().values[0])
assigned_dict[neighbor] = neighbor.getAssignment()
for c in self.network.getModifiedConstraints():
if not c.isConsistent():
consistent = False
return (assigned_dict, consistent)
return (assigned_dict, consistent)
"""
Optional TODO: Implement your own advanced Constraint Propagation
Completing the three tourn heuristic will automatically enter
your program into a tournament.
"""
def getTournCC ( self ):
return False
# ==================================================================
# Variable Selectors
# ==================================================================
# Basic variable selector, returns first unassigned variable
def getfirstUnassignedVariable ( self ):
for v in self.network.variables:
if not v.isAssigned():
return v
# Everything is assigned
return None
"""
Part 1 TODO: Implement the Minimum Remaining Value Heuristic
Return: The unassigned variable with the smallest domain
"""
def getMRV ( self ):
empty = None
val = 0
min = float('inf')
variables = [v for v in self.network.variables]
for v in variables:
if v.isAssigned() == 0:
val = v.size()
if val < min:
empty = v
min = val
return empty
"""
Part 2 TODO: Implement the Minimum Remaining Value Heuristic
with Degree Heuristic as a Tie Breaker
Return: The unassigned variable with the smallest domain and affecting the most unassigned neighbors.
If there are multiple variables that have the same smallest domain with the same number of unassigned neighbors, add them to the list of Variables.
If there is only one variable, return the list of size 1 containing that variable.
"""
def MRVwithTieBreaker ( self ):
var_list = []
degree = float("inf")
unassigned_list = [x for x in self.network.variables if not x.isAssigned()]
if len(unassigned_list) != 0:
s_domain = min([x.size() for x in unassigned_list])
else:
return [None]
s_unassigned = [x for x in unassigned_list if x.size() == s_domain]
for x in s_unassigned:
unassigned = 0
for neighbor in self.network.getNeighborsOfVariable(x):
if not neighbor.isAssigned():
unassigned +=1
if degree > unassigned:
degree = unassigned
var_list = [x]
elif degree == unassigned:
var_list.append(x)
return var_list
"""
Optional TODO: Implement your own advanced Variable Heuristic
Completing the three tourn heuristic will automatically enter
your program into a tournament.
"""
def getTournVar ( self ):
return None
# ==================================================================
# Value Selectors
# ==================================================================
# Default Value Ordering
def getValuesInOrder ( self, v ):
values = v.domain.values
return sorted( values )
"""
Part 1 TODO: Implement the Least Constraining Value Heuristic
The Least constraining value is the one that will knock the least
values out of it's neighbors domain.
Return: A list of v's domain sorted by the LCV heuristic
The LCV is first and the MCV is last
"""
def getValuesLCVOrder ( self, v ):
# lcv func go here
values = dict()
for k in v.getValues():
values[k] = 0
neighbors = [neighbor for neighbor in self.network.getNeighborsOfVariable(v)]
for selectedNeighbor in neighbors:
if not (selectedNeighbor.isAssigned()):
for val in selectedNeighbor.getValues():
if val in values:
values[val] += 1
results = []
sortRes = sorted(values.items(), key = lambda element: element[1])
for key, val in sortRes:
results.append(key)
return results
"""
Optional TODO: Implement your own advanced Value Heuristic
Completing the three tourn heuristic will automatically enter
your program into a tournament.
"""
def getTournVal ( self, v ):
return None
# ==================================================================
# Engine Functions
# ==================================================================
def solve ( self, time_left=600):
if time_left <= 60:
return -1
start_time = time.time()
if self.hassolution:
return 0
# Variable Selection
v = self.selectNextVariable()
# check if the assigment is complete
if ( v == None ):
# Success
self.hassolution = True
return 0
# Attempt to assign a value
for i in self.getNextValues( v ):
# Store place in trail and push variable's state on trail
self.trail.placeTrailMarker()
self.trail.push( v )
# Assign the value
v.assignValue( i )
# Propagate constraints, check consistency, recur
if self.checkConsistency():
elapsed_time = time.time() - start_time
new_start_time = time_left - elapsed_time
if self.solve(time_left=new_start_time) == -1:
return -1
# If this assignment succeeded, return
if self.hassolution:
return 0
# Otherwise backtrack
self.trail.undo()
return 0
def checkConsistency ( self ):
if self.cChecks == "forwardChecking":
return self.forwardChecking()[1]
if self.cChecks == "norvigCheck":
return self.norvigCheck()[1]
if self.cChecks == "tournCC":
return self.getTournCC()
else:
return self.assignmentsCheck()
def selectNextVariable ( self ):
if self.varHeuristics == "MinimumRemainingValue":
return self.getMRV()
if self.varHeuristics == "MRVwithTieBreaker":
return self.MRVwithTieBreaker()[0]
if self.varHeuristics == "tournVar":
return self.getTournVar()
else:
return self.getfirstUnassignedVariable()
def getNextValues ( self, v ):
if self.valHeuristics == "LeastConstrainingValue":
return self.getValuesLCVOrder( v )
if self.valHeuristics == "tournVal":
return self.getTournVal( v )
else:
return self.getValuesInOrder( v )
def getSolution ( self ):
return self.network.toSudokuBoard(self.gameboard.p, self.gameboard.q)