diff --git a/.gitignore b/.gitignore index 225fc6f..158882f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ /__pycache__ +*.pyc + +# Text editor temp files +*~ +\#*\# diff --git a/FibonacciHeap.py b/FibonacciHeap.py index ca58b5a..0a01f99 100644 --- a/FibonacciHeap.py +++ b/FibonacciHeap.py @@ -93,6 +93,7 @@ def removeminimum(self): if self.minnode == None: raise AssertionError("Cannot remove from an empty heap") + removed_node = self.minnode self.count -= 1 # 1: Assign all old root children as new roots @@ -113,7 +114,7 @@ def removeminimum(self): if self.count != 0: raise AssertionError("Heap error: Expected 0 keys, count is " + str(self.count)) self.minnode = None - return + return removed_node # 2.2: Merge any roots with the same degree logsize = 100 @@ -154,6 +155,8 @@ def removeminimum(self): maxdegree = newmaxdegree + return removed_node + def decreasekey(self, node, newkey): if newkey > node.key: diff --git a/astar.py b/astar.py index e5a2f51..ed6e634 100644 --- a/astar.py +++ b/astar.py @@ -1,99 +1,103 @@ from FibonacciHeap import FibHeap +from priority_queue import FibPQ, HeapPQ, QueuePQ # This implementatoin of A* is almost identical to the Dijkstra implementation. So for clarity I've removed all comments, and only added those # Specifically showing the difference between dijkstra and A* def solve(maze): - width = maze.width - total = maze.width * maze.height - - start = maze.start - startpos = start.Position - end = maze.end - endpos = end.Position - - visited = [False] * total - prev = [None] * total - - infinity = float("inf") - distances = [infinity] * total - - unvisited = FibHeap() - - nodeindex = [None] * total - - distances[start.Position[0] * width + start.Position[1]] = 0 - startnode = FibHeap.Node(0, start) - nodeindex[start.Position[0] * width + start.Position[1]] = startnode - unvisited.insert(startnode) - - count = 0 - completed = False - - while unvisited.count > 0: - count += 1 - - n = unvisited.minimum() - unvisited.removeminimum() - - u = n.value - upos = u.Position - uposindex = upos[0] * width + upos[1] - - if distances[uposindex] == infinity: - break - - if upos == endpos: - completed = True - break - - for v in u.Neighbours: - if v != None: - vpos = v.Position - vposindex = vpos[0] * width + vpos[1] - - if visited[vposindex] == False: - d = abs(vpos[0] - upos[0]) + abs(vpos[1] - upos[1]) - - # New path cost to v is distance to u + extra. Some descriptions of A* call this the g cost. - # New distance is the distance of the path from the start, through U, to V. - newdistance = distances[uposindex] + d - - # A* includes a remaining cost, the f cost. In this case we use manhattan distance to calculate the distance from - # V to the end. We use manhattan again because A* works well when the g cost and f cost are balanced. - # https://en.wikipedia.org/wiki/Taxicab_geometry - remaining = abs(vpos[0] - endpos[0]) + abs(vpos[1] - endpos[1]) - - # Notice that we don't include f cost in this first check. We want to know that the path *to* our node V is shortest - if newdistance < distances[vposindex]: - vnode = nodeindex[vposindex] - - if vnode == None: - # V goes into the priority queue with a cost of g + f. So if it's moving closer to the end, it'll get higher - # priority than some other nodes. The order we visit nodes is a trade-off between a short path, and moving - # closer to the goal. - vnode = FibHeap.Node(newdistance + remaining, v) - unvisited.insert(vnode) - nodeindex[vposindex] = vnode - # The distance *to* the node remains just g, no f included. - distances[vposindex] = newdistance - prev[vposindex] = u - else: - # As above, we decrease the node since we've found a new path. But we include the f cost, the distance remaining. - unvisited.decreasekey(vnode, newdistance + remaining) - # The distance *to* the node remains just g, no f included. - distances[vposindex] = newdistance - prev[vposindex] = u - - - visited[uposindex] = True - - from collections import deque - - path = deque() - current = end - while (current != None): - path.appendleft(current) - current = prev[current.Position[0] * width + current.Position[1]] - - return [path, [count, len(path), completed]] + width = maze.width + total = maze.width * maze.height + + start = maze.start + startpos = start.Position + end = maze.end + endpos = end.Position + + visited = [False] * total + prev = [None] * total + + infinity = float("inf") + distances = [infinity] * total + + # The priority queue. There are multiple implementations in priority_queue.py + # unvisited = FibHeap() + unvisited = HeapPQ() + # unvisited = FibPQ() + # unvisited = QueuePQ() + + nodeindex = [None] * total + + distances[start.Position[0] * width + start.Position[1]] = 0 + startnode = FibHeap.Node(0, start) + nodeindex[start.Position[0] * width + start.Position[1]] = startnode + unvisited.insert(startnode) + + count = 0 + completed = False + + while len(unvisited) > 0: + count += 1 + + n = unvisited.removeminimum() + + u = n.value + upos = u.Position + uposindex = upos[0] * width + upos[1] + + if distances[uposindex] == infinity: + break + + if upos == endpos: + completed = True + break + + for v in u.Neighbours: + if v != None: + vpos = v.Position + vposindex = vpos[0] * width + vpos[1] + + if visited[vposindex] == False: + d = abs(vpos[0] - upos[0]) + abs(vpos[1] - upos[1]) + + # New path cost to v is distance to u + extra. Some descriptions of A* call this the g cost. + # New distance is the distance of the path from the start, through U, to V. + newdistance = distances[uposindex] + d + + # A* includes a remaining cost, the f cost. In this case we use manhattan distance to calculate the distance from + # V to the end. We use manhattan again because A* works well when the g cost and f cost are balanced. + # https://en.wikipedia.org/wiki/Taxicab_geometry + remaining = abs(vpos[0] - endpos[0]) + abs(vpos[1] - endpos[1]) + + # Notice that we don't include f cost in this first check. We want to know that the path *to* our node V is shortest + if newdistance < distances[vposindex]: + vnode = nodeindex[vposindex] + + if vnode == None: + # V goes into the priority queue with a cost of g + f. So if it's moving closer to the end, it'll get higher + # priority than some other nodes. The order we visit nodes is a trade-off between a short path, and moving + # closer to the goal. + vnode = FibHeap.Node(newdistance + remaining, v) + unvisited.insert(vnode) + nodeindex[vposindex] = vnode + # The distance *to* the node remains just g, no f included. + distances[vposindex] = newdistance + prev[vposindex] = u + else: + # As above, we decrease the node since we've found a new path. But we include the f cost, the distance remaining. + unvisited.decreasekey(vnode, newdistance + remaining) + # The distance *to* the node remains just g, no f included. + distances[vposindex] = newdistance + prev[vposindex] = u + + + visited[uposindex] = True + + from collections import deque + + path = deque() + current = end + while (current != None): + path.appendleft(current) + current = prev[current.Position[0] * width + current.Position[1]] + + return [path, [count, len(path), completed]] diff --git a/breadthfirst.py b/breadthfirst.py index 408d809..d874620 100644 --- a/breadthfirst.py +++ b/breadthfirst.py @@ -1,42 +1,42 @@ from collections import deque def solve(maze): - start = maze.start - end = maze.end + start = maze.start + end = maze.end - width = maze.width + width = maze.width - queue = deque([start]) - shape = (maze.height, maze.width) - prev = [None] * (maze.width * maze.height) - visited = [False] * (maze.width * maze.height) + queue = deque([start]) + shape = (maze.height, maze.width) + prev = [None] * (maze.width * maze.height) + visited = [False] * (maze.width * maze.height) - count = 0 + count = 0 - completed = False + completed = False - visited[start.Position[0] * width + start.Position[1]] = True + visited[start.Position[0] * width + start.Position[1]] = True - while queue: - count += 1 - current = queue.pop() + while queue: + count += 1 + current = queue.pop() - if current == end: - completed = True - break + if current == end: + completed = True + break - for n in current.Neighbours: - if n != None: - npos = n.Position[0] * width + n.Position[1] - if visited[npos] == False: - queue.appendleft(n) - visited[npos] = True - prev[npos] = current + for n in current.Neighbours: + if n != None: + npos = n.Position[0] * width + n.Position[1] + if visited[npos] == False: + queue.appendleft(n) + visited[npos] = True + prev[npos] = current - path = deque() - current = end - while (current != None): - path.appendleft(current) - current = prev[current.Position[0] * width + current.Position[1]] + path = deque() + current = end + while (current != None): + path.appendleft(current) + current = prev[current.Position[0] * width + current.Position[1]] - return [path, [count, len(path), completed]] + return [path, [count, len(path), completed]] diff --git a/depthfirst.py b/depthfirst.py index 53b3f4a..9e08932 100644 --- a/depthfirst.py +++ b/depthfirst.py @@ -1,40 +1,40 @@ from collections import deque def solve(maze): - start = maze.start - end = maze.end - width = maze.width - stack = deque([start]) - shape = (maze.height, maze.width) - prev = [None] * (maze.width * maze.height) - visited = [False] * (maze.width * maze.height) - count = 0 - - completed = False - while stack: - count += 1 - current = stack.pop() - - if current == end: - completed = True - break - - visited[current.Position[0] * width + current.Position[1]] = True - - #import code - #code.interact(local=locals()) - - for n in current.Neighbours: - if n != None: - npos = n.Position[0] * width + n.Position[1] - if visited[npos] == False: - stack.append(n) - prev[npos] = current - - path = deque() - current = end - while (current != None): - path.appendleft(current) - current = prev[current.Position[0] * width + current.Position[1]] - - return [path, [count, len(path), completed]] + start = maze.start + end = maze.end + width = maze.width + stack = deque([start]) + shape = (maze.height, maze.width) + prev = [None] * (maze.width * maze.height) + visited = [False] * (maze.width * maze.height) + count = 0 + + completed = False + while stack: + count += 1 + current = stack.pop() + + if current == end: + completed = True + break + + visited[current.Position[0] * width + current.Position[1]] = True + + #import code + #code.interact(local=locals()) + + for n in current.Neighbours: + if n != None: + npos = n.Position[0] * width + n.Position[1] + if visited[npos] == False: + stack.append(n) + prev[npos] = current + + path = deque() + current = end + while (current != None): + path.appendleft(current) + current = prev[current.Position[0] * width + current.Position[1]] + + return [path, [count, len(path), completed]] diff --git a/dijkstra.py b/dijkstra.py index 15a2f15..4078515 100644 --- a/dijkstra.py +++ b/dijkstra.py @@ -1,104 +1,107 @@ from FibonacciHeap import FibHeap +from priority_queue import FibPQ, HeapPQ, QueuePQ def solve(maze): - # Width is used for indexing, total is used for array sizes - width = maze.width - total = maze.width * maze.height - - # Start node, end node - start = maze.start - startpos = start.Position - end = maze.end - endpos = end.Position - - # Visited holds true/false on whether a node has been seen already. Used to stop us returning to nodes multiple times - visited = [False] * total - - # Previous holds a link to the previous node in the path. Used at the end for reconstructing the route - prev = [None] * total - - # Distances holds the distance to any node taking the best known path so far. Better paths replace worse ones as we find them. - # We start with all distances at infinity - infinity = float("inf") - distances = [infinity] * total - - # The priority queue. We are using a Fibonacci heap in this case. - unvisited = FibHeap() - - # This index holds all priority queue nodes as they are created. We use this to decrease the key of a specific node when a shorter path is found. - # This isn't hugely memory efficient, but likely to be faster than a dictionary or similar. - nodeindex = [None] * total - - # To begin, we set the distance to the start to zero (we're there) and add it into the unvisited queue - distances[start.Position[0] * width + start.Position[1]] = 0 - startnode = FibHeap.Node(0, start) - nodeindex[start.Position[0] * width + start.Position[1]] = startnode - unvisited.insert(startnode) - - # Zero nodes visited, and not completed yet. - count = 0 - completed = False - - # Begin Dijkstra - continue while there are unvisited nodes in the queue - while unvisited.count > 0: - count += 1 - - # Find current shortest path point to explore - n = unvisited.minimum() - unvisited.removeminimum() - - # Current node u, all neighbours will be v - u = n.value - upos = u.Position - uposindex = upos[0] * width + upos[1] - - if distances[uposindex] == infinity: - break - - # If upos == endpos, we're done! - if upos == endpos: - completed = True - break - - for v in u.Neighbours: - if v != None: - vpos = v.Position - vposindex = vpos[0] * width + vpos[1] - - if visited[vposindex] == False: - # The extra distance from where we are (upos) to the neighbour (vpos) - this is manhattan distance - # https://en.wikipedia.org/wiki/Taxicab_geometry - d = abs(vpos[0] - upos[0]) + abs(vpos[1] - upos[1]) - - # New path cost to v is distance to u + extra - newdistance = distances[uposindex] + d - - # If this new distance is the new shortest path to v - if newdistance < distances[vposindex]: - vnode = nodeindex[vposindex] - # v isn't already in the priority queue - add it - if vnode == None: - vnode = FibHeap.Node(newdistance, v) - unvisited.insert(vnode) - nodeindex[vposindex] = vnode - distances[vposindex] = newdistance - prev[vposindex] = u - # v is already in the queue - decrease its key to re-prioritise it - else: - unvisited.decreasekey(vnode, newdistance) - distances[vposindex] = newdistance - prev[vposindex] = u - - visited[uposindex] = True - - - # We want to reconstruct the path. We start at end, and then go prev[end] and follow all the prev[] links until we're back at the start - from collections import deque - - path = deque() - current = end - while (current != None): - path.appendleft(current) - current = prev[current.Position[0] * width + current.Position[1]] - - return [path, [count, len(path), completed]] + # Width is used for indexing, total is used for array sizes + width = maze.width + total = maze.width * maze.height + + # Start node, end node + start = maze.start + startpos = start.Position + end = maze.end + endpos = end.Position + + # Visited holds true/false on whether a node has been seen already. Used to stop us returning to nodes multiple times + visited = [False] * total + + # Previous holds a link to the previous node in the path. Used at the end for reconstructing the route + prev = [None] * total + + # Distances holds the distance to any node taking the best known path so far. Better paths replace worse ones as we find them. + # We start with all distances at infinity + infinity = float("inf") + distances = [infinity] * total + + # The priority queue. There are multiple implementations in priority_queue.py + # unvisited = FibHeap() + unvisited = HeapPQ() + # unvisited = FibPQ() + # unvisited = QueuePQ() + + # This index holds all priority queue nodes as they are created. We use this to decrease the key of a specific node when a shorter path is found. + # This isn't hugely memory efficient, but likely to be faster than a dictionary or similar. + nodeindex = [None] * total + + # To begin, we set the distance to the start to zero (we're there) and add it into the unvisited queue + distances[start.Position[0] * width + start.Position[1]] = 0 + startnode = FibHeap.Node(0, start) + nodeindex[start.Position[0] * width + start.Position[1]] = startnode + unvisited.insert(startnode) + + # Zero nodes visited, and not completed yet. + count = 0 + completed = False + + # Begin Dijkstra - continue while there are unvisited nodes in the queue + while len(unvisited) > 0: + count += 1 + + # Find current shortest path point to explore + n = unvisited.removeminimum() + + # Current node u, all neighbours will be v + u = n.value + upos = u.Position + uposindex = upos[0] * width + upos[1] + + if distances[uposindex] == infinity: + break + + # If upos == endpos, we're done! + if upos == endpos: + completed = True + break + + for v in u.Neighbours: + if v != None: + vpos = v.Position + vposindex = vpos[0] * width + vpos[1] + + if visited[vposindex] == False: + # The extra distance from where we are (upos) to the neighbour (vpos) - this is manhattan distance + # https://en.wikipedia.org/wiki/Taxicab_geometry + d = abs(vpos[0] - upos[0]) + abs(vpos[1] - upos[1]) + + # New path cost to v is distance to u + extra + newdistance = distances[uposindex] + d + + # If this new distance is the new shortest path to v + if newdistance < distances[vposindex]: + vnode = nodeindex[vposindex] + # v isn't already in the priority queue - add it + if vnode == None: + vnode = FibHeap.Node(newdistance, v) + unvisited.insert(vnode) + nodeindex[vposindex] = vnode + distances[vposindex] = newdistance + prev[vposindex] = u + # v is already in the queue - decrease its key to re-prioritise it + else: + unvisited.decreasekey(vnode, newdistance) + distances[vposindex] = newdistance + prev[vposindex] = u + + visited[uposindex] = True + + + # We want to reconstruct the path. We start at end, and then go prev[end] and follow all the prev[] links until we're back at the start + from collections import deque + + path = deque() + current = end + while (current != None): + path.appendleft(current) + current = prev[current.Position[0] * width + current.Position[1]] + + return [path, [count, len(path), completed]] diff --git a/factory.py b/factory.py index f05fc7f..fbbf112 100644 --- a/factory.py +++ b/factory.py @@ -2,23 +2,23 @@ # Not hugely necessary, but reduces the code in solve.py, making it easier to read. class SolverFactory: - def __init__(self): - self.Default = "breadthfirst" - self.Choices = ["breadthfirst","depthfirst","dijkstra", "astar","leftturn"] + def __init__(self): + self.Default = "breadthfirst" + self.Choices = ["breadthfirst","depthfirst","dijkstra", "astar","leftturn"] - def createsolver(self, type): - if type == "leftturn": - import leftturn - return ["Left turn only", leftturn.solve] - elif type == "depthfirst": - import depthfirst - return ["Depth first search", depthfirst.solve] - elif type == "dijkstra": - import dijkstra - return ["Dijkstra's Algorithm", dijkstra.solve] - elif type == "astar": - import astar - return ["A-star Search", astar.solve] - else: - import breadthfirst - return ["Breadth first search", breadthfirst.solve] + def createsolver(self, type): + if type == "leftturn": + import leftturn + return ["Left turn only", leftturn.solve] + elif type == "depthfirst": + import depthfirst + return ["Depth first search", depthfirst.solve] + elif type == "dijkstra": + import dijkstra + return ["Dijkstra's Algorithm", dijkstra.solve] + elif type == "astar": + import astar + return ["A-star Search", astar.solve] + else: + import breadthfirst + return ["Breadth first search", breadthfirst.solve] diff --git a/leftturn.py b/leftturn.py index 4d1fc48..1c76c14 100644 --- a/leftturn.py +++ b/leftturn.py @@ -2,59 +2,59 @@ def solve(maze): - path = deque([maze.start]) + path = deque([maze.start]) - current = maze.start.Neighbours[2] + current = maze.start.Neighbours[2] - if current == None: - return path + if current == None: + return path - heading = 2 # South + heading = 2 # South - turn = 1 # Turning left, -1 for right + turn = 1 # Turning left, -1 for right - startpos = maze.start.Position - endpos = maze.end.Position + startpos = maze.start.Position + endpos = maze.end.Position - # N E S W - just a helpful reminder - # 0 1 2 3 + # N E S W - just a helpful reminder + # 0 1 2 3 - count = 1 + count = 1 - completed = False + completed = False - while True: - path.append(current) - count += 1 - position = current.Position - if position == startpos or position == endpos: - if position == endpos: - completed = True - break + while True: + path.append(current) + count += 1 + position = current.Position + if position == startpos or position == endpos: + if position == endpos: + completed = True + break - n = current.Neighbours + n = current.Neighbours - if n[(heading - turn) % 4] != None: - heading = (heading - turn) % 4 - current = n[heading] - continue + if n[(heading - turn) % 4] != None: + heading = (heading - turn) % 4 + current = n[heading] + continue - if n[heading] != None: - current = n[heading] - continue + if n[heading] != None: + current = n[heading] + continue - if n[(heading + turn) % 4] != None: - heading = (heading + turn) % 4 - current = n[heading] - continue + if n[(heading + turn) % 4] != None: + heading = (heading + turn) % 4 + current = n[heading] + continue - if n[(heading + 2) % 4] != None: - heading = (heading + 2) % 4 - current = n[heading] - continue + if n[(heading + 2) % 4] != None: + heading = (heading + 2) % 4 + current = n[heading] + continue - completed = False - break + completed = False + break - return [path, [count, len(path), completed]] + return [path, [count, len(path), completed]] diff --git a/mazes.py b/mazes.py index 8a842de..9586cb2 100644 --- a/mazes.py +++ b/mazes.py @@ -1,111 +1,111 @@ class Maze: - class Node: - def __init__(self, position): - self.Position = position - self.Neighbours = [None, None, None, None] - #self.Weights = [0, 0, 0, 0] - - def __init__(self, im): - - width = im.width - height = im.height - data = list(im.getdata(0)) - - self.start = None - self.end = None - - # Top row buffer - topnodes = [None] * width - count = 0 - - # Start row - for x in range (1, width - 1): - if data[x] > 0: - self.start = Maze.Node((0,x)) - topnodes[x] = self.start - count += 1 - - for y in range (1, height - 1): - #print ("row", str(y)) # Uncomment this line to keep a track of row progress - - rowoffset = y * width - rowaboveoffset = rowoffset - width - rowbelowoffset = rowoffset + width - - # Initialise previous, current and next values - prv = False - cur = False - nxt = data[rowoffset + 1] > 0 - - leftnode = None - - for x in range (1, width - 1): - # Move prev, current and next onwards. This way we read from the image once per pixel, marginal optimisation - prv = cur - cur = nxt - nxt = data[rowoffset + x + 1] > 0 - - n = None - - if cur == False: - # ON WALL - No action - continue - - if prv == True: - if nxt == True: - # PATH PATH PATH - # Create node only if paths above or below - if data[rowaboveoffset + x] > 0 or data[rowbelowoffset + x] > 0: - n = Maze.Node((y,x)) - leftnode.Neighbours[1] = n - n.Neighbours[3] = leftnode - leftnode = n - else: - # PATH PATH WALL - # Create path at end of corridor - n = Maze.Node((y,x)) - leftnode.Neighbours[1] = n - n.Neighbours[3] = leftnode - leftnode = None - else: - if nxt == True: - # WALL PATH PATH - # Create path at start of corridor - n = Maze.Node((y,x)) - leftnode = n - else: - # WALL PATH WALL - # Create node only if in dead end - if (data[rowaboveoffset + x] == 0) or (data[rowbelowoffset + x] == 0): - #print ("Create Node in dead end") - n = Maze.Node((y,x)) - - # If node isn't none, we can assume we can connect N-S somewhere - if n != None: - # Clear above, connect to waiting top node - if (data[rowaboveoffset + x] > 0): - t = topnodes[x] - t.Neighbours[2] = n - n.Neighbours[0] = t - - # If clear below, put this new node in the top row for the next connection - if (data[rowbelowoffset + x] > 0): - topnodes[x] = n - else: - topnodes[x] = None - - count += 1 - - # End row - rowoffset = (height - 1) * width - for x in range (1, width - 1): - if data[rowoffset + x] > 0: - self.end = Maze.Node((height - 1,x)) - t = topnodes[x] - t.Neighbours[2] = self.end - self.end.Neighbours[0] = t - count += 1 - - self.count = count - self.width = width - self.height = height + class Node: + def __init__(self, position): + self.Position = position + self.Neighbours = [None, None, None, None] + #self.Weights = [0, 0, 0, 0] + + def __init__(self, im): + + width = im.width + height = im.height + data = list(im.getdata(0)) + + self.start = None + self.end = None + + # Top row buffer + topnodes = [None] * width + count = 0 + + # Start row + for x in range (1, width - 1): + if data[x] > 0: + self.start = Maze.Node((0,x)) + topnodes[x] = self.start + count += 1 + + for y in range (1, height - 1): + #print ("row", str(y)) # Uncomment this line to keep a track of row progress + + rowoffset = y * width + rowaboveoffset = rowoffset - width + rowbelowoffset = rowoffset + width + + # Initialise previous, current and next values + prv = False + cur = False + nxt = data[rowoffset + 1] > 0 + + leftnode = None + + for x in range (1, width - 1): + # Move prev, current and next onwards. This way we read from the image once per pixel, marginal optimisation + prv = cur + cur = nxt + nxt = data[rowoffset + x + 1] > 0 + + n = None + + if cur == False: + # ON WALL - No action + continue + + if prv == True: + if nxt == True: + # PATH PATH PATH + # Create node only if paths above or below + if data[rowaboveoffset + x] > 0 or data[rowbelowoffset + x] > 0: + n = Maze.Node((y,x)) + leftnode.Neighbours[1] = n + n.Neighbours[3] = leftnode + leftnode = n + else: + # PATH PATH WALL + # Create path at end of corridor + n = Maze.Node((y,x)) + leftnode.Neighbours[1] = n + n.Neighbours[3] = leftnode + leftnode = None + else: + if nxt == True: + # WALL PATH PATH + # Create path at start of corridor + n = Maze.Node((y,x)) + leftnode = n + else: + # WALL PATH WALL + # Create node only if in dead end + if (data[rowaboveoffset + x] == 0) or (data[rowbelowoffset + x] == 0): + #print ("Create Node in dead end") + n = Maze.Node((y,x)) + + # If node isn't none, we can assume we can connect N-S somewhere + if n != None: + # Clear above, connect to waiting top node + if (data[rowaboveoffset + x] > 0): + t = topnodes[x] + t.Neighbours[2] = n + n.Neighbours[0] = t + + # If clear below, put this new node in the top row for the next connection + if (data[rowbelowoffset + x] > 0): + topnodes[x] = n + else: + topnodes[x] = None + + count += 1 + + # End row + rowoffset = (height - 1) * width + for x in range (1, width - 1): + if data[rowoffset + x] > 0: + self.end = Maze.Node((height - 1,x)) + t = topnodes[x] + t.Neighbours[2] = self.end + self.end.Neighbours[0] = t + count += 1 + + self.count = count + self.width = width + self.height = height diff --git a/priority_queue.py b/priority_queue.py new file mode 100644 index 0000000..763edbb --- /dev/null +++ b/priority_queue.py @@ -0,0 +1,130 @@ + +from abc import ABCMeta, abstractmethod +import itertools + +from FibonacciHeap import FibHeap +import heapq +import Queue + +class PriorityQueue(): + __metaclass__ = ABCMeta + + @abstractmethod + def __len__(self): pass + + @abstractmethod + def insert(self, node): pass + + @abstractmethod + def minimum(self): pass + + @abstractmethod + def removeminimum(self): pass + + @abstractmethod + def decreasekey(self, node, new_priority): pass + +class FibPQ(PriorityQueue): + def __init__(self): + self.heap = FibHeap() + + def __len__(self): + return self.heap.count + + def insert(self, node): + self.heap.insert(node) + + def minimum(self): + return self.heap.minimum() + + def removeminimum(self): + return self.heap.removeminimum() + + def decreasekey(self, node, new_priority): + self.heap.decreasekey(node, new_priority) + +class HeapPQ(PriorityQueue): + def __init__(self): + self.pq = [] + self.removed = set() + self.count = 0 + + def __len__(self): + return self.count + + def insert(self, node): + entry = node.key, node.value + if entry in self.removed: + self.removed.discard(entry) + heapq.heappush(self.pq, entry) + self.count += 1 + + def minimum(self): + priority, item = heapq.heappop(self.pq) + node = FibHeap.Node(priority, item) + self.insert(node) + return node + + def removeminimum(self): + while True: + (priority, item) = heapq.heappop(self.pq) + if (priority, item) in self.removed: + self.removed.discard((priority, item)) + else: + self.count -= 1 + return FibHeap.Node(priority, item) + + def remove(self, node): + entry = node.key, node.value + if entry not in self.removed: + self.removed.add(entry) + self.count -= 1 + + def decreasekey(self, node, new_priority): + self.remove(node) + node.key = new_priority + self.insert(node) + + +class QueuePQ(PriorityQueue): + def __init__(self): + self.pq = Queue.PriorityQueue() + self.removed = set() + self.count = 0 + + def __len__(self): + return self.count + + def insert(self, node): + entry = node.key, node.value + if entry in self.removed: + self.removed.discard(entry) + self.pq.put(entry) + self.count += 1 + + def minimum(self): + (priority, item) = self.pq.get_nowait() + node = FibHeap.Node(priority, item) + self.insert(node) + return node + + def removeminimum(self): + while True: + (priority, item) = self.pq.get_nowait() + if (priority, item) in self.removed: + self.removed.discard((priority, item)) + else: + self.count -= 1 + return FibHeap.Node(priority, item) + + def remove(self, node): + entry = node.key, node.value + if entry not in self.removed: + self.removed.add(entry) + self.count -= 1 + + def decreasekey(self, node, new_priority): + self.remove(node) + node.key = new_priority + self.insert(node) + diff --git a/profile.py b/profile.py new file mode 100644 index 0000000..62cf7a1 --- /dev/null +++ b/profile.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +from bprofile import BProfile + +import tempfile +from solve import solve +from factory import SolverFactory + +methods = [ + "astar", + # "breadthfirst", + # "depthfirst", + "dijkstra", + # "leftturn", +] +inputs = [ + # "tiny", + # "small", + # "normal", + # "braid200", + "logo", + "combo400", + "braid2k", + "perfect2k", + # "perfect4k", + # "combo6k", + # "perfect10k", + # "vertical15k", +] + +def profile(): + for m in methods: + for i in inputs: + with tempfile.NamedTemporaryFile(suffix='.png') as tmp: + solve(SolverFactory(), m, "examples/%s.png" % i, tmp.name) + +profiler = BProfile('profiler.png') +with profiler: + profile() diff --git a/solve.py b/solve.py index e48a732..be915dc 100644 --- a/solve.py +++ b/solve.py @@ -6,84 +6,92 @@ # Read command line arguments - the python argparse class is convenient here. import argparse -sf = SolverFactory() -parser = argparse.ArgumentParser() -parser.add_argument("-m", "--method", nargs='?', const=sf.Default, default=sf.Default, - choices=sf.Choices) -parser.add_argument("input_file") -parser.add_argument("output_file") -args = parser.parse_args() - -method = args.method - -# Load Image -print ("Loading Image") -im = Image.open(args.input_file) - -# Create the maze (and time it) - for many mazes this is more time consuming than solving the maze -print ("Creating Maze") -t0 = time.time() -maze = Maze(im) -t1 = time.time() -print ("Node Count:", maze.count) -total = t1-t0 -print ("Time elapsed:", total, "\n") - -# Create and run solver -[title, solver] = sf.createsolver(args.method) -print ("Starting Solve:", title) - -t0 = time.time() -[result, stats] = solver(maze) -t1 = time.time() - -total = t1-t0 - -# Print solve stats -print ("Nodes explored: ", stats[0]) -if (stats[2]): - print ("Path found, length", stats[1]) -else: - print ("No Path Found") -print ("Time elapsed: ", total, "\n") - -""" -Create and save the output image. -This is simple drawing code that travels between each node in turn, drawing either -a horizontal or vertical line as required. Line colour is roughly interpolated between -blue and red depending on how far down the path this section is. Dependency on numpy -should be easy to remove at some point. -""" - -print ("Saving Image") -mazeimage = np.array(im) -imout = np.array(mazeimage) -imout[imout==1] = 255 -out = imout[:,:,np.newaxis] - -out = np.repeat(out, 3, axis=2) - -resultpath = [n.Position for n in result] - -length = len(resultpath) - -px = [0, 0, 0] -for i in range(0, length - 1): - a = resultpath[i] - b = resultpath[i+1] - - # Blue - red - px[0] = int((i / length) * 255) - px[2] = 255 - px[0] - - if a[0] == b[0]: - # Ys equal - horizontal line - for x in range(min(a[1],b[1]), max(a[1],b[1])): - out[a[0],x,:] = px - elif a[1] == b[1]: - # Xs equal - vertical line - for y in range(min(a[0],b[0]), max(a[0],b[0]) + 1): - out[y,a[1],:] = px - -img = Image.fromarray(out) -img.save(args.output_file) + +def solve(factory, method, input_file, output_file): + # Load Image + print ("Loading Image") + im = Image.open(input_file) + + # Create the maze (and time it) - for many mazes this is more time consuming than solving the maze + print ("Creating Maze") + t0 = time.time() + maze = Maze(im) + t1 = time.time() + print ("Node Count:", maze.count) + total = t1-t0 + print ("Time elapsed:", total, "\n") + + # Create and run solver + [title, solver] = factory.createsolver(method) + print ("Starting Solve:", title) + + t0 = time.time() + [result, stats] = solver(maze) + t1 = time.time() + + total = t1-t0 + + # Print solve stats + print ("Nodes explored: ", stats[0]) + if (stats[2]): + print ("Path found, length", stats[1]) + else: + print ("No Path Found") + print ("Time elapsed: ", total, "\n") + + """ + Create and save the output image. + This is simple drawing code that travels between each node in turn, drawing either + a horizontal or vertical line as required. Line colour is roughly interpolated between + blue and red depending on how far down the path this section is. Dependency on numpy + should be easy to remove at some point. + """ + + print ("Saving Image") + mazeimage = np.array(im) + imout = np.array(mazeimage) + imout[imout==1] = 255 + out = imout[:,:,np.newaxis] + + out = np.repeat(out, 3, axis=2) + + resultpath = [n.Position for n in result] + + length = len(resultpath) + + px = [0, 0, 0] + for i in range(0, length - 1): + a = resultpath[i] + b = resultpath[i+1] + + # Blue - red + px[0] = int((i / length) * 255) + px[2] = 255 - px[0] + + if a[0] == b[0]: + # Ys equal - horizontal line + for x in range(min(a[1],b[1]), max(a[1],b[1])): + out[a[0],x,:] = px + elif a[1] == b[1]: + # Xs equal - vertical line + for y in range(min(a[0],b[0]), max(a[0],b[0]) + 1): + out[y,a[1],:] = px + + img = Image.fromarray(out) + img.save(output_file) + + +def main(): + sf = SolverFactory() + parser = argparse.ArgumentParser() + parser.add_argument("-m", "--method", nargs='?', const=sf.Default, default=sf.Default, + choices=sf.Choices) + parser.add_argument("input_file") + parser.add_argument("output_file") + args = parser.parse_args() + + solve(sf, args.method, args.input_file, args.output_file) + +if __name__ == "__main__": + main() +