Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
96335a1
Remove erronious text files
matthomas15 Aug 11, 2020
49ab91f
Repository Structure for Comparison Platform
matthomas15 Aug 11, 2020
a780ca9
Adding visual.py
verbal-noun Aug 11, 2020
af93e34
Adding benchmark.py for benchmarking
verbal-noun Aug 11, 2020
7d48700
Maze Generation Features
matthomas15 Aug 14, 2020
5a7ef20
Merge branch 'feature/comparison_platform' of https://github.com/matt…
matthomas15 Aug 14, 2020
5dc43ee
Merging benchmark and Wrapper Functions
matthomas15 Aug 14, 2020
200ea3d
Importing a-star variants
verbal-noun Aug 14, 2020
956bffe
Adding path visualisation
verbal-noun Aug 15, 2020
d311f68
Upgrading the a-star
verbal-noun Aug 28, 2020
074d140
Implemented faster a-star algorithm
verbal-noun Aug 28, 2020
9d18e11
Fixed logic error and implemented results
verbal-noun Aug 28, 2020
0640791
Implemented visuals being based in a results folder
verbal-noun Aug 28, 2020
df67f54
Added csv result storage feature
verbal-noun Aug 28, 2020
6ba1e65
Adding comments
verbal-noun Aug 28, 2020
05a9dc2
Adding updated A-star files
verbal-noun Aug 28, 2020
a2da92a
Adding README.md
verbal-noun Aug 30, 2020
b49f666
Adding comments
verbal-noun Aug 31, 2020
1125e5d
Updating README
verbal-noun Sep 3, 2020
aedebf7
Adding results in README
verbal-noun Sep 3, 2020
47afa8b
Added wrapper guide
verbal-noun Sep 3, 2020
2e43593
Adding images
verbal-noun Sep 3, 2020
c030479
Adding images
verbal-noun Sep 3, 2020
d8aeef9
updating images
verbal-noun Sep 3, 2020
eabdb7e
Fixing typos and images
verbal-noun Sep 3, 2020
46f6f2a
Add files via upload
Xuester Sep 6, 2020
171ff0d
Add files via upload
Xuester Sep 6, 2020
3af5ff0
Update rrt_star_2d.py
Xuester Sep 6, 2020
b1f32c0
fresh off the keyboard yay
Xuester Sep 11, 2020
bb82f8d
Changed relative imports for RRT star
verbal-noun Sep 11, 2020
2d65a89
Convert obstacle input for RRT
verbal-noun Sep 11, 2020
bf0bbe0
RRT star needs processing
verbal-noun Sep 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
*.pyc
*.pyc

.vscode
23 changes: 14 additions & 9 deletions nova_rover_demos/pathfinding/a_star.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import numpy as np
from itertools import product
from math import sqrt, inf
from pathfinding.heuristic import euclidean_cost
import sys
import os
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
Expand All @@ -6,10 +10,6 @@
except:
raise

from pathfinding.heuristic import euclidean_cost
from math import sqrt, inf
from itertools import product
import numpy as np

def reconstruct_path_to_destination(prev, end):
"""
Expand All @@ -25,6 +25,8 @@ def reconstruct_path_to_destination(prev, end):
return path

# A* Search


def get_successors(node, grid):
"""
The neighbors of a cell (node) in the grid are the 8-surrounding cells.
Expand All @@ -35,7 +37,7 @@ def get_successors(node, grid):
n_rows = len(grid)
n_cols = len(grid[0])

for dx, dy in product([-1,0,1],[-1,0,1]):
for dx, dy in product([-1, 0, 1], [-1, 0, 1]):
# skip the current node itself
if (dx == 0 and dy == 0):
continue
Expand All @@ -49,17 +51,20 @@ def get_successors(node, grid):
# put infinite penalty on successors that would take us off the edge of the grid
cost = inf

successors.append( ((x, y), cost) )
successors.append(((x, y), cost))

return successors

def node_with_min_fscore(open_set, f_cost): # open_set is a set (of cell) and f_cost is a dict (with cells as keys)

# open_set is a set (of cell) and f_cost is a dict (with cells as keys)
def node_with_min_fscore(open_set, f_cost):
"""
Find the cell in open set with the smallest f score.
"""
f_cost_open = dict([a for a in f_cost.items() if a[0] in open_set])
return min(f_cost_open, key=f_cost_open.get)


def a_star_search(grid, start, end, heuristic_cost=euclidean_cost):
"""
Implementation of A Star over a 2D grid. Returns a list of waypoints
Expand Down Expand Up @@ -108,7 +113,7 @@ def a_star_search(grid, start, end, heuristic_cost=euclidean_cost):
if neighbor in closed_set:
continue

curr_g_score = g_cost[curr] + cost
curr_g_score = g_cost[curr] + cost
# add neighbor to newly discovered nodes
if neighbor not in open_set:
open_set.add(neighbor)
Expand All @@ -122,4 +127,4 @@ def a_star_search(grid, start, end, heuristic_cost=euclidean_cost):
f_cost[neighbor] = g_cost[neighbor] + heuristic_cost(neighbor, end)

# if we get to this point, it's not possible to reach the end destination
return []
return []
103 changes: 103 additions & 0 deletions nova_rover_demos/pathfinding/comparison_platform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Path Finding Algorithm Comparison Platform

## Introduction
This is software platform aimed at comparing the relative performance of various path planning/route finding algorithms. This software tool benchmarks the processing time, peak memory usage and distance of the path found and presents them in a visual manner. We have designed our path finding software in such a way so easily a range of different algorithms can be added to the platform and their performance can be benchmarked.

Currently in the repository there are three classes of pathfinding algorithms are present as default:
1. A* and variants (Heuristic based family)
2. Pledge (Wall follower family)
3. RRT and variants (Probabilistic family)


## Usage

Our goal is to create a scalable easy to use function. To use the comparison platform you simply need to:

1. Import your algorithm(s) into the comparison platform
2. Select it inside the `run.py` file

### 1. Importing your Algorithm(s)

**Step 1:** You can import the file or folder of the algorithm inside the lib folder.

<img alt = "Lib folder" src="img/lib-folder.png" width="250">

**Step 2:** Ensure you have fixed any relative imports inside your files. This step is only applicable if you have multiple files are they're importing functions/classes from each other.

<img alt = "Relative import" src="img/relative-import.png" width="600">

Because our a-star variant file imports a class from a-star and both are under the lib folder, we import in the manner shown in the image above.

**Step 3:** Write a wrapper class (If applicable). Currently the comparison platform is designed in a way that all functions accept 3 arguments in the order:
1. List of obstacles (A list of (x, y) tuples)
2. A starting coordinate (A (x, y) format tuple)
3. A goal coordinate (A (x, y) format tuple)

If your algorithm accepts other out formats, you can call the main algorithm with all the necessary arguments inside the wrapper function.

<img alt = "Wrapper class" src="img/wrapper-demo.png" width="400">

We have put together a very [short guide](wrapper.md) on writing the wrapper functions and you can find [here](wrapper.md). It has a template code for you to get started as well.

### 2. Configure `run.py`

**Step 4:** Import algorithm or wrapper function in the `run.py` file.


<img alt = "Run.py file" src="img/run-file.png" width="200">

Importing the files should be done as the following (Remember to do relative importing):

<img alt = "Importing algorithms" src="img/runpy-import.png" width="600">


**Step 5:** List your algorithm / wrapper function in the algorithm list.

<img alt = "Listing algorithms" src="img/algorithm-list.png" width="600">

**Step 6:** Run `run.py` file. After completing the easy steps above simply run the following command:

```python
python run.py
```

You can also select the number of times the functions will be tested. For example:

```python
python run.py 10
```

**Step 7 (Optional):** Configure how dense you want the environment to be.

<img alt = "Map density" src="img/map-density.png" width="600">


### Check algorithm is working correctly

Even if no errors are thrown during runtime, you should check the results folder. In the results folder a visual map (PDF file) with the planned route should be automatically generated with the name of your algorithm/wrapper function. Please review this figure and see if everything is okay.

You can also check the CSV generated inside the results folder to cross check things.


## Results

The main power of this comparison platform lies within the extensive results it generated. The comparison platform gives you visualization of the following key performance factors:
- Runtime
- Memory Comparison
- Average path length


Upon running the program you should see a couple of figures which give you a visual comparison of the algorithms key metrics.


<img alt = "Result sample 1" src="img/results-fig-1.png" width="350"> <img alt = "Result sample 2" src="img/results-fig-2.png" width="350">


Furthermore inside the results folder you can also view the map of the environment and the path generated by each of the algorithms in separate PDF file. Sample:

<img alt = "Map sample" src="img/map-sample.png" width="500" style="padding-bottom: 15px;">


You have also have access to the raw numbers inside a CSV file inside the results folder.

<img alt = "CSV sample" src="img/csv-sample.png" width="600">
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
169 changes: 169 additions & 0 deletions nova_rover_demos/pathfinding/comparison_platform/lib/a_star.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
'''
This file is provided for additional testing of the different algorithms
'''

import time
import random
import math
import heapq
from collections import defaultdict

# Wrapper class with added functionalities


class PriorityQueue:
def __init__(self):
self.elements = []

def isEmpty(self):
return len(self.elements) == 0

def put(self, item, priority):
heapq.heappush(self.elements, (priority, item))

def get(self):
# Return only the item
return heapq.heappop(self.elements)[1]


# Function to reconstruct path from start to goal using the source dictionary
def reconstruct_path(came_from, start, goal):
current = goal
path = []

while current != start:
path.append(current)
current = came_from[current]

# Add the start to the path - optional
path.append(start)
# reverse the path to get from start to goal
path.reverse()
return path

# Combine two paths and avoid happing overlapping nodes


def join_paths(path_1, path_2):
path_2.reverse()
if(path_1[-1] == path_2[0]):
return path_1 + path_2[1:]


# The heuristic used by the a-star algorithm
# This is used to calculate the distance of current node form the goal too
def manhattan_heuristic(a, b):
(x1, y1) = a
(x2, y2) = b

return abs(x1 - x2) + abs(y1 - y2)

# Diagonal heuristic which considers the diagonal value of nodes


def diagonal_heuristic(a, b):
(x1, y1) = a
(x2, y2) = b

return max(abs(x1 - x2), abs(y1 - y2))

# Taking the euclidian distance as heuristics


def euclidian_heuristic(a, b):
(x1, y1) = a
(x2, y2) = b

return math.sqrt((x1 - x2)**2 + (y1 - y2)**2)

# Implementation of the a-star search algorithm


def a_star_search(graph, start, goal):
# Priority Queue track progression of nodes
frontier = PriorityQueue()
frontier.put(start, 0)

# Dictionary to track origin of a node
came_from = {}
# Dictionary to track the cost to move to a particular node
cost_so_far = {}

# Add starting node into the dictionaries
came_from[start] = None
cost_so_far[start] = 0

# While the Priority queue is not empty
while not frontier.isEmpty():
# Get the top of queue
current = frontier.get()

# check if we have reached destination
if current == goal:
break

# Loop through neighbors of current node and process them
for next in graph.neighbors(current):
# Calculate the new cost to travel to neighboring node
# The new cost is cost of travelling to current node plus the cost of
# travelling from current node to the neighbor
new_cost = cost_so_far[current] + graph.cost(current, next)

# Check if this node hasn't been reached before or we have a new cheaper path
if next not in cost_so_far or new_cost < cost_so_far[next]:
cost_so_far[next] = new_cost
# Set the priority of the neighbor using the heuristic
# We are taking distance to goal in consideration through heuristics
priority = new_cost + manhattan_heuristic(goal, next)
frontier.put(next, priority)
came_from[next] = current

# Return the cost and source dictionary
# return came_from, cost_so_far
return reconstruct_path(came_from, start, goal)


# A wrapper function around the main star search function
def a_star(oc_grid, start, end):
diagram = OpenGrid()
diagram.add_walls(oc_grid)
try:
path = a_star_search(diagram, start, end)
maze_solved = True
except:
path = start
maze_solved = False

return path, maze_solved


# A class which handles the grid world for A-star
class OpenGrid:
def __init__(self):
self.walls = defaultdict(int)
self.weights = {}

# Check if current location is blocked for not
def passable(self, id):
return False if self.walls[id] == 1 else True

# Check the neighbors of the current grid
def neighbors(self, id):
(x, y) = id
result = [(x+1, y), (x, y-1), (x-1, y), (x, y+1)]
# Just for aesthetics
if(x + y) % 2 == 0:
result.reverse()
# Check if the neighbors are not blocked
result = list(filter(self.passable, result))

return result

# Method to get cost to travel from weights, else default value of 1
def cost(self, from_node, to_node):
return self.weights.get(to_node, 1)

# Helper method to add walls to the dictionary
def add_walls(self, walls):
for wall in walls:
self.walls[wall] = 1
Loading