1
- """CSP (Constraint Satisfaction Problems) problems and solvers. (Chapter 5 )."""
1
+ """CSP (Constraint Satisfaction Problems) problems and solvers. (Chapter 6 )."""
2
2
3
- # (Written for the second edition of AIMA; expect some discrepanciecs
4
- # from the third edition until this gets reviewed.)
5
-
6
- from __future__ import generators
7
3
from utils import *
8
4
import search
9
- import types
10
5
11
6
class CSP (search .Problem ):
12
7
"""This class describes finite-domain Constraint Satisfaction Problems.
@@ -129,28 +124,57 @@ def choices(self, var):
129
124
"Return all values for var that aren't currently ruled out."
130
125
return (self .curr_domains or self .domains )[var ]
131
126
132
- def restore (self , removals ):
133
- "Undo all inferences from a supposition."
134
- for B , b in removals :
135
- self .curr_domains [B ].append (b )
136
-
137
127
def infer_assignment (self ):
138
128
"Return the partial assignment implied by the current inferences."
139
129
self .support_pruning ()
140
130
return dict ((v , self .curr_domains [v ][0 ])
141
131
for v in self .vars if 1 == len (self .curr_domains [v ]))
142
132
133
+ def restore (self , removals ):
134
+ "Undo a supposition and all inferences from it."
135
+ for B , b in removals :
136
+ self .curr_domains [B ].append (b )
137
+
143
138
## This is for min_conflicts search
144
139
145
140
def conflicted_vars (self , current ):
146
141
"Return a list of variables in current assignment that are in conflict"
147
142
return [var for var in self .vars
148
143
if self .nconflicts (var , current [var ], current ) > 0 ]
149
144
145
+ #______________________________________________________________________________
146
+ # Constraint Propagation with AC-3
147
+
148
+ def AC3 (csp , queue = None , removals = None ):
149
+ """[Fig. 6.3]"""
150
+ if queue is None :
151
+ queue = [(Xi , Xk ) for Xi in csp .vars for Xk in csp .neighbors [Xi ]]
152
+ csp .support_pruning ()
153
+ while queue :
154
+ (Xi , Xj ) = queue .pop ()
155
+ if revise (csp , Xi , Xj , removals ):
156
+ if not csp .curr_domains [Xi ]:
157
+ return False
158
+ for Xk in csp .neighbors [Xi ]:
159
+ if Xk != Xi :
160
+ queue .append ((Xk , Xi ))
161
+ return True
162
+
163
+ def revise (csp , Xi , Xj , removals ):
164
+ "Return true if we remove a value."
165
+ revised = False
166
+ for x in csp .curr_domains [Xi ][:]:
167
+ # If Xi=x conflicts with Xj=y for every possible y, eliminate Xi=x
168
+ if every (lambda y : not csp .constraints (Xi , x , Xj , y ),
169
+ csp .curr_domains [Xj ]):
170
+ csp .prune (Xi , x , removals )
171
+ revised = True
172
+ return revised
173
+
150
174
#______________________________________________________________________________
151
175
# CSP Backtracking Search
152
176
153
- ## Variable ordering
177
+ # Variable ordering
154
178
155
179
def first_unassigned_variable (assignment , csp ):
156
180
"The default variable order."
@@ -169,7 +193,7 @@ def num_legal_values(csp, var, assignment):
169
193
return count_if (lambda val : csp .nconflicts (var , val , assignment ) == 0 ,
170
194
csp .domains [var ])
171
195
172
- ## Value ordering
196
+ # Value ordering
173
197
174
198
def unordered_domain_values (var , assignment , csp ):
175
199
"The default value order."
@@ -180,34 +204,33 @@ def lcv(var, assignment, csp):
180
204
return sorted (csp .choices (var ),
181
205
key = lambda val : csp .nconflicts (var , val , assignment ))
182
206
183
- ## Inference
207
+ # Inference
184
208
185
- def no_inference (assignment , csp , var , value ):
186
- return []
209
+ def no_inference (csp , var , value , assignment , removals ):
210
+ return True
187
211
188
- def forward_checking (assignment , csp , var , value ):
212
+ def forward_checking (csp , var , value , assignment , removals ):
189
213
"Prune neighbor values inconsistent with var=value."
190
- removals = csp .suppose (var , value )
191
214
for B in csp .neighbors [var ]:
192
215
if B not in assignment :
193
216
for b in csp .curr_domains [B ][:]:
194
217
if not csp .constraints (var , value , B , b ):
195
218
csp .prune (B , b , removals )
196
- return removals
219
+ if not csp .curr_domains [B ]:
220
+ return False
221
+ return True
197
222
198
- def mac (assignment , csp , var , value ):
223
+ def mac (csp , var , value , assignment , removals ):
199
224
"Maintain arc consistency."
200
- removals = csp .suppose (var , value )
201
- AC3 (csp , [(X , var ) for X in csp .neighbors [var ]], removals )
202
- return removals
225
+ return AC3 (csp , [(X , var ) for X in csp .neighbors [var ]], removals )
203
226
204
- ## The search, proper
227
+ # The search, proper
205
228
206
229
def backtracking_search (csp ,
207
230
select_unassigned_variable = first_unassigned_variable ,
208
231
order_domain_values = unordered_domain_values ,
209
232
inference = no_inference ):
210
- """Fig. 6.5 (3rd edition)
233
+ """[ Fig. 6.5]
211
234
>>> backtracking_search(australia) is not None
212
235
True
213
236
>>> backtracking_search(australia, select_unassigned_variable=mrv) is not None
@@ -231,43 +254,19 @@ def backtrack(assignment):
231
254
for value in order_domain_values (var , assignment , csp ):
232
255
if 0 == csp .nconflicts (var , value , assignment ):
233
256
csp .assign (var , value , assignment )
234
- removals = inference (assignment , csp , var , value )
235
- result = backtrack (assignment )
236
- if result is not None :
237
- return result
257
+ removals = csp .suppose (var , value )
258
+ if inference (csp , var , value , assignment , removals ):
259
+ result = backtrack (assignment )
260
+ if result is not None :
261
+ return result
238
262
csp .restore (removals )
239
- csp .unassign (var , assignment )
263
+ csp .unassign (var , assignment )
240
264
return None
241
265
242
266
result = backtrack ({})
243
267
assert result is None or csp .goal_test (result )
244
268
return result
245
269
246
- #______________________________________________________________________________
247
- # Constraint Propagation with AC-3
248
-
249
- def AC3 (csp , queue = None , removals = None ):
250
- """[Fig. 5.7]"""
251
- if queue is None :
252
- queue = [(Xi , Xk ) for Xi in csp .vars for Xk in csp .neighbors [Xi ]]
253
- csp .support_pruning ()
254
- while queue :
255
- (Xi , Xj ) = queue .pop ()
256
- if remove_inconsistent_values (csp , Xi , Xj , removals ):
257
- for Xk in csp .neighbors [Xi ]:
258
- queue .append ((Xk , Xi ))
259
-
260
- def remove_inconsistent_values (csp , Xi , Xj , removals ):
261
- "Return true if we remove a value."
262
- removed = False
263
- for x in csp .curr_domains [Xi ][:]:
264
- # If Xi=x conflicts with Xj=y for every possible y, eliminate Xi=x
265
- if every (lambda y : not csp .constraints (Xi , x , Xj , y ),
266
- csp .curr_domains [Xj ]):
267
- csp .prune (Xi , x , removals )
268
- removed = True
269
- return removed
270
-
271
270
#______________________________________________________________________________
272
271
# Min-conflicts hillclimbing search for CSPs
273
272
@@ -294,6 +293,29 @@ def min_conflicts_value(csp, var, current):
294
293
return argmin_random_tie (csp .domains [var ],
295
294
lambda val : csp .nconflicts (var , val , current ))
296
295
296
+ #______________________________________________________________________________
297
+
298
+ def tree_csp_solver (csp ):
299
+ "[Fig. 6.11]"
300
+ n = len (csp .vars )
301
+ assignment = {}
302
+ root = csp .vars [0 ]
303
+ X , parent = topological_sort (csp .vars , root )
304
+ for Xj in reversed (X ):
305
+ if not make_arc_consistent (parent [Xj ], Xj , csp ):
306
+ return None
307
+ for Xi in X :
308
+ if not csp .curr_domains [Xi ]:
309
+ return None
310
+ assignment [Xi ] = csp .curr_domains [Xi ][0 ]
311
+ return assignment
312
+
313
+ def topological_sort (xs , x ):
314
+ unimplemented ()
315
+
316
+ def make_arc_consistent (Xj , Xk , csp ):
317
+ unimplemented ()
318
+
297
319
#______________________________________________________________________________
298
320
# Map-Coloring Problems
299
321
@@ -316,8 +338,7 @@ def MapColoringCSP(colors, neighbors):
316
338
"""Make a CSP for the problem of coloring a map with different colors
317
339
for any two adjacent regions. Arguments are a list of colors, and a
318
340
dict of {region: [neighbor,...]} entries. This dict may also be
319
- specified as a string of the form defined by parse_neighbors"""
320
-
341
+ specified as a string of the form defined by parse_neighbors."""
321
342
if isinstance (neighbors , str ):
322
343
neighbors = parse_neighbors (neighbors )
323
344
return CSP (neighbors .keys (), UniversalDict (colors ), neighbors ,
@@ -475,6 +496,7 @@ class Sudoku(CSP):
475
496
8 . . | 2 . 3 | . . 9
476
497
. . 5 | . 1 . | 3 . .
477
498
>>> AC3(e); e.display(e.infer_assignment())
499
+ True
478
500
4 8 3 | 9 2 1 | 6 5 7
479
501
9 6 7 | 3 4 5 | 8 2 1
480
502
2 5 1 | 8 7 6 | 4 9 3
0 commit comments