├── README.md ├── helpers.py ├── map-10.pickle ├── map-40.pickle ├── project_notebook.ipynb └── test.py /README.md: -------------------------------------------------------------------------------- 1 | # Archival Note 2 | We are archiving this repository because we do not want learners to push personal development to the current repository. If you have any issues or suggestions to make, feel free to: 3 | - Utilize the https://knowledge.udacity.com/ forum to seek help on content-specific issues. 4 | - Submit a support ticket along with the link to your forked repository if (learners are) blocked for other reasons. Here are the links for the [retail consumers](https://udacity.zendesk.com/hc/en-us/requests/new) and [enterprise learners](https://udacityenterprise.zendesk.com/hc/en-us/requests/new?ticket_form_id=360000279131). 5 | 6 | # isdc-router-planner-project 7 | Final project for "Navigating Data Structures" in the Introduction to Self Driving Cars Nanodegree 8 | 9 | ## Project Requirements 10 | 11 | To run this project you will need: 12 | 13 | 1. Python 3 14 | 2. [Jupyter notebook](http://jupyter.readthedocs.io/en/latest/install.html) 15 | 3. [networkx](https://networkx.github.io/documentation/latest/install.html) 16 | 4. [plotly](https://plot.ly/python/getting-started/#installation) 17 | 18 | ## Reviewer Instructions 19 | 20 | Open `project_notebook.ipynb` in a Jupyter notebook and go through the instructions listed there! 21 | -------------------------------------------------------------------------------- /helpers.py: -------------------------------------------------------------------------------- 1 | 2 | import networkx as nx 3 | import pickle 4 | import plotly.plotly as py 5 | import random 6 | from plotly.graph_objs import * 7 | from plotly.offline import init_notebook_mode, plot, iplot 8 | init_notebook_mode(connected=True) 9 | 10 | 11 | map_10_dict = { 12 | 0: {'pos': (0.7798606835438107, 0.6922727646627362), 'connections': [7, 6, 5]}, 13 | 1: {'pos': (0.7647837074641568, 0.3252670836724646), 'connections': [4, 3, 2]}, 14 | 2: {'pos': (0.7155217893995438, 0.20026498027300055), 'connections': [4, 3, 1]}, 15 | 3: {'pos': (0.7076566826610747, 0.3278339270610988), 'connections': [5, 4, 1, 2]}, 16 | 4: {'pos': (0.8325506249953353, 0.02310946309985762), 'connections': [1, 2, 3]}, 17 | 5: {'pos': (0.49016747075266875, 0.5464878695400415), 'connections': [7, 0, 3]}, 18 | 6: {'pos': (0.8820353070895344, 0.6791919587749445), 'connections': [0]}, 19 | 7: {'pos': (0.46247219371675075, 0.6258061621642713), 'connections': [0, 5]}, 20 | 8: {'pos': (0.11622158839385677, 0.11236327488812581), 'connections': [9]}, 21 | 9: {'pos': (0.1285377678230034, 0.3285840695698353), 'connections': [8]} 22 | } 23 | 24 | map_40_dict = { 25 | 0: {'pos': (0.7801603911549438, 0.49474860768712914), 'connections': [36, 34, 31, 28, 17]}, 26 | 1: {'pos': (0.5249831588690298, 0.14953665513987202), 'connections': [35, 31, 27, 26, 25, 20, 18, 17, 15, 6]}, 27 | 2: {'pos': (0.8085335344099086, 0.7696330846542071), 'connections': [39, 36, 21, 19, 9, 7, 4]}, 28 | 3: {'pos': (0.2599134798656856, 0.14485659826020547), 'connections': [35, 20, 15, 11, 6]}, 29 | 4: {'pos': (0.7353838928272886, 0.8089961609345658), 'connections': [39, 36, 21, 19, 9, 7, 2]}, 30 | 5: {'pos': (0.09088671576431506, 0.7222846879290787), 'connections': [32, 16, 14]}, 31 | 6: {'pos': (0.313999018186756, 0.01876171413125327), 'connections': [35, 20, 15, 11, 1, 3]}, 32 | 7: {'pos': (0.6824813442515916, 0.8016111783687677), 'connections': [39, 36, 22, 21, 19, 9, 2, 4]}, 33 | 8: {'pos': (0.20128789391122526, 0.43196344222361227), 'connections': [33, 30, 14]}, 34 | 9: {'pos': (0.8551947714242674, 0.9011339078096633), 'connections': [36, 21, 19, 2, 4, 7]}, 35 | 10: {'pos': (0.7581736589784409, 0.24026772497187532), 'connections': [31, 27, 26, 25, 24, 18, 17, 13]}, 36 | 11: {'pos': (0.25311953895059136, 0.10321622277398101), 'connections': [35, 20, 15, 3, 6]}, 37 | 12: {'pos': (0.4813859169876731, 0.5006237737207431), 'connections': [37, 34, 31, 28, 22, 17]}, 38 | 13: {'pos': (0.9112422509614865, 0.1839028760606296), 'connections': [27, 24, 18, 10]}, 39 | 14: {'pos': (0.04580558670435442, 0.5886703168399895), 'connections': [33, 30, 16, 5, 8]}, 40 | 15: {'pos': (0.4582523173083307, 0.1735506267461867), 'connections': [35, 31, 26, 25, 20, 17, 1, 3, 6, 11]}, 41 | 16: {'pos': (0.12939557977525573, 0.690016328140396), 'connections': [37, 30, 5, 14]}, 42 | 17: {'pos': (0.607698913404794, 0.362322730884702), 'connections': [34, 31, 28, 26, 25, 18, 0, 1, 10, 12, 15]}, 43 | 18: {'pos': (0.719569201584275, 0.13985272363426526), 'connections': [31, 27, 26, 25, 24, 1, 10, 13, 17]}, 44 | 19: {'pos': (0.8860336256842246, 0.891868301175821), 'connections': [21, 2, 4, 7, 9]}, 45 | 20: {'pos': (0.4238357358399233, 0.026771817842421997), 'connections': [35, 26, 1, 3, 6, 11, 15]}, 46 | 21: {'pos': (0.8252497121120052, 0.9532681441921305), 'connections': [2, 4, 7, 9, 19]}, 47 | 22: {'pos': (0.47415009287034726, 0.7353428557575755), 'connections': [39, 37, 29, 7, 12]}, 48 | 23: {'pos': (0.26253385360950576, 0.9768234503830939), 'connections': [38, 32, 29]}, 49 | 24: {'pos': (0.9363713903322148, 0.13022993020357043), 'connections': [27, 10, 13, 18]}, 50 | 25: {'pos': (0.6243437191127235, 0.21665962402659544), 'connections': [34, 31, 27, 26, 1, 10, 15, 17, 18]}, 51 | 26: {'pos': (0.5572917679006295, 0.2083567880838434), 'connections': [34, 31, 27, 1, 10, 15, 17, 18, 20, 25]}, 52 | 27: {'pos': (0.7482655725962591, 0.12631654071213483), 'connections': [31, 1, 10, 13, 18, 24, 25, 26]}, 53 | 28: {'pos': (0.6435799740880603, 0.5488515965193208), 'connections': [39, 36, 34, 31, 0, 12, 17]}, 54 | 29: {'pos': (0.34509802713919313, 0.8800306496459869), 'connections': [38, 37, 32, 22, 23]}, 55 | 30: {'pos': (0.021423673670808885, 0.4666482714834408), 'connections': [33, 8, 14, 16]}, 56 | 31: {'pos': (0.640952694324525, 0.3232711412508066), 'connections': [34, 0, 1, 10, 12, 15, 17, 18, 25, 26, 27, 28]}, 57 | 32: {'pos': (0.17440205342790494, 0.9528527425842739), 'connections': [38, 5, 23, 29]}, 58 | 33: {'pos': (0.1332965908314021, 0.3996510641743197), 'connections': [8, 14, 30]}, 59 | 34: {'pos': (0.583993110207876, 0.42704536740474663), 'connections': [0, 12, 17, 25, 26, 28, 31]}, 60 | 35: {'pos': (0.3073865727705063, 0.09186645974288632), 'connections': [1, 3, 6, 11, 15, 20]}, 61 | 36: {'pos': (0.740625863119245, 0.68128520136847), 'connections': [39, 0, 2, 4, 7, 9, 28]}, 62 | 37: {'pos': (0.3345284735051981, 0.6569436279895382), 'connections': [12, 16, 22, 29]}, 63 | 38: {'pos': (0.17972981733780147, 0.999395685828547), 'connections': [23, 29, 32]}, 64 | 39: {'pos': (0.6315322816286787, 0.7311657634689946), 'connections': [2, 4, 7, 22, 28, 36]} 65 | } 66 | 67 | 68 | class Map: 69 | def __init__(self, G): 70 | self._graph = G 71 | self.intersections = nx.get_node_attributes(G, "pos") 72 | self.roads = [list(G[node]) for node in G.nodes()] 73 | 74 | def save(self, filename): 75 | with open(filename, 'wb') as f: 76 | pickle.dump(self._graph, f) 77 | 78 | def load_map_graph(map_dict): 79 | G = nx.Graph() 80 | for node in map_dict.keys(): 81 | G.add_node(node, pos=map_dict[node]['pos']) 82 | for node in map_dict.keys(): 83 | for con_node in map_dict[node]['connections']: 84 | G.add_edge(node, con_node) 85 | return G 86 | 87 | def load_map_10(): 88 | G = load_map_graph(map_10_dict) 89 | return Map(G) 90 | 91 | def load_map_40(): 92 | G = load_map_graph(map_40_dict) 93 | return Map(G) 94 | 95 | def show_map(M, start=None, goal=None, path=None): 96 | G = M._graph 97 | pos = nx.get_node_attributes(G, 'pos') 98 | edge_trace = Scatter( 99 | x=[], 100 | y=[], 101 | line=Line(width=0.5,color='#888'), 102 | hoverinfo='none', 103 | mode='lines') 104 | 105 | for edge in G.edges(): 106 | x0, y0 = G.node[edge[0]]['pos'] 107 | x1, y1 = G.node[edge[1]]['pos'] 108 | edge_trace['x'] += [x0, x1, None] 109 | edge_trace['y'] += [y0, y1, None] 110 | 111 | node_trace = Scatter( 112 | x=[], 113 | y=[], 114 | text=[], 115 | mode='markers', 116 | hoverinfo='text', 117 | marker=Marker( 118 | showscale=False, 119 | # colorscale options 120 | # 'Greys' | 'Greens' | 'Bluered' | 'Hot' | 'Picnic' | 'Portland' | 121 | # Jet' | 'RdBu' | 'Blackbody' | 'Earth' | 'Electric' | 'YIOrRd' | 'YIGnBu' 122 | colorscale='Hot', 123 | reversescale=True, 124 | color=[], 125 | size=10, 126 | colorbar=dict( 127 | thickness=15, 128 | title='Node Connections', 129 | xanchor='left', 130 | titleside='right' 131 | ), 132 | line=dict(width=2))) 133 | for node in G.nodes(): 134 | x, y = G.node[node]['pos'] 135 | node_trace['x'].append(x) 136 | node_trace['y'].append(y) 137 | 138 | for node, adjacencies in enumerate(G.adjacency_list()): 139 | color = 0 140 | if path and node in path: 141 | color = 2 142 | if node == start: 143 | color = 3 144 | elif node == goal: 145 | color = 1 146 | # node_trace['marker']['color'].append(len(adjacencies)) 147 | node_trace['marker']['color'].append(color) 148 | node_info = "Intersection " + str(node) 149 | node_trace['text'].append(node_info) 150 | 151 | fig = Figure(data=Data([edge_trace, node_trace]), 152 | layout=Layout( 153 | title='
Network graph made with Python', 154 | titlefont=dict(size=16), 155 | showlegend=False, 156 | hovermode='closest', 157 | margin=dict(b=20,l=5,r=5,t=40), 158 | 159 | xaxis=XAxis(showgrid=False, zeroline=False, showticklabels=False), 160 | yaxis=YAxis(showgrid=False, zeroline=False, showticklabels=False))) 161 | 162 | iplot(fig) 163 | -------------------------------------------------------------------------------- /map-10.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/isdc-router-planner-project/3b08cd88860706a8b4d5decda2455555e9ce854c/map-10.pickle -------------------------------------------------------------------------------- /map-40.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/isdc-router-planner-project/3b08cd88860706a8b4d5decda2455555e9ce854c/map-40.pickle -------------------------------------------------------------------------------- /project_notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "collapsed": true 7 | }, 8 | "source": [ 9 | "# Implementing a Route Planner\n", 10 | "In this project you will use A\\* search to implement a \"Google-maps\" style route planning algorithm." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "## The Map" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "# Run this cell first!\n", 27 | "\n", 28 | "from helpers import Map, load_map_10, load_map_40, show_map\n", 29 | "import math\n", 30 | "\n", 31 | "%load_ext autoreload\n", 32 | "%autoreload 2" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "### Map Basics" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "map_10 = load_map_10()\n", 49 | "show_map(map_10)" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "The map above (run the code cell if you don't see it) shows a disconnected network of 10 intersections. The two intersections on the left are connected to each other but they are not connected to the rest of the road network. This map is quite literal in its expression of distance and connectivity. On the graph above, the edge between 2 nodes(intersections) represents a literal straight road not just an abstract connection of 2 cities.\n", 57 | "\n", 58 | "These `Map` objects have two properties you will want to use to implement A\\* search: `intersections` and `roads`\n", 59 | "\n", 60 | "**Intersections**\n", 61 | "\n", 62 | "The `intersections` are represented as a dictionary. \n", 63 | "\n", 64 | "In this example, there are 10 intersections, each identified by an x,y coordinate. The coordinates are listed below. You can hover over each dot in the map above to see the intersection number." 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "map_10.intersections" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "**Roads**\n", 81 | "\n", 82 | "The `roads` property is a list where `roads[i]` contains a list of the intersections that intersection `i` connects to." 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [ 91 | "# this shows that intersection 0 connects to intersections 7, 6, and 5\n", 92 | "map_10.roads[0] " 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "# This shows the full connectivity of the map\n", 102 | "map_10.roads" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "# map_40 is a bigger map than map_10\n", 112 | "map_40 = load_map_40()\n", 113 | "show_map(map_40)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "### Advanced Visualizations\n", 121 | "\n", 122 | "The map above shows a network of roads which spans 40 different intersections (labeled 0 through 39). \n", 123 | "\n", 124 | "The `show_map` function which generated this map also takes a few optional parameters which might be useful for visualizing the output of the search algorithm you will write.\n", 125 | "\n", 126 | "* `start` - The \"start\" node for the search algorithm.\n", 127 | "* `goal` - The \"goal\" node.\n", 128 | "* `path` - An array of integers which corresponds to a valid sequence of intersection visits on the map." 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "# run this code, note the effect of including the optional\n", 138 | "# parameters in the function call.\n", 139 | "show_map(map_40, start=5, goal=34, path=[5,16,37,12,34])" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "## The Algorithm\n", 147 | "### Writing your algorithm\n", 148 | "The algorithm written will be responsible for generating a `path` like the one passed into `show_map` above. In fact, when called with the same map, start and goal, as above you algorithm should produce the path `[5, 16, 37, 12, 34]`. However you must complete several methods before it will work.\n", 149 | "\n", 150 | "```bash\n", 151 | "> PathPlanner(map_40, 5, 34).path\n", 152 | "[5, 16, 37, 12, 34]\n", 153 | "```" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "### PathPlanner class\n", 161 | "\n", 162 | "The below class is already partly implemented for you - you will implement additional functions that will also get included within this class further below.\n", 163 | "\n", 164 | "Let's very briefly walk through each part below.\n", 165 | "\n", 166 | "`__init__` - We initialize our path planner with a map, M, and typically a start and goal node. If either of these are `None`, the rest of the variables here are also set to none. If you don't have both a start and a goal, there's no path to plan! The rest of these variables come from functions you will soon implement. \n", 167 | "- `closedSet` includes any explored/visited nodes. \n", 168 | "- `openSet` are any nodes on our frontier for potential future exploration. \n", 169 | "- `cameFrom` will hold the previous node that best reaches a given node\n", 170 | "- `gScore` is the `g` in our `f = g + h` equation, or the actual cost to reach our current node\n", 171 | "- `fScore` is the combination of `g` and `h`, i.e. the `gScore` plus a heuristic; total cost to reach the goal\n", 172 | "- `path` comes from the `run_search` function, which is already built for you.\n", 173 | "\n", 174 | "`reconstruct_path` - This function just rebuilds the path after search is run, going from the goal node backwards using each node's `cameFrom` information.\n", 175 | "\n", 176 | "`_reset` - Resets *most* of our initialized variables for PathPlanner. This *does not* reset the map, start or goal variables, for reasons which you may notice later, depending on your implementation.\n", 177 | "\n", 178 | "`run_search` - This does a lot of the legwork to run search once you've implemented everything else below. First, it checks whether the map, goal and start have been added to the class. Then, it will also check if the other variables, other than `path` are initialized (note that these are only needed to be re-run if the goal or start were not originally given when initializing the class, based on what we discussed above for `__init__`.\n", 179 | "\n", 180 | "From here, we use a function you will implement, `is_open_empty`, to check that there are still nodes to explore (you'll need to make sure to feed `openSet` the start node to make sure the algorithm doesn't immediately think there is nothing to open!). If we're at our goal, we reconstruct the path. If not, we move our current node from the frontier (`openSet`) and into explored (`closedSet`). Then, we check out the neighbors of the current node, check out their costs, and plan our next move.\n", 181 | "\n", 182 | "This is the main idea behind A*, but none of it is going to work until you implement all the relevant parts, which will be included below after the class code." 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": null, 188 | "metadata": {}, 189 | "outputs": [], 190 | "source": [ 191 | "# Do not change this cell\n", 192 | "# When you write your methods correctly this cell will execute\n", 193 | "# without problems\n", 194 | "class PathPlanner():\n", 195 | " \"\"\"Construct a PathPlanner Object\"\"\"\n", 196 | " def __init__(self, M, start=None, goal=None):\n", 197 | " \"\"\" \"\"\"\n", 198 | " self.map = M\n", 199 | " self.start= start\n", 200 | " self.goal = goal\n", 201 | " self.closedSet = self.create_closedSet() if goal != None and start != None else None\n", 202 | " self.openSet = self.create_openSet() if goal != None and start != None else None\n", 203 | " self.cameFrom = self.create_cameFrom() if goal != None and start != None else None\n", 204 | " self.gScore = self.create_gScore() if goal != None and start != None else None\n", 205 | " self.fScore = self.create_fScore() if goal != None and start != None else None\n", 206 | " self.path = self.run_search() if self.map and self.start != None and self.goal != None else None\n", 207 | " \n", 208 | " def reconstruct_path(self, current):\n", 209 | " \"\"\" Reconstructs path after search \"\"\"\n", 210 | " total_path = [current]\n", 211 | " while current in self.cameFrom.keys():\n", 212 | " current = self.cameFrom[current]\n", 213 | " total_path.append(current)\n", 214 | " return total_path\n", 215 | " \n", 216 | " def _reset(self):\n", 217 | " \"\"\"Private method used to reset the closedSet, openSet, cameFrom, gScore, fScore, and path attributes\"\"\"\n", 218 | " self.closedSet = None\n", 219 | " self.openSet = None\n", 220 | " self.cameFrom = None\n", 221 | " self.gScore = None\n", 222 | " self.fScore = None\n", 223 | " self.path = self.run_search() if self.map and self.start and self.goal else None\n", 224 | "\n", 225 | " def run_search(self):\n", 226 | " \"\"\" \"\"\"\n", 227 | " if self.map == None:\n", 228 | " raise(ValueError, \"Must create map before running search. Try running PathPlanner.set_map(start_node)\")\n", 229 | " if self.goal == None:\n", 230 | " raise(ValueError, \"Must create goal node before running search. Try running PathPlanner.set_goal(start_node)\")\n", 231 | " if self.start == None:\n", 232 | " raise(ValueError, \"Must create start node before running search. Try running PathPlanner.set_start(start_node)\")\n", 233 | "\n", 234 | " self.closedSet = self.closedSet if self.closedSet != None else self.create_closedSet()\n", 235 | " self.openSet = self.openSet if self.openSet != None else self.create_openSet()\n", 236 | " self.cameFrom = self.cameFrom if self.cameFrom != None else self.create_cameFrom()\n", 237 | " self.gScore = self.gScore if self.gScore != None else self.create_gScore()\n", 238 | " self.fScore = self.fScore if self.fScore != None else self.create_fScore()\n", 239 | "\n", 240 | " while not self.is_open_empty():\n", 241 | " current = self.get_current_node()\n", 242 | "\n", 243 | " if current == self.goal:\n", 244 | " self.path = [x for x in reversed(self.reconstruct_path(current))]\n", 245 | " return self.path\n", 246 | " else:\n", 247 | " self.openSet.remove(current)\n", 248 | " self.closedSet.add(current)\n", 249 | "\n", 250 | " for neighbor in self.get_neighbors(current):\n", 251 | " if neighbor in self.closedSet:\n", 252 | " continue # Ignore the neighbor which is already evaluated.\n", 253 | "\n", 254 | " if not neighbor in self.openSet: # Discover a new node\n", 255 | " self.openSet.add(neighbor)\n", 256 | " \n", 257 | " # The distance from start to a neighbor\n", 258 | " #the \"dist_between\" function may vary as per the solution requirements.\n", 259 | " if self.get_tentative_gScore(current, neighbor) >= self.get_gScore(neighbor):\n", 260 | " continue # This is not a better path.\n", 261 | "\n", 262 | " # This path is the best until now. Record it!\n", 263 | " self.record_best_path_to(current, neighbor)\n", 264 | " print(\"No Path Found\")\n", 265 | " self.path = None\n", 266 | " return False" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": {}, 272 | "source": [ 273 | "## Your Turn\n", 274 | "\n", 275 | "Implement the following functions to get your search algorithm running smoothly!" 276 | ] 277 | }, 278 | { 279 | "cell_type": "markdown", 280 | "metadata": {}, 281 | "source": [ 282 | "### Data Structures\n", 283 | "\n", 284 | "The next few functions requre you to decide on data structures to use - lists, sets, dictionaries, etc. Make sure to think about what would work most efficiently for each of these. Some can be returned as just an empty data structure (see `create_closedSet()` for an example), while others should be initialized with one or more values within." 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": null, 290 | "metadata": {}, 291 | "outputs": [], 292 | "source": [ 293 | "def create_closedSet(self):\n", 294 | " \"\"\" Creates and returns a data structure suitable to hold the set of nodes already evaluated\"\"\"\n", 295 | " # EXAMPLE: return a data structure suitable to hold the set of nodes already evaluated\n", 296 | " return set()" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": null, 302 | "metadata": {}, 303 | "outputs": [], 304 | "source": [ 305 | "def create_openSet(self):\n", 306 | " \"\"\" Creates and returns a data structure suitable to hold the set of currently discovered nodes \n", 307 | " that are not evaluated yet. Initially, only the start node is known.\"\"\"\n", 308 | " if self.start != None:\n", 309 | " # TODO: return a data structure suitable to hold the set of currently discovered nodes \n", 310 | " # that are not evaluated yet. Make sure to include the start node.\n", 311 | " return\n", 312 | " \n", 313 | " raise(ValueError, \"Must create start node before creating an open set. Try running PathPlanner.set_start(start_node)\")" 314 | ] 315 | }, 316 | { 317 | "cell_type": "code", 318 | "execution_count": null, 319 | "metadata": {}, 320 | "outputs": [], 321 | "source": [ 322 | "def create_cameFrom(self):\n", 323 | " \"\"\"Creates and returns a data structure that shows which node can most efficiently be reached from another,\n", 324 | " for each node.\"\"\"\n", 325 | " # TODO: return a data structure that shows which node can most efficiently be reached from another,\n", 326 | " # for each node. " 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": null, 332 | "metadata": {}, 333 | "outputs": [], 334 | "source": [ 335 | "def create_gScore(self):\n", 336 | " \"\"\"Creates and returns a data structure that holds the cost of getting from the start node to that node, \n", 337 | " for each node. The cost of going from start to start is zero.\"\"\"\n", 338 | " # TODO: return a data structure that holds the cost of getting from the start node to that node, for each node.\n", 339 | " # for each node. The cost of going from start to start is zero. The rest of the node's values should \n", 340 | " # be set to infinity.\n", 341 | " " 342 | ] 343 | }, 344 | { 345 | "cell_type": "code", 346 | "execution_count": null, 347 | "metadata": {}, 348 | "outputs": [], 349 | "source": [ 350 | "def create_fScore(self):\n", 351 | " \"\"\"Creates and returns a data structure that holds the total cost of getting from the start node to the goal\n", 352 | " by passing by that node, for each node. That value is partly known, partly heuristic.\n", 353 | " For the first node, that value is completely heuristic.\"\"\"\n", 354 | " # TODO: return a data structure that holds the total cost of getting from the start node to the goal\n", 355 | " # by passing by that node, for each node. That value is partly known, partly heuristic.\n", 356 | " # For the first node, that value is completely heuristic. The rest of the node's value should be \n", 357 | " # set to infinity.\n", 358 | " \n" 359 | ] 360 | }, 361 | { 362 | "cell_type": "markdown", 363 | "metadata": {}, 364 | "source": [ 365 | "### Set certain variables\n", 366 | "\n", 367 | "The below functions help set certain variables if they weren't a part of initializating our `PathPlanner` class, or if they need to be changed for anothe reason." 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": null, 373 | "metadata": {}, 374 | "outputs": [], 375 | "source": [ 376 | "def set_map(self, M):\n", 377 | " \"\"\"Method used to set map attribute \"\"\"\n", 378 | " self._reset(self)\n", 379 | " self.start = None\n", 380 | " self.goal = None\n", 381 | " # TODO: Set map to new value. \n" 382 | ] 383 | }, 384 | { 385 | "cell_type": "code", 386 | "execution_count": null, 387 | "metadata": {}, 388 | "outputs": [], 389 | "source": [ 390 | "def set_start(self, start):\n", 391 | " \"\"\"Method used to set start attribute \"\"\"\n", 392 | " self._reset(self)\n", 393 | " # TODO: Set start value. Remember to remove goal, closedSet, openSet, cameFrom, gScore, fScore, \n", 394 | " # and path attributes' values.\n", 395 | " " 396 | ] 397 | }, 398 | { 399 | "cell_type": "code", 400 | "execution_count": null, 401 | "metadata": {}, 402 | "outputs": [], 403 | "source": [ 404 | "def set_goal(self, goal):\n", 405 | " \"\"\"Method used to set goal attribute \"\"\"\n", 406 | " self._reset(self)\n", 407 | " # TODO: Set goal value. \n" 408 | ] 409 | }, 410 | { 411 | "cell_type": "markdown", 412 | "metadata": {}, 413 | "source": [ 414 | "### Get node information\n", 415 | "\n", 416 | "The below functions concern grabbing certain node information. In `is_open_empty`, you are checking whether there are still nodes on the frontier to explore. In `get_current_node()`, you'll want to come up with a way to find the lowest `fScore` of the nodes on the frontier. In `get_neighbors`, you'll need to gather information from the map to find the neighbors of the current node." 417 | ] 418 | }, 419 | { 420 | "cell_type": "code", 421 | "execution_count": null, 422 | "metadata": {}, 423 | "outputs": [], 424 | "source": [ 425 | "def is_open_empty(self):\n", 426 | " \"\"\"returns True if the open set is empty. False otherwise. \"\"\"\n", 427 | " # TODO: Return True if the open set is empty. False otherwise.\n" 428 | ] 429 | }, 430 | { 431 | "cell_type": "code", 432 | "execution_count": null, 433 | "metadata": {}, 434 | "outputs": [], 435 | "source": [ 436 | "def get_current_node(self):\n", 437 | " \"\"\" Returns the node in the open set with the lowest value of f(node).\"\"\"\n", 438 | " # TODO: Return the node in the open set with the lowest value of f(node).\n" 439 | ] 440 | }, 441 | { 442 | "cell_type": "code", 443 | "execution_count": null, 444 | "metadata": {}, 445 | "outputs": [], 446 | "source": [ 447 | "def get_neighbors(self, node):\n", 448 | " \"\"\"Returns the neighbors of a node\"\"\"\n", 449 | " # TODO: Return the neighbors of a node\n" 450 | ] 451 | }, 452 | { 453 | "cell_type": "markdown", 454 | "metadata": {}, 455 | "source": [ 456 | "### Scores and Costs\n", 457 | "\n", 458 | "Below, you'll get into the main part of the calculation for determining the best path - calculating the various parts of the `fScore`." 459 | ] 460 | }, 461 | { 462 | "cell_type": "code", 463 | "execution_count": null, 464 | "metadata": {}, 465 | "outputs": [], 466 | "source": [ 467 | "def get_gScore(self, node):\n", 468 | " \"\"\"Returns the g Score of a node\"\"\"\n", 469 | " # TODO: Return the g Score of a node\n" 470 | ] 471 | }, 472 | { 473 | "cell_type": "code", 474 | "execution_count": null, 475 | "metadata": {}, 476 | "outputs": [], 477 | "source": [ 478 | "def distance(self, node_1, node_2):\n", 479 | " \"\"\" Computes the Euclidean L2 Distance\"\"\"\n", 480 | " # TODO: Compute and return the Euclidean L2 Distance\n" 481 | ] 482 | }, 483 | { 484 | "cell_type": "code", 485 | "execution_count": null, 486 | "metadata": {}, 487 | "outputs": [], 488 | "source": [ 489 | "def get_tentative_gScore(self, current, neighbor):\n", 490 | " \"\"\"Returns the tentative g Score of a node\"\"\"\n", 491 | " # TODO: Return the g Score of the current node \n", 492 | " # plus distance from the current node to it's neighbors\n" 493 | ] 494 | }, 495 | { 496 | "cell_type": "code", 497 | "execution_count": null, 498 | "metadata": {}, 499 | "outputs": [], 500 | "source": [ 501 | "def heuristic_cost_estimate(self, node):\n", 502 | " \"\"\" Returns the heuristic cost estimate of a node \"\"\"\n", 503 | " # TODO: Return the heuristic cost estimate of a node\n" 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": null, 509 | "metadata": {}, 510 | "outputs": [], 511 | "source": [ 512 | "def calculate_fscore(self, node):\n", 513 | " \"\"\"Calculate the f score of a node. \"\"\"\n", 514 | " # TODO: Calculate and returns the f score of a node. \n", 515 | " # REMEMBER F = G + H\n", 516 | " " 517 | ] 518 | }, 519 | { 520 | "cell_type": "markdown", 521 | "metadata": {}, 522 | "source": [ 523 | "### Recording the best path\n", 524 | "\n", 525 | "Now that you've implemented the various functions on scoring, you can record the best path to a given neighbor node from the current node!" 526 | ] 527 | }, 528 | { 529 | "cell_type": "code", 530 | "execution_count": null, 531 | "metadata": {}, 532 | "outputs": [], 533 | "source": [ 534 | "def record_best_path_to(self, current, neighbor):\n", 535 | " \"\"\"Record the best path to a node \"\"\"\n", 536 | " # TODO: Record the best path to a node, by updating cameFrom, gScore, and fScore\n" 537 | ] 538 | }, 539 | { 540 | "cell_type": "markdown", 541 | "metadata": {}, 542 | "source": [ 543 | "### Associating your functions with the `PathPlanner` class\n", 544 | "\n", 545 | "To check your implementations, we want to associate all of the above functions back to the `PathPlanner` class. Python makes this easy using the dot notation (i.e. `PathPlanner.myFunction`), and setting them equal to your function implementations. Run the below code cell for this to occur.\n", 546 | "\n", 547 | "*Note*: If you need to make further updates to your functions above, you'll need to re-run this code cell to associate the newly updated function back with the `PathPlanner` class again!" 548 | ] 549 | }, 550 | { 551 | "cell_type": "code", 552 | "execution_count": null, 553 | "metadata": {}, 554 | "outputs": [], 555 | "source": [ 556 | "# Associates implemented functions with PathPlanner class\n", 557 | "PathPlanner.create_closedSet = create_closedSet\n", 558 | "PathPlanner.create_openSet = create_openSet\n", 559 | "PathPlanner.create_cameFrom = create_cameFrom\n", 560 | "PathPlanner.create_gScore = create_gScore\n", 561 | "PathPlanner.create_fScore = create_fScore\n", 562 | "PathPlanner.set_map = set_map\n", 563 | "PathPlanner.set_start = set_start\n", 564 | "PathPlanner.set_goal = set_goal\n", 565 | "PathPlanner.is_open_empty = is_open_empty\n", 566 | "PathPlanner.get_current_node = get_current_node\n", 567 | "PathPlanner.get_neighbors = get_neighbors\n", 568 | "PathPlanner.get_gScore = get_gScore\n", 569 | "PathPlanner.distance = distance\n", 570 | "PathPlanner.get_tentative_gScore = get_tentative_gScore\n", 571 | "PathPlanner.heuristic_cost_estimate = heuristic_cost_estimate\n", 572 | "PathPlanner.calculate_fscore = calculate_fscore\n", 573 | "PathPlanner.record_best_path_to = record_best_path_to" 574 | ] 575 | }, 576 | { 577 | "cell_type": "markdown", 578 | "metadata": {}, 579 | "source": [ 580 | "### Preliminary Test\n", 581 | "\n", 582 | "The below is the first test case, just based off of one set of inputs. If some of the functions above aren't implemented yet, or are implemented incorrectly, you likely will get an error from running this cell. Try debugging the error to help you figure out what needs further revision!" 583 | ] 584 | }, 585 | { 586 | "cell_type": "code", 587 | "execution_count": null, 588 | "metadata": {}, 589 | "outputs": [], 590 | "source": [ 591 | "planner = PathPlanner(map_40, 5, 34)\n", 592 | "path = planner.path\n", 593 | "if path == [5, 16, 37, 12, 34]:\n", 594 | " print(\"great! Your code works for these inputs!\")\n", 595 | "else:\n", 596 | " print(\"something is off, your code produced the following:\")\n", 597 | " print(path)" 598 | ] 599 | }, 600 | { 601 | "cell_type": "markdown", 602 | "metadata": {}, 603 | "source": [ 604 | "#### Visualize\n", 605 | "\n", 606 | "Once the above code worked for you, let's visualize the results of your algorithm!" 607 | ] 608 | }, 609 | { 610 | "cell_type": "code", 611 | "execution_count": null, 612 | "metadata": {}, 613 | "outputs": [], 614 | "source": [ 615 | "# Visualize your the result of the above test! You can also change start and goal here to check other paths\n", 616 | "start = 5\n", 617 | "goal = 34\n", 618 | "\n", 619 | "show_map(map_40, start=start, goal=goal, path=PathPlanner(map_40, start, goal).path)" 620 | ] 621 | }, 622 | { 623 | "cell_type": "markdown", 624 | "metadata": {}, 625 | "source": [ 626 | "### Testing your Code\n", 627 | "If the code below produces no errors, your algorithm is behaving correctly. You are almost ready to submit! Before you submit, go through the following submission checklist:\n", 628 | "\n", 629 | "**Submission Checklist**\n", 630 | "\n", 631 | "1. Does my code pass all tests?\n", 632 | "2. Does my code implement `A*` search and not some other search algorithm?\n", 633 | "3. Do I use an **admissible heuristic** to direct search efforts towards the goal?\n", 634 | "4. Do I use data structures which avoid unnecessarily slow lookups?\n", 635 | "\n", 636 | "When you can answer \"yes\" to all of these questions, and also have answered the written questions below, submit by pressing the Submit button in the lower right!" 637 | ] 638 | }, 639 | { 640 | "cell_type": "code", 641 | "execution_count": null, 642 | "metadata": {}, 643 | "outputs": [], 644 | "source": [ 645 | "from test import test\n", 646 | "\n", 647 | "test(PathPlanner)" 648 | ] 649 | }, 650 | { 651 | "cell_type": "markdown", 652 | "metadata": {}, 653 | "source": [ 654 | "## Questions\n", 655 | "\n", 656 | "**Instructions** \n", 657 | "\n", 658 | "Answer the following questions in your own words. We do not you expect you to know all of this knowledge on the top of your head. We expect you to do research and ask question. However do not merely copy and paste the answer from a google or stackoverflow. Read the information and understand it first. Then use your own words to explain the answer." 659 | ] 660 | }, 661 | { 662 | "cell_type": "markdown", 663 | "metadata": {}, 664 | "source": [ 665 | "---\n", 666 | "How would you explain A-Star to a family member(layman)?\n", 667 | "\n", 668 | "**ANSWER**:\n" 669 | ] 670 | }, 671 | { 672 | "cell_type": "markdown", 673 | "metadata": {}, 674 | "source": [ 675 | "---\n", 676 | "How does A-Star search algorithm differ from Uniform cost search? What about Best First search?\n", 677 | "\n", 678 | "**ANSWER**:\n" 679 | ] 680 | }, 681 | { 682 | "cell_type": "markdown", 683 | "metadata": {}, 684 | "source": [ 685 | "---\n", 686 | "What is a heuristic?\n", 687 | "\n", 688 | "**ANSWER**:" 689 | ] 690 | }, 691 | { 692 | "cell_type": "markdown", 693 | "metadata": {}, 694 | "source": [ 695 | "---\n", 696 | "What is a consistent heuristic?\n", 697 | "\n", 698 | "**ANSWER**:" 699 | ] 700 | }, 701 | { 702 | "cell_type": "markdown", 703 | "metadata": {}, 704 | "source": [ 705 | "---\n", 706 | "What is a admissible heuristic? \n", 707 | "\n", 708 | "**ANSWER**:\n" 709 | ] 710 | }, 711 | { 712 | "cell_type": "markdown", 713 | "metadata": {}, 714 | "source": [ 715 | "---\n", 716 | "___ admissible heuristic are consistent.\n", 717 | "\n", 718 | "*CHOOSE ONE*\n", 719 | " - All\n", 720 | " - Some\n", 721 | " - None\n", 722 | " \n", 723 | "**ANSWER**:" 724 | ] 725 | }, 726 | { 727 | "cell_type": "markdown", 728 | "metadata": {}, 729 | "source": [ 730 | "---\n", 731 | "___ Consistent heuristic are admissible.\n", 732 | "\n", 733 | "*CHOOSE ONE*\n", 734 | " - All\n", 735 | " - Some\n", 736 | " - None\n", 737 | " \n", 738 | "**ANSWER**:" 739 | ] 740 | }, 741 | { 742 | "cell_type": "markdown", 743 | "metadata": {}, 744 | "source": [ 745 | "---" 746 | ] 747 | } 748 | ], 749 | "metadata": { 750 | "kernelspec": { 751 | "display_name": "Python 3", 752 | "language": "python", 753 | "name": "python3" 754 | }, 755 | "language_info": { 756 | "codemirror_mode": { 757 | "name": "ipython", 758 | "version": 3 759 | }, 760 | "file_extension": ".py", 761 | "mimetype": "text/x-python", 762 | "name": "python", 763 | "nbconvert_exporter": "python", 764 | "pygments_lexer": "ipython3", 765 | "version": "3.6.3" 766 | } 767 | }, 768 | "nbformat": 4, 769 | "nbformat_minor": 2 770 | } 771 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from helpers import load_map_40 2 | 3 | MAP_40_ANSWERS = [ 4 | (5, 34, [5, 16, 37, 12, 34]), 5 | (5, 5, [5]), 6 | (8, 24, [8, 14, 16, 37, 12, 17, 10, 24]) 7 | ] 8 | 9 | def test(shortest_path_function): 10 | map_40 = load_map_40() 11 | correct = 0 12 | for start, goal, answer_path in MAP_40_ANSWERS: 13 | path = shortest_path_function(map_40, start, goal).path 14 | if path == answer_path: 15 | correct += 1 16 | else: 17 | print("For start:", start, 18 | "Goal: ", goal, 19 | "Your path:", path, 20 | "Correct: ", answer_path) 21 | if correct == len(MAP_40_ANSWERS): 22 | print("All tests pass! Congratulations!") 23 | else: 24 | print("You passed", correct, "/", len(MAP_40_ANSWERS), "test cases") 25 | --------------------------------------------------------------------------------