Subversion Repositories programming

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
380 ira 1
#!/usr/bin/env python
2
 
3
__author__    = "Ira W. Snyder (devel@irasnyder.com)"
4
__copyright__ = "Copyright (c) 2006 Ira W. Snyder (devel@irasnyder.com)"
5
__license__   = "GNU GPL v2 (or, at your option, any later version)"
6
 
7
# 1) Put the start node N0 on OPEN. Create G with just this node
8
# 2) Create the list CLOSED which is empty
9
#
10
# LOOP:
11
#
12
# 3) If OPEN is empty, exit with FAILURE
13
# 4) Select the first node from OPEN, put it on CLOSED. Call this N
14
# 5) If N is a goal node, exit successfully with the solution in G
15
# 6) Generate M from the children of N
16
# 7) Add anything not in M not in (CLOSED union OPEN) to OPEN
17
# 8) Reorder OPEN appropriately
18
# 9) goto LOOP
19
 
395 ira 20
# Fix for the lame version of python on the school's computers (v2.1)
21
try:
22
	(True, False)
23
except NameError:
24
	(True, False) = (1, 0)
25
 
387 ira 26
from PuzzlePiece import PuzzlePiece
380 ira 27
from Graph import Graph
28
 
395 ira 29
# See if we can import GraphViz binding
30
try:
31
	no_graphviz = False
32
	import yapgvb
33
except ImportError:
34
	no_graphviz = True
35
 
36
class SearchResult:
393 ira 37
	"""Class to store a search result"""
38
 
39
	def __init__ (self, completed, status, depth_reached, nodes_created, result_graph):
40
		self.completed = completed
41
		self.status = status
42
		self.depth_reached = depth_reached
43
		self.nodes_created = nodes_created
44
		self.result_graph = result_graph
45
		self.search_name = None
46
 
47
	def __repr__ (self):
48
		"""Turn myself into a string"""
49
		answer =  '%s -- Reached Depth: %s -- Nodes Created: %s' % \
50
			(self.status, self.depth_reached, self.nodes_created)
51
 
52
		if self.search_name:
53
			answer = self.search_name + '\n' + answer
54
 
55
		return answer
56
 
57
	def set_search_name (self, name):
58
		self.search_name = name
59
 
60
 
395 ira 61
class PuzzleSearch:
380 ira 62
	"""Implements a graph search"""
63
 
387 ira 64
	def __init__ (self, start_node, goal_nodes):
380 ira 65
		"""Constructor.
387 ira 66
		start_node: the node to start at (must have a get_children() function)
380 ira 67
		goal_nodes: a list of nodes to end at"""
68
 
69
		self.__start_node = start_node
70
		self.__goal_nodes = goal_nodes
71
 
72
	def __find_nearest_child (self, children, already_visited):
73
		"""Find the child that we came from. This necessitates that
74
		the list already_visited be sorted in the order of nodes visited"""
396 ira 75
 
76
		# This is the stupidest hack ever (gotta hate they school computers!)
77
		rev = already_visited[:]
78
		rev.reverse()
79
 
80
		for n in rev: #reversed(already_visited):
380 ira 81
			if n in children:
82
				return n
83
 
84
		# This should never happen
85
		raise ValueError
86
 
391 ira 87
	def search (self, add_function, MAX_NODES_CREATED=100):
380 ira 88
 
89
		# Create the result graph
90
		result = Graph ()
91
		firsttime = True
92
		counter = 0
93
 
94
		OPEN = [self.__start_node]
95
		CLOSED = []
96
 
97
		while OPEN:
98
			N = OPEN.pop(0)
99
			CLOSED.append (N)
100
 
101
			# Find all possible next paths
387 ira 102
			M = N.get_children()
380 ira 103
 
387 ira 104
			###############################################################
380 ira 105
			# Add the current place to the result graph
395 ira 106
			result.add_vertex (str(N), str(counter), raw_obj=N)
392 ira 107
 
380 ira 108
			if not firsttime:
387 ira 109
				v1 = str(N)
110
				v2 = str(self.__find_nearest_child (M, CLOSED))
380 ira 111
 
112
				result.add_edge (v1, v2)
396 ira 113
				result.set_edge_color (v1, v2, 'red')
380 ira 114
				result.set_edge_label (v1, v2, str(counter))
115
 
116
			else:
391 ira 117
				# Set start node shape to be a double circle
396 ira 118
				result.set_vertex_shape (str(N), 'doublecircle')
380 ira 119
				firsttime = False
387 ira 120
			###############################################################
380 ira 121
 
122
			# Check if we've reached the goal
123
			if N in self.__goal_nodes:
391 ira 124
				# Set the goal node's shape to be a diamond
396 ira 125
				result.set_vertex_shape (str(N), 'diamond')
380 ira 126
 
393 ira 127
				# Create the return result
128
				search_result = SearchResult (completed=True,
129
							status='Success',
130
							depth_reached=N.depth,
131
							nodes_created=len(OPEN)+len(CLOSED),
132
							result_graph=result)
133
				return search_result
134
 
388 ira 135
			# Add the children of N (aka M) to OPEN
136
			OPEN = add_function (M, OPEN, CLOSED)
380 ira 137
 
138
			counter += 1
139
 
388 ira 140
			# Check to make sure we don't loop for too long
391 ira 141
			if (len(OPEN) + len(CLOSED)) > MAX_NODES_CREATED:
393 ira 142
				search_result = SearchResult (completed=False,
143
							status='FAILURE -- Max nodes exceeded',
144
							depth_reached=N.depth,
145
							nodes_created=len(OPEN)+len(CLOSED),
146
							result_graph=None)
147
				return search_result
388 ira 148
 
393 ira 149
		search_result = SearchResult (completed=False,
150
					status='FAILURE -- goal not found',
151
					depth_reached=N.depth,
152
					nodes_created=len(OPEN)+len(CLOSED),
153
					result_graph=None)
154
		return search_result
380 ira 155
 
393 ira 156
################################################################################
157
### Specific Search Algorithms
158
################################################################################
380 ira 159
def add_bfs (M, OPEN, CLOSED):
160
	for node in M:
161
		if (node not in OPEN) and (node not in CLOSED):
162
			OPEN.append (node)
163
 
382 ira 164
	return OPEN
165
 
380 ira 166
def add_dfs (M, OPEN, CLOSED):
167
	for node in M:
168
		if (node not in OPEN) and (node not in CLOSED):
169
			OPEN.insert (0, node) # insert node at beginning
170
 
382 ira 171
	return OPEN
380 ira 172
 
393 ira 173
def best_first_generic (M, OPEN, CLOSED, heuristic_func):
174
	newopen = []
382 ira 175
 
393 ira 176
	for node in M:
177
		if (node not in OPEN) and (node not in CLOSED):
178
			OPEN.append (node)
179
 
180
	for node in OPEN:
181
		heuristic = heuristic_func (node)
182
		newopen.append ((heuristic, node))
183
 
184
	newopen.sort ()
185
 
186
	return [n[1] for n in newopen]
187
 
188
def bfs_oop (M, OPEN, CLOSED):
189
	return best_first_generic (M, OPEN, CLOSED, lambda x: x.num_out_of_place ())
190
 
191
def bfs_dfc (M, OPEN, CLOSED):
192
	return best_first_generic (M, OPEN, CLOSED, lambda x: x.total_distance_from_correct ())
193
 
194
def astar_bfs (M, OPEN, CLOSED):
195
	return best_first_generic (M, OPEN, CLOSED, lambda x: x.depth)
196
 
197
def astar_oop (M, OPEN, CLOSED):
198
	return best_first_generic (M, OPEN, CLOSED,
199
			lambda x: x.depth + x.num_out_of_place ())
200
 
201
def astar_dfc (M, OPEN, CLOSED):
202
	return best_first_generic (M, OPEN, CLOSED,
203
			lambda x: x.depth + x.total_distance_from_correct ())
204
 
205
 
380 ira 206
from Graph import Graph
207
from DrawGraph import DrawGraph
387 ira 208
import random
380 ira 209
 
387 ira 210
def get_nth_child (start, n):
211
	child = start
212
	for i in xrange(n):
213
		child = random.choice(child.get_children())
214
 
215
	return child
216
 
380 ira 217
def main ():
387 ira 218
	initial = [1, 2, 3, 4, 'E', 5, 6, 7, 8]
380 ira 219
 
393 ira 220
	start = PuzzlePiece (initial) # temporary use!
221
	goal = get_nth_child (start, 20)
222
	start = PuzzlePiece (initial, 0, goal)
380 ira 223
 
387 ira 224
	s = PuzzleSearch (start, (goal, ))
393 ira 225
 
226
	# Run some tests
227
	result = s.search (add_dfs, 1000)
228
	result.set_search_name ('Depth First Search')
229
	print result
230
	print
231
 
388 ira 232
	result = s.search (add_bfs, 1000)
393 ira 233
	result.set_search_name ('Breadth First Search: normal')
234
	print result
235
	print
380 ira 236
 
393 ira 237
	result = s.search (bfs_oop, 1000)
238
	result.set_search_name ('Best First Search: Out-of-place')
239
	print result
240
	print
241
 
242
	result = s.search (astar_bfs, 1000)
243
	result.set_search_name ('A*-search: Breadth-first-search')
244
	print result
245
	print
246
 
247
	result = s.search (astar_oop, 1000)
248
	result.set_search_name ('A*-search: Out-of-place')
249
	print result
250
	print
251
 
252
	result = s.search (astar_dfc, 1000)
253
	result.set_search_name ('A*-search: Distance-from-correct')
254
	print result
255
	print
256
 
257
	if result.result_graph != None:
395 ira 258
		DrawGraph ('result', result.result_graph).render_graphviz ('res.svg', yapgvb.engines.dot)
259
		DrawGraph ('result', result.result_graph).render_stupid  ('generated_by')
388 ira 260
	else:
393 ira 261
		print 'Failed to render graph'
380 ira 262
 
263
 
264
if __name__ == '__main__':
265
	main ()