-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcsp_problems.py
More file actions
381 lines (323 loc) · 14.1 KB
/
csp_problems.py
File metadata and controls
381 lines (323 loc) · 14.1 KB
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
from csp import Constraint, Variable, CSP
from constraints import *
from backtracking import bt_search
import util
##################################################################
### NQUEENS
##################################################################
def nQueens(n, tableCnstr):
'''Return an n-queens CSP, optionally use tableContraints'''
i = 0
dom = []
for i in range(n):
dom.append(i+1)
vars = []
for i in dom:
vars.append(Variable('Q{}'.format(i), dom))
cons = []
for qi in range(len(dom)):
for qj in range(qi+1, len(dom)):
if tableCnstr:
con = QueensTableConstraint("C(Q{},Q{})".format(qi+1,qj+1),
vars[qi], vars[qj], qi+1, qj+1)
else: con = QueensConstraint("C(Q{},Q{})".format(qi+1,qj+1),
vars[qi], vars[qj], qi+1, qj+1)
cons.append(con)
csp = CSP("{}-Queens".format(n), vars, cons)
return csp
def solve_nQueens(n, algo, allsolns, tableCnstr=False, variableHeuristic='fixed', trace=False):
'''Create and solve an nQueens CSP problem. The first
parameer is 'n' the number of queens in the problem,
The second specifies the search algorithm to use (one
of 'BT', 'FC', or 'GAC'), the third specifies if
all solutions are to be found or just one, variableHeuristic
specfies how the next variable is to be selected
'random' at random, 'fixed' in a fixed order, 'mrv'
minimum remaining values. Finally 'trace' if specified to be
'True' will generate some output as the search progresses.
'''
csp = nQueens(n, tableCnstr)
solutions, num_nodes = bt_search(algo, csp, variableHeuristic, allsolns, trace)
print "Explored {} nodes".format(num_nodes)
if len(solutions) == 0:
print "No solutions to {} found".format(csp.name())
else:
print "Solutions to {}:".format(csp.name())
i = 0
for s in solutions:
i += 1
print "Solution #{}: ".format(i),
for (var,val) in s:
print "{} = {}, ".format(var.name(),val),
print ""
##################################################################
### SUDOKU
##################################################################
def sudokuCSP(initial_sudoku_board, model='neq'):
'''The input board is specified as a list of 9 lists. Each of the
9 lists represents a row of the board. If a 0 is in the list it
represents an empty cell. Otherwise if a number between 1--9 is
in the list then this represents a pre-set board
position. E.g., the board
-------------------
| | |2| |9| | |6| |
| |4| | | |1| | |8|
| |7| |4|2| | | |3|
|5| | | | | |3| | |
| | |1| |6| |5| | |
| | |3| | | | | |6|
|1| | | |5|7| |4| |
|6| | |9| | | |2| |
| |2| | |8| |1| | |
-------------------
would be represented by the list of lists
[[0,0,2,0,9,0,0,6,0],
[0,4,0,0,0,1,0,0,8],
[0,7,0,4,2,0,0,0,3],
[5,0,0,0,0,0,3,0,0],
[0,0,1,0,6,0,5,0,0],
[0,0,3,0,0,0,0,0,6],
[1,0,0,0,5,7,0,4,0],
[6,0,0,9,0,0,0,2,0],
[0,2,0,0,8,0,1,0,0]]
Construct and return CSP for solving this sudoku board using
binary not equals if model='neq' or using allDiff constraints
if model='alldiff'
The CSP contains a variable for each cell of the board with
with domain equal to {1-9} if the board has a 0 at that position,
and domain equal {i} if the board has a fixed number i at that
cell.
The CSP has a neq constraint between every relevant pair of
varibles, or an alldiff constraint between every set of
variables in a row, column, or sub-square
'''
#your implementation for Question 4 changes this function
#implement handling of model == 'alldiff'
if not model in ['neq', 'alldiff']:
print "Error wrong sudoku model specified {}. Must be one of {}".format(
model, ['neq', 'alldiff'])
#first define the variables
i = 0
var_array = []
for row_list in initial_sudoku_board:
var_array.append([])
j = 0
for col in row_list:
cell = initial_sudoku_board[i][j]
if cell == 0:
dom = [1, 2, 3, 4, 5, 6, 7, 8, 9]
else:
dom = [cell]
var = Variable("V{},{}".format(i+1, j+1), dom)
var_array[i].append(var)
j += 1
i += 1
#Set up the constraints
#row constraints
constraint_list = []
for row in var_array:
if model == 'neq':
constraint_list.extend(post_all_pairs(row))
elif model == 'alldiff':
constraint_list.append(AllDiffConstraint('Row', row))
for colj in range(len(var_array[0])):
scope = map(lambda row: row[colj], var_array)
if model == 'neq':
constraint_list.extend(post_all_pairs(scope))
elif model == 'alldiff':
constraint_list.append(AllDiffConstraint('Column', scope))
for i in [0, 3, 6]:
for j in [0, 3, 6]:
#initial upper left hand index of subsquare
scope = []
for k in [0, 1, 2]:
for l in [0,1,2]:
scope.append(var_array[i+k][j+l])
if model == 'neq':
constraint_list.extend(post_all_pairs(scope))
elif model == 'alldiff':
constraint_list.append(AllDiffConstraint('Block', scope))
vars = [var for row in var_array for var in row]
return CSP("Sudoku", vars, constraint_list)
def post_all_pairs(var_list):
'''create a not equal constraint between all pairs of variables in var_list
return list of constructed constraint objects'''
constraints = []
for i in range(len(var_list)):
for j in range(i+1,len(var_list)):
c = NeqConstraint("({},{})".format(var_list[i].name(), var_list[j].name()),[var_list[i], var_list[j]])
constraints.append(c)
return constraints
def solve_sudoku(initialBoard, model, algo, allsolns,
variableHeuristic='fixed', trace=False):
if not model in ['neq', 'alldiff']:
print "Error wrong sudoku model specified {}. Must be one of {}".format(
model, ['neq', 'alldiff'])
csp = sudokuCSP(initialBoard, model)
solutions, num_nodes = bt_search(algo, csp, variableHeuristic, allsolns, trace)
print "Explored {} nodes".format(num_nodes)
if len(solutions) == 0:
print "No solutions to {} found".format(csp.name())
else:
i = 0
for s in solutions:
i += 1
print "Solution #{}: ".format(i)
sudoku_print_soln(s)
def sudoku_print_soln(s):
'''s is a list of (var,value) pairs. Organize them into
the right order and then print it in a board layout'''
s.sort(key=lambda varval_pair: varval_pair[0].name())
print "-"*37
for i in range(0,9):
print "|",
for j in range(0,9):
indx = i*9 + j
print s[indx][1], "|",
print ""
print "-"*37
##################################################################
### Plane Sequencing
##################################################################
class PlaneProblem:
'''Class to hold an instance of the plane scheduling problem.
defined by the following data items
a) A list of planes
b) A list of needed flights
c) Legal flights for each plane. Specified as a list of lists
For each list L. L[0] = a particular plane P. and L[1], L[2} ...
are all of the flights that P is equipped to fly.
This must be a subset of the list of flights
d) Possible starting flights for each plane. Specified as a
list of lists For each list L. L[0] = a particular plane P,
and L[1], L[2], ... are all of the flights that in the same
place the plane that P is initially located. This must be a
subset of the list of flights. Note however, that that P
might not be able to fly all of these flights (so the list
(c) needs to be checked as well)
e) A list of pairs of flights (f1,f2) such that f2 can legally
follow f1 in a plane's schedule. (That is, f2 starts at the
same location that f1 end).
f) A list of flights that end in a location where maintenance
can be performed
g) In integer specifying the minimum frequency of plane
maintenance. That is, if the minimum frequency is 4, then at
least one out of every sequence of 4 flights a plane makes
must be a flight ending at a location where maintenance can
be performed.
See plane_scheduling.py for examples of the use of this class.
Note also the access functions can_fly and can_start
'''
def __init__(self, planes, flights, can_fly, flights_at_start,
can_follow, maintenance_flights, min_maintenance_frequency):
self.planes = planes
self.flights = flights
self._can_fly = dict()
self._flights_at_start = dict()
self.can_follow = can_follow
self.maintenance_flights = maintenance_flights
self.min_maintenance_frequency = min_maintenance_frequency
#do some data checks
for l in can_fly:
for f in l[1:]:
if f not in flights:
print "PlaneProblem Error, can_fly contains a non-flight", f
for l in flights_at_start:
for f in l[1:]:
if f not in flights:
print "PplaneProblem Error, flights_at_start contains a non-flight", f
for (f1, f2) in can_follow:
if f1 not in flights or f2 not in flights:
print "PlaneProblem Error, can_fly contains pair with non-flight (", f1, ",", f2, ")"
for f in maintenance_flights:
if f not in flights:
print "PlaneProblem Error, maintenance_flights fly contains non-flight", f
if min_maintenance_frequency == 0:
print "PlaneProblem Error, min_maintenance_frequency must be greater than 0"
#now convert can_fly and flights_at_start to a dictionary that
#can be indexed by the plane.
for l in can_fly:
self._can_fly[l[0]] = l[1:]
for l in flights_at_start:
self._flights_at_start[l[0]] = l[1:]
#some useful access functions
def can_fly(self, plane):
'''Return list of flights plane can fly'''
return self._can_fly[plane]
def can_start(self, plane):
'''Return list of flights plane can start with'''
return list(
set(self._can_fly[plane]).intersection(
self._flights_at_start[plane]))
def solve_planes(planes_problem, algo, allsolns,
variableHeuristic='mrv', silent=False, trace=False):
#Your implementation for Question 6 goes here.
#
#Do not but do not change the functions signature
#(the autograder will twig out if you do).
#If the silent parameter is set to True
#you must ensure that you do not execute any print statements
#in this function.
#(else the output of the autograder will become confusing).
#So if you have any debugging print statements make sure you
#only execute them "if not silent". (The autograder will call
#this function with silent=True, plane_scheduling.py will call
#this function with silent=False)
#You can optionally ignore the trace parameter
#If you implemented tracing in your FC and GAC implementations
#you can set this argument to True for debugging.
#
#Once you have implemented this function you should be able to
#run plane_scheduling.py to solve the test problems (or the autograder).
#
#
'''This function takes a planes_problem (an instance of PlaneProblem
class) as input. It constructs a CSP, solves the CSP with bt_search
(using the options passed to it), and then from the set of CSP
solutions it constructs a list of lists specifying a schedule
for each plane and returns that list of lists
The required format is the list of lists is:
For each plane P the list of lists contains a list L.
L[0] == P (i.e., the first item of the list is the plane)
and L[1], ..., L[k] (i.e., L[1:]) is the sequence of flights
assigned to P.
The returned list of lists should contain a list for every
plane.
'''
#BUILD your CSP here and store it in the varable csp
list_of_planes = planes_problem.planes
list_of_flights = planes_problem.flights
# valid_flights = planes_problem.can_fly(list_of_planes[0])
# can_start = planes_problem.can_start(list_of_planes[0])
can_follow_list = planes_problem.can_follow
constraint = []
variables = []
for plane in list_of_planes:
valid_flights = planes_problem.can_fly(plane)
for f in valid_flights:
variable_to_add = Variable(plane, planes_problem.can_fly(plane))
variables.append(variable_to_add)
for flight in list_of_flights:
constraint_to_add = NValuesConstraint(flight, variables, [flight], 1, 1)
constraint.append(constraint_to_add)
for i in range(len(variables)):
if i < len(variables) - 1:
table_constraint = TableConstraint("table", [variables[i], variables[i + 1]], planes_problem.can_follow)
constraint.append(table_constraint)
csp = CSP("Plane Problem", variables, constraint)
#invoke search with the passed parameters
solutions, num_nodes = bt_search(algo, csp, variableHeuristic, allsolns, trace)
#Convert each solution into a list of lists specifying a schedule
#for each plane in the format described above.
#then return a list containing all converted solutions
#(i.e., a list of lists of lists)
answer = []
for i in range(len(solutions)):
f = []
f.append(solutions[i][0][0].name())
for var in solutions[i]:
f.append(var[1])
solution = []
solution.append(f)
answer.append(solution)
return answer