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