diff --git a/python/pathfinding_demo.ipynb b/python/pathfinding_demo.ipynb index b4807d3..ead59df 100644 --- a/python/pathfinding_demo.ipynb +++ b/python/pathfinding_demo.ipynb @@ -1,141 +1,323 @@ { - "metadata": { - "kernelspec": { - "name": "python", - "display_name": "Python (Pyodide)", - "language": "python" + "cells": [ + { + "cell_type": "markdown", + "id": "16f8fedb-ac10-450c-b5c7-f820a985902d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" }, - "language_info": { - "codemirror_mode": { - "name": "python", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8" - } + "tags": [] + }, + "source": [ + "# Pathfinding demo\n", + "\n", + "## List of methods\n", + "\n", + "Non-exhaustive list of methods follows.\n", + "\n", + "$V$ is the set of all vertices; $E$ is the set of all edges; $|V|$ is the size of the set\n", + "\n", + "1. Depth-first search\n", + " - s\n", + "1. [Bellman-Ford algorithm](https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm)\n", + " - exhaustive method\n", + " - $O(|V|*|E|)$\n", + "2. Dijkstra\n", + "3. A*" + ] }, - "nbformat_minor": 5, - "nbformat": 4, - "cells": [ + { + "cell_type": "code", + "execution_count": 11, + "id": "fbdf9d2c-d050-4744-b559-abc71e550725", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#\n", + "# Imports\n", + "#\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from typing import Protocol, Optional" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "c704cf15-95fa-49c1-af1b-c99f7b5c8b95", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#\n", + "# Type and interfaces definition\n", + "#\n", + "\n", + "type Point2D = tuple[int, int] # tuple(x, y)\n", + "type Path = list[Point2D]\n", + "type ElapsedTime_ns = float # nanoseconds\n", + "type VisitedNodeCount = int\n", + "\n", + "class Map:\n", + " \"\"\"\n", + " 2D map consisting of cells with given cost\n", + " \"\"\"\n", + " array: np.array\n", + "\n", + " def __init__(self, width: int, height: int) -> None:\n", + " assert width > 0\n", + " assert height > 0\n", + " self.array = np.zeros((width, height), dtype=np.float64)\n", + "\n", + " def Randomize(self, low: float = 0.0, high: float = 1.0) -> None:\n", + " self.array = np.random.uniform(low, high, self.array.shape)\n", + "\n", + " def GetCost(self, point: Point2D) -> float:\n", + " return self.array[point]\n", + " \n", + " def IsPointValid(self, point: Point2D) -> bool:\n", + " ...\n", + " \n", + " def GetNeighbours(self) -> list[Point2D]:\n", + " ...\n", + "\n", + " \n", + "\n", + "class PathFinder(Protocol):\n", + " def SetMap(m: Map) -> None:\n", + " ...\n", + "\n", + " def CalculatePath(start: Point2D, end: Point2D) -> Path:\n", + " \"\"\"\n", + " Calculate path on a given map.\n", + " Note: map must be set first using SetMap (or using constructor)\n", + " \"\"\"\n", + "\n", + " def GetStats() -> (ElapsedTime_ns, VisitedNodeCount):\n", + " \"\"\"\n", + " Return performance stats for the last calculation:\n", + " - elapsed time in nanoseconds,\n", + " - number of visited nodes during search\n", + " \"\"\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "043a1f1c-a7a7-4f24-b69c-c6c809830111", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#\n", + "# Drawing utilities\n", + "#\n", + "\n", + "class Visualizer:\n", + " _axes: Optional[plt.Axes]\n", + " _cmap: plt.Colormap\n", + " _cmap_counter: int\n", + " \n", + " def __init__(self):\n", + " self._axes = None\n", + " self._cmap = plt.get_cmap('tab10')\n", + " self._cmap_counter = 0\n", + " \n", + " def DrawMap(self, m: Map):\n", + " M, N = m.array.shape\n", + " _, ax = plt.subplots()\n", + " ax.imshow(m.array, cmap='terrain', origin='lower', interpolation='none')\n", + " self._axes = ax\n", + "\n", + " def DrawPath(self, path: Path, label: str = \"Path\"):\n", + " \"\"\"\n", + " Draw path on a map. Note that DrawMap has to be called first\n", + " \"\"\"\n", + " assert self._axes is not None, \"DrawMap must be called first\"\n", + " xs, ys = zip(*path)\n", + " color = self._cmap(self._cmap_counter)\n", + " self._cmap_counter += 1\n", + " self._axes.plot(xs, ys, 'o-', color=color, label=label)\n", + " self._axes.plot(xs[0], ys[0], 'o', color='lime', markersize=8) # starting point\n", + " self._axes.plot(xs[-1], ys[-1], 'o', color='magenta', markersize=8) # end point\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "859c64f4-e65c-4905-a775-c6f17542eac8", + "metadata": {}, + "outputs": [], + "source": [ + "#\n", + "# Method: depth-first search\n", + "#\n", + "\n", + "class DFS:\n", + "\n", + " name = \"Depth First Search\"\n", + " _map: Optional[Map]\n", + " \n", + " def __init__(self) -> None:\n", + " self._map = None\n", + " \n", + " def SetMap(self, m: Map) -> None:\n", + " self._map = m\n", + " \n", + " def CalculatePath(self, start: Point2D, end: Point2D) -> Path:\n", + " assert m is not None, \"SetMap must be called first\"\n", + " return [(0,0), (5,5), (6,6), (1,9)]\n", + "\n", + " def GetStats(self) -> (ElapsedTime_ns, VisitedNodeCount):\n", + " return 150.0, 42\n", + "\n", + "\n", + "class BFS:\n", + "\n", + " name = \"Breadth First Search\"\n", + " _map: Optional[Map]\n", + " \n", + " def __init__(self) -> None:\n", + " self._map = None\n", + " \n", + " def SetMap(self, m: Map) -> None:\n", + " self._map = m\n", + " \n", + " def CalculatePath(self, start: Point2D, end: Point2D) -> Path:\n", + " assert m is not None, \"SetMap must be called first\"\n", + " return [(0,0), (1,0), (2,0), (3,0)]\n", + "\n", + " def GetStats(self) -> (ElapsedTime_ns, VisitedNodeCount):\n", + " return 300.0, 21" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "ece3a6c8-aa1d-49a8-9f4c-06ebff72f991", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ { - "id": "16f8fedb-ac10-450c-b5c7-f820a985902d", - "cell_type": "markdown", - "source": "# Pathfinding demo\n\n## Optimal path finding\n\nNon-exhaustive list of methods follows. \n\n$V$ is the set of all vertices; $|V|$ is the size of the set\n\n1. [Bellman-Ford algorithm](https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm)\n - exhaustive method\n - $O(|V|*|E|)$\n2. Dijkstra\n3. A*", - "metadata": { - "tags": [], - "slideshow": { - "slide_type": "" - }, - "editable": true - } + "name": "stdout", + "output_type": "stream", + "text": [ + "Depth First Search : took 150.0 ns, visited 42 nodes\n", + "Breadth First Search : took 300.0 ns, visited 21 nodes\n" + ] }, { - "id": "fbdf9d2c-d050-4744-b559-abc71e550725", - "cell_type": "code", - "source": "#\n# Imports\n#\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom typing import Protocol", - "metadata": { - "trusted": true, - "tags": [], - "editable": true, - "slideshow": { - "slide_type": "" - } - }, - "outputs": [], - "execution_count": null - }, - { - "id": "c704cf15-95fa-49c1-af1b-c99f7b5c8b95", - "cell_type": "code", - "source": "#\n# Type and interfaces definition\n#\n\nPoint2D = tuple[int, int] # tuple(x, y)\nPath = list[Point2D]\n\nclass Map:\n \"\"\"\n 2D map consisting of cells with given cost\n \"\"\"\n array: np.array\n\n def __init__(self, width: int, height: int):\n assert width > 0\n assert height > 0\n self.array = np.zeros((width, height))\n\n def randomize(self, low: float = 0.0, high: float = 1.0):\n self.array = np.random.uniform(low, high, self.array.shape)\n\n \n\nclass PathFinder(Protocol):\n def SetMap(m: Map):\n ...\n def SetStartingPoint(p: Point2D):\n ...\n def SetEndPoint(p: Point2D):\n ...\n def CalculatePath() -> Path:\n \"\"\"\n Calculate path on a given map.\n Note: map must be set first using SetMap\n \"\"\"\n ...", - "metadata": { - "trusted": true, - "tags": [], - "editable": true, - "slideshow": { - "slide_type": "" - } - }, - "outputs": [], - "execution_count": 34 - }, - { - "id": "043a1f1c-a7a7-4f24-b69c-c6c809830111", - "cell_type": "code", - "source": "#\n# Drawing utilities\n#\n\nclass Visualizer:\n _axes: plt.Axes\n\n def draw_map(self, m: Map) -> tuple[plt.Figure, plt.Axes]:\n M, N = m.array.shape\n _, ax = plt.subplots()\n ax.imshow(m.array, cmap='terrain', origin='lower', interpolation='none')\n self._axes = ax\n\n def draw_path(self, path: Path, color='red', label: str = None):\n xs, ys = zip(*path) \n self._axes.plot(xs, ys, 'o-', color='blue', label='Path')\n self._axes.plot(xs[0], ys[0], 'o', color='lime', markersize=8) # start\n self._axes.plot(xs[-1], ys[-1], 'o', color='magenta', markersize=8) # goal\n ", - "metadata": { - "trusted": true, - "tags": [], - "editable": true, - "slideshow": { - "slide_type": "" - } - }, - "outputs": [], - "execution_count": 68 - }, - { - "id": "ece3a6c8-aa1d-49a8-9f4c-06ebff72f991", - "cell_type": "code", - "source": "m = Map(10, 10)\nm.randomize()\npath: Path = [(0,0), (5,5), (6,6), (1,9)]\nv = Visualizer()\nv.draw_map(m)\nprint(path)\nv.draw_path(path)", - "metadata": { - "trusted": true, - "tags": [], - "editable": true, - "slideshow": { - "slide_type": "" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": "[(0, 0), (5, 5), (6, 6), (1, 9)]\n" - }, - { - "output_type": "display_data", - "data": { - "text/plain": "
", - "image/png": "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" - }, - "metadata": {} - } - ], - "execution_count": 69 - }, - { - "id": "2ec9fb78-089d-4d51-9f16-087a04b4e8a4", - "cell_type": "code", - "source": "", - "metadata": { - "trusted": true, - "tags": [], - "editable": true, - "slideshow": { - "slide_type": "" - } - }, - "outputs": [], - "execution_count": null - }, - { - "id": "b050caaa-d9b5-4a22-8e6d-aaccfaa4fb1b", - "cell_type": "code", - "source": "", - "metadata": { - "trusted": true, - "tags": [], - "editable": true, - "slideshow": { - "slide_type": "" - } - }, - "outputs": [], - "execution_count": null + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" } - ] -} \ No newline at end of file + ], + "source": [ + "# Define the map and start/stop points\n", + "m = Map(10, 10)\n", + "m.Randomize()\n", + "starting_point: Point2D = (0,0)\n", + "end_point: Point2D = (9,9)\n", + "\n", + "#\n", + "# Calculate paths using various methods and visualize them\n", + "#\n", + "\n", + "path_finder_classes: list[PathFinder] = {\n", + " DFS, BFS\n", + "}\n", + "\n", + "v = Visualizer()\n", + "v.DrawMap(m)\n", + "\n", + "for pt in path_finder_classes:\n", + " path_finder = pt()\n", + " path_finder.SetMap(m)\n", + " path = path_finder.CalculatePath(starting_point, end_point)\n", + " elapsed_time, visited_nodes = path_finder.GetStats()\n", + " print(f\"{path_finder.name:22}: took {elapsed_time} ns, visited {visited_nodes} nodes\")\n", + " v.DrawPath(path)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ec9fb78-089d-4d51-9f16-087a04b4e8a4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b050caaa-d9b5-4a22-8e6d-aaccfaa4fb1b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}