4
4
then create problem instances and solve them with calls to the various search
5
5
functions."""
6
6
7
- # (Written for the second edition of AIMA; expect some discrepanciecs
8
- # from the third edition until this gets reviewed.)
9
-
10
7
from utils import *
11
8
import agents
12
9
import math , random , sys , time , bisect , string
13
10
14
11
#______________________________________________________________________________
15
12
16
13
class Problem (object ):
17
- """The abstract class for a formal problem. You should subclass this and
18
- implement the method successor, and possibly __init__, goal_test, and
19
- path_cost. Then you will create instances of your subclass and solve them
20
- with the various search functions."""
14
+ """The abstract class for a formal problem. You should subclass
15
+ this and implement the methods actions and result, and possibly
16
+ __init__, goal_test, and path_cost. Then you will create instances
17
+ of your subclass and solve them with the various search functions."""
21
18
22
19
def __init__ (self , initial , goal = None ):
23
20
"""The constructor specifies the initial state, and possibly a goal
24
21
state, if there is a unique goal. Your subclass's constructor can add
25
22
other arguments."""
26
23
self .initial = initial ; self .goal = goal
27
24
28
- def successor (self , state ):
29
- """Given a state, return a sequence of (action, state) pairs reachable
30
- from this state. If there are many successors, consider an iterator
31
- that yields the successors one at a time, rather than building them
32
- all at once. Iterators will work fine within the framework."""
25
+ def actions (self , state ):
26
+ """Return the actions that can be executed in the given
27
+ state. The result would typically be a list, but if there are
28
+ many actions, consider yielding them one at a time in an
29
+ iterator, rather than building them all at once."""
30
+ abstract
31
+
32
+ def result (self , state , action ):
33
+ """Return the state that results from executing the given
34
+ action in the given state. The action must be one of
35
+ self.actions(state)."""
33
36
abstract
34
37
35
38
def goal_test (self , state ):
@@ -72,20 +75,29 @@ def __init__(self, state, parent=None, action=None, path_cost=0):
72
75
def __repr__ (self ):
73
76
return "<Node %s>" % (self .state ,)
74
77
78
+ def expand (self , problem ):
79
+ "List the nodes reachable in one step from this node."
80
+ return [self .child_node (problem , action )
81
+ for action in problem .actions (self .state )]
82
+
83
+ def child_node (self , problem , action ):
84
+ "Fig. 3.10"
85
+ next = problem .result (self .state , action )
86
+ return Node (next , self , action ,
87
+ problem .path_cost (self .path_cost , self .state , action , next ))
88
+
89
+ def solution (self ):
90
+ "Return the sequence of actions to go from the root to this node."
91
+ return [node .action for node in self .path ()]
92
+
75
93
def path (self ):
76
- """Create a list of nodes from the root to this node."" "
94
+ "Return a list of nodes forming the path from the root to this node."
77
95
node , path_back = self , []
78
96
while node :
79
97
path_back .append (node )
80
98
node = node .parent
81
99
return list (reversed (path_back ))
82
100
83
- def expand (self , problem ):
84
- "Return a list of nodes reachable from this node. [Fig. 3.8]"
85
- return [Node (next , self , act ,
86
- problem .path_cost (self .path_cost , self .state , act , next ))
87
- for (act , next ) in problem .successor (self .state )]
88
-
89
101
#______________________________________________________________________________
90
102
91
103
class SimpleProblemSolvingAgentProgram :
@@ -117,51 +129,60 @@ def search(self, problem):
117
129
#______________________________________________________________________________
118
130
## Uninformed Search algorithms
119
131
120
- def tree_search (problem , fringe ):
132
+ def tree_search (problem , frontier ):
121
133
"""Search through the successors of a problem to find a goal.
122
- The argument fringe should be an empty queue.
123
- Don't worry about repeated paths to a state. [Fig. 3.8 ]"""
124
- fringe .append (Node (problem .initial ))
125
- while fringe :
126
- node = fringe .pop ()
134
+ The argument frontier should be an empty queue.
135
+ Don't worry about repeated paths to a state. [Fig. 3.7 ]"""
136
+ frontier .append (Node (problem .initial ))
137
+ while frontier :
138
+ node = frontier .pop ()
127
139
if problem .goal_test (node .state ):
128
140
return node
129
- fringe .extend (node .expand (problem ))
141
+ frontier .extend (node .expand (problem ))
142
+ return None
143
+
144
+ def graph_search (problem , frontier ):
145
+ """Search through the successors of a problem to find a goal.
146
+ The argument frontier should be an empty queue.
147
+ If two paths reach a state, only use the best one. [Fig. 3.7]"""
148
+ frontier .append (Node (problem .initial ))
149
+ explored = set ()
150
+ while frontier :
151
+ node = frontier .pop ()
152
+ if problem .goal_test (node .state ):
153
+ return node
154
+ explored .add (node .state )
155
+ frontier .extend (successor for successor in node .expand (problem )
156
+ if successor .state not in explored
157
+ and successor .state not in frontier )
130
158
return None
131
159
132
160
def breadth_first_tree_search (problem ):
133
- "Search the shallowest nodes in the search tree first. [p 74] "
161
+ "Search the shallowest nodes in the search tree first."
134
162
return tree_search (problem , FIFOQueue ())
135
163
136
164
def depth_first_tree_search (problem ):
137
- "Search the deepest nodes in the search tree first. [p 74] "
165
+ "Search the deepest nodes in the search tree first."
138
166
return tree_search (problem , Stack ())
139
167
140
- def graph_search (problem , fringe ):
141
- """Search through the successors of a problem to find a goal.
142
- The argument fringe should be an empty queue.
143
- If two paths reach a state, only use the best one. [Fig. 3.18]"""
144
- closed = {}
145
- fringe .append (Node (problem .initial ))
146
- while fringe :
147
- node = fringe .pop ()
148
- if problem .goal_test (node .state ):
149
- return node
150
- if node .state not in closed :
151
- closed [node .state ] = True
152
- fringe .extend (node .expand (problem ))
153
- return None
154
-
155
168
def breadth_first_graph_search (problem ):
156
- "Search the shallowest nodes in the search tree first. [p 74] "
169
+ "Search the shallowest nodes in the search tree first."
157
170
return graph_search (problem , FIFOQueue ())
158
171
159
172
def depth_first_graph_search (problem ):
160
- "Search the deepest nodes in the search tree first. [p 74] "
173
+ "Search the deepest nodes in the search tree first."
161
174
return graph_search (problem , Stack ())
162
175
176
+ def breadth_first_search (problem ):
177
+ "Fig. 3.11"
178
+ NotImplemented
179
+
180
+ def uniform_cost_search (problem ):
181
+ "Fig. 3.14"
182
+ NotImplemented
183
+
163
184
def depth_limited_search (problem , limit = 50 ):
164
- "[Fig. 3.12 ]"
185
+ "[Fig. 3.17 ]"
165
186
def recursive_dls (node , problem , limit ):
166
187
if problem .goal_test (node .state ):
167
188
return node
@@ -181,7 +202,7 @@ def recursive_dls(node, problem, limit):
181
202
return recursive_dls (Node (problem .initial ), problem , limit )
182
203
183
204
def iterative_deepening_search (problem ):
184
- "[Fig. 3.13 ]"
205
+ "[Fig. 3.18 ]"
185
206
for depth in xrange (sys .maxint ):
186
207
result = depth_limited_search (problem , depth )
187
208
if result is not 'cutoff' :
@@ -217,7 +238,7 @@ def f(n):
217
238
## Other search algorithms
218
239
219
240
def recursive_best_first_search (problem , h = None ):
220
- "[Fig. 4.5 ]"
241
+ "[Fig. 3.26 ]"
221
242
h = h or problem .h
222
243
223
244
def RBFS (problem , node , flimit ):
@@ -248,7 +269,7 @@ def RBFS(problem, node, flimit):
248
269
249
270
def hill_climbing (problem ):
250
271
"""From the initial node, keep choosing the neighbor with highest value,
251
- stopping when no neighbor is better. [Fig. 4.11 ]"""
272
+ stopping when no neighbor is better. [Fig. 4.2 ]"""
252
273
current = Node (problem .initial )
253
274
while True :
254
275
neighbors = current .expand (problem )
@@ -280,28 +301,32 @@ def simulated_annealing(problem, schedule=exp_schedule()):
280
301
if delta_e > 0 or probability (math .exp (delta_e / T )):
281
302
current = next
282
303
283
- def online_dfs_agent (a ):
284
- "[Fig. 4.12]"
285
- pass #### more
304
+ def and_or_graph_search (problem ):
305
+ "[Fig. 4.11]"
306
+ NotImplemented
307
+
308
+ def online_dfs_agent (s1 ):
309
+ "[Fig. 4.21]"
310
+ NotImplemented
286
311
287
- def lrta_star_agent (a ):
288
- "[Fig. 4.12 ]"
289
- pass #### more
312
+ def lrta_star_agent (s1 ):
313
+ "[Fig. 4.24 ]"
314
+ NotImplemented
290
315
291
316
#______________________________________________________________________________
292
317
# Genetic Algorithm
293
318
294
319
def genetic_search (problem , fitness_fn , ngen = 1000 , pmut = 0.1 , n = 20 ):
295
320
"""Call genetic_algorithm on the appropriate parts of a problem.
296
- This requires that the problem has a successor function that
297
- generates states that can mate and mutate, and that it has a value
298
- method that scores states."""
299
- states = [s for ( a , s ) in problem .successor ( problem . initial_state )]
321
+ This requires the problem to have states that can mate and mutate,
322
+ plus a value method that scores states."""
323
+ s = problem . initial_state
324
+ states = [problem . result ( s , a ) for a in problem .actions ( s )]
300
325
random .shuffle (states )
301
326
return genetic_algorithm (states [:n ], problem .value , ngen , pmut )
302
327
303
328
def genetic_algorithm (population , fitness_fn , ngen = 1000 , pmut = 0.1 ):
304
- """ [Fig. 4.7]"" "
329
+ "[Fig. 4.8] "
305
330
for i in range (ngen ):
306
331
new_population = []
307
332
for i in len (population ):
@@ -314,7 +339,7 @@ def genetic_algorithm(population, fitness_fn, ngen=1000, pmut=0.1):
314
339
return argmax (population , fitness_fn )
315
340
316
341
class GAState :
317
- "Abstract class for individuals in a genetic algorithm ."
342
+ "Abstract class for individuals in a genetic search ."
318
343
def __init__ (self , genes ):
319
344
self .genes = genes
320
345
@@ -464,9 +489,13 @@ def __init__(self, initial, goal, graph):
464
489
Problem .__init__ (self , initial , goal )
465
490
self .graph = graph
466
491
467
- def successor (self , A ):
468
- "Return a list of (action, result) pairs."
469
- return [(B , B ) for B in self .graph .get (A ).keys ()]
492
+ def actions (self , A ):
493
+ "The actions at a graph node are just its neighbors."
494
+ return self .graph .get (A ).keys ()
495
+
496
+ def result (self , state , action ):
497
+ "The result of going to a neighbor is just that neighbor."
498
+ return action
470
499
471
500
def path_cost (self , cost_so_far , A , action , B ):
472
501
return cost_so_far + (self .graph .get (A ,B ) or infinity )
@@ -486,24 +515,30 @@ class NQueensProblem(Problem):
486
515
each other. A state is represented as an N-element array, where
487
516
a value of r in the c-th entry means there is a queen at column c,
488
517
row r, and a value of None means that the c-th column has not been
489
- filled in yet. We fill in columns left to right."""
518
+ filled in yet. We fill in columns left to right.
519
+ >>> depth_first_tree_search(NQueensProblem(8))
520
+ <Node [7, 3, 0, 2, 5, 1, 6, 4]>
521
+ """
490
522
def __init__ (self , N ):
491
523
self .N = N
492
524
self .initial = [None ] * N
493
525
494
- def successor (self , state ):
526
+ def actions (self , state ):
495
527
"In the leftmost empty column, try all non-conflicting rows."
496
528
if state [- 1 ] is not None :
497
529
return [] # All columns filled; no successors
498
530
else :
499
- def place (col , row ):
500
- new = state [:]
501
- new [col ] = row
502
- return new
503
531
col = state .index (None )
504
- return [( row , place ( col , row )) for row in range (self .N )
532
+ return [row for row in range (self .N )
505
533
if not self .conflicted (state , row , col )]
506
534
535
+ def result (self , state , row ):
536
+ "Place the next queen at the given row."
537
+ col = state .index (None )
538
+ new = state [:]
539
+ new [col ] = row
540
+ return new
541
+
507
542
def conflicted (self , state , row , col ):
508
543
"Would placing a queen at (row, col) conflict with anything?"
509
544
for c in range (col ):
@@ -723,14 +758,15 @@ def __init__(self, problem):
723
758
self .succs = self .goal_tests = self .states = 0
724
759
self .found = None
725
760
726
- def successor (self , state ):
727
- "Return a list of (action, state) pairs reachable from this state."
728
- result = self .problem .successor (state )
729
- self .succs += 1 ; self .states += len (result )
730
- return result
761
+ def actions (self , state ):
762
+ self .succs += 1
763
+ return self .problem .actions (state )
764
+
765
+ def result (self , state , action ):
766
+ self .states += 1
767
+ return self .problem .result (state , action )
731
768
732
769
def goal_test (self , state ):
733
- "Return true if the state is a goal."
734
770
self .goal_tests += 1
735
771
result = self .problem .goal_test (state )
736
772
if result :
@@ -742,7 +778,7 @@ def __getattr__(self, attr):
742
778
743
779
def __repr__ (self ):
744
780
return '<%4d/%4d/%4d/%s>' % (self .succs , self .goal_tests ,
745
- self .states , str (self .found )[0 :4 ])
781
+ self .states , str (self .found )[:4 ])
746
782
747
783
def compare_searchers (problems , header , searchers = [breadth_first_tree_search ,
748
784
breadth_first_graph_search , depth_first_graph_search ,
@@ -760,11 +796,11 @@ def compare_graph_searchers():
760
796
>>> compare_graph_searchers()
761
797
Searcher Romania(A, B) Romania(O, N) Australia
762
798
breadth_first_tree_search < 21/ 22/ 59/B> <1158/1159/3288/N> < 7/ 8/ 22/WA>
763
- breadth_first_graph_search < 10 / 19 / 26 /B> < 19 / 45 / 45 /N> < 5 / 8 / 16 /WA>
764
- depth_first_graph_search < 9/ 15 / 23/B> < 16/ 27 / 39/N> < 4/ 7 / 13/WA>
799
+ breadth_first_graph_search < 11 / 12 / 28 /B> < 33 / 34 / 76 /N> < 6 / 7 / 19 /WA>
800
+ depth_first_graph_search < 9/ 10 / 23/B> < 16/ 17 / 39/N> < 4/ 5 / 13/WA>
765
801
iterative_deepening_search < 11/ 33/ 31/B> < 656/1815/1812/N> < 3/ 11/ 11/WA>
766
802
depth_limited_search < 54/ 65/ 185/B> < 387/1012/1125/N> < 50/ 54/ 200/WA>
767
- astar_search < 3/ 4/ 9/B> < 8/ 10 / 22/N> < 2/ 3/ 6/WA>
803
+ astar_search < 3/ 4/ 9/B> < 8/ 9 / 22/N> < 2/ 3/ 6/WA>
768
804
recursive_best_first_search < 200/ 201/ 601/B> < 71/ 72/ 213/N> < 11/ 12/ 43/WA> """
769
805
compare_searchers (problems = [GraphProblem ('A' , 'B' , romania ),
770
806
GraphProblem ('O' , 'N' , romania ),
0 commit comments