Skip to content

Commit 1c9a371

Browse files
committed
Convert csp.py to 3rd edition and fix inference bug.
1 parent e4bc956 commit 1c9a371

File tree

1 file changed

+79
-57
lines changed

1 file changed

+79
-57
lines changed

csp.py

Lines changed: 79 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
"""CSP (Constraint Satisfaction Problems) problems and solvers. (Chapter 5)."""
1+
"""CSP (Constraint Satisfaction Problems) problems and solvers. (Chapter 6)."""
22

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
73
from utils import *
84
import search
9-
import types
105

116
class CSP(search.Problem):
127
"""This class describes finite-domain Constraint Satisfaction Problems.
@@ -129,28 +124,57 @@ def choices(self, var):
129124
"Return all values for var that aren't currently ruled out."
130125
return (self.curr_domains or self.domains)[var]
131126

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-
137127
def infer_assignment(self):
138128
"Return the partial assignment implied by the current inferences."
139129
self.support_pruning()
140130
return dict((v, self.curr_domains[v][0])
141131
for v in self.vars if 1 == len(self.curr_domains[v]))
142132

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+
143138
## This is for min_conflicts search
144139

145140
def conflicted_vars(self, current):
146141
"Return a list of variables in current assignment that are in conflict"
147142
return [var for var in self.vars
148143
if self.nconflicts(var, current[var], current) > 0]
149144

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+
150174
#______________________________________________________________________________
151175
# CSP Backtracking Search
152176

153-
## Variable ordering
177+
# Variable ordering
154178

155179
def first_unassigned_variable(assignment, csp):
156180
"The default variable order."
@@ -169,7 +193,7 @@ def num_legal_values(csp, var, assignment):
169193
return count_if(lambda val: csp.nconflicts(var, val, assignment) == 0,
170194
csp.domains[var])
171195

172-
## Value ordering
196+
# Value ordering
173197

174198
def unordered_domain_values(var, assignment, csp):
175199
"The default value order."
@@ -180,34 +204,33 @@ def lcv(var, assignment, csp):
180204
return sorted(csp.choices(var),
181205
key=lambda val: csp.nconflicts(var, val, assignment))
182206

183-
## Inference
207+
# Inference
184208

185-
def no_inference(assignment, csp, var, value):
186-
return []
209+
def no_inference(csp, var, value, assignment, removals):
210+
return True
187211

188-
def forward_checking(assignment, csp, var, value):
212+
def forward_checking(csp, var, value, assignment, removals):
189213
"Prune neighbor values inconsistent with var=value."
190-
removals = csp.suppose(var, value)
191214
for B in csp.neighbors[var]:
192215
if B not in assignment:
193216
for b in csp.curr_domains[B][:]:
194217
if not csp.constraints(var, value, B, b):
195218
csp.prune(B, b, removals)
196-
return removals
219+
if not csp.curr_domains[B]:
220+
return False
221+
return True
197222

198-
def mac(assignment, csp, var, value):
223+
def mac(csp, var, value, assignment, removals):
199224
"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)
203226

204-
## The search, proper
227+
# The search, proper
205228

206229
def backtracking_search(csp,
207230
select_unassigned_variable = first_unassigned_variable,
208231
order_domain_values = unordered_domain_values,
209232
inference = no_inference):
210-
"""Fig. 6.5 (3rd edition)
233+
"""[Fig. 6.5]
211234
>>> backtracking_search(australia) is not None
212235
True
213236
>>> backtracking_search(australia, select_unassigned_variable=mrv) is not None
@@ -231,43 +254,19 @@ def backtrack(assignment):
231254
for value in order_domain_values(var, assignment, csp):
232255
if 0 == csp.nconflicts(var, value, assignment):
233256
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
238262
csp.restore(removals)
239-
csp.unassign(var, assignment)
263+
csp.unassign(var, assignment)
240264
return None
241265

242266
result = backtrack({})
243267
assert result is None or csp.goal_test(result)
244268
return result
245269

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-
271270
#______________________________________________________________________________
272271
# Min-conflicts hillclimbing search for CSPs
273272

@@ -294,6 +293,29 @@ def min_conflicts_value(csp, var, current):
294293
return argmin_random_tie(csp.domains[var],
295294
lambda val: csp.nconflicts(var, val, current))
296295

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+
297319
#______________________________________________________________________________
298320
# Map-Coloring Problems
299321

@@ -316,8 +338,7 @@ def MapColoringCSP(colors, neighbors):
316338
"""Make a CSP for the problem of coloring a map with different colors
317339
for any two adjacent regions. Arguments are a list of colors, and a
318340
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."""
321342
if isinstance(neighbors, str):
322343
neighbors = parse_neighbors(neighbors)
323344
return CSP(neighbors.keys(), UniversalDict(colors), neighbors,
@@ -475,6 +496,7 @@ class Sudoku(CSP):
475496
8 . . | 2 . 3 | . . 9
476497
. . 5 | . 1 . | 3 . .
477498
>>> AC3(e); e.display(e.infer_assignment())
499+
True
478500
4 8 3 | 9 2 1 | 6 5 7
479501
9 6 7 | 3 4 5 | 8 2 1
480502
2 5 1 | 8 7 6 | 4 9 3

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy