Subversion Repositories programming

Rev

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