{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "**Make sure you run this at the begining**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import sys\n", "import math\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Append template path to sys path\n", "sys.path.append(os.getcwd() + \"/template\") " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from utils.load_data import load_data\n", "from utils.visualize_tsp import plotTSP\n", "\n", "from tsp import TSP_Bench_ONE\n", "from tsp import TSP_Bench_PATH\n", "from tsp import TSP_Bench_ALL" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Workshop Starts Here" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"TSP\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"solutions\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Get familiar with your dataset" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are problems at different levels. **3 simple, 2 medium, 1 difficult**." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "for root, _, files in os.walk('./template/data'):\n", " if(files):\n", " for f in files:\n", " print(str(root) + \"/\" + f)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ulysses16 = np.array(load_data(\"./template/data/simple/ulysses16.tsp\"))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ulysses16[:]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "plt.scatter(ulysses16[:, 0], ulysses16[:, 1])\n", "for i in range(0, 16):\n", " plt.annotate(i, (ulysses16[i, 0], ulysses16[i, 1]+0.5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Naive Solution: In Order" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "simple_sequence = list(range(0, 16))\n", "print(simple_sequence)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plotTSP([simple_sequence], ulysses16, num_iters=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Naive Solution: Random Permutation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "random_permutation = np.random.permutation(16).tolist()\n", "print(random_permutation)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plotTSP([random_permutation], ulysses16, num_iters=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Best Solution" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "best_ulysses16 = [0, 13, 12, 11, 6, 5, 14, 4, 10, 8, 9, 15, 2, 1, 3, 7]\n", "plotTSP([best_ulysses16], ulysses16, num_iters=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calculate Fitness (Sum of all Distances)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def dist(node_0, node_1, coords):\n", " \"\"\"\n", " Euclidean distance between two nodes.\n", " \"\"\"\n", " coord_0, coord_1 = coords[node_0], coords[node_1]\n", " return math.sqrt((coord_0[0] - coord_1[0]) ** 2 + (coord_0[1] - coord_1[1]) ** 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Coordinate of City 0:\", ulysses16[0])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Coordinate of City 1:\", ulysses16[1])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "print(\"Distance Between\", dist(0, 1, ulysses16))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def fitness(solution, coords):\n", " N = len(coords)\n", " cur_fit = 0\n", " if(len(solution) != N):\n", " return math.inf\n", " for i in range(len(solution)):\n", " cur_fit += dist(solution[i % N], solution[(i + 1) % N], coords)\n", " return cur_fit" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "print (\"Order Fitness:\\t\", fitness(simple_sequence, ulysses16))\n", "print (\"Random Fitness:\\t\", fitness(random_permutation, ulysses16))\n", "print (\"Best Fitness:\\t\", fitness(best_ulysses16, ulysses16))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Naive Random Model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math\n", "import random\n", "from model.base_model import Model\n", "import numpy as np\n", "\n", "class MyRandomModel(Model):\n", " def __init__(self):\n", " super().__init__()\n", "\n", " def init(self, nodes):\n", " \"\"\"\n", " Put your initialization here.\n", " \"\"\"\n", " super().init(nodes)\n", "\n", " def fit(self, max_it=1000):\n", " \"\"\"\n", " Put your iteration process here.\n", " \"\"\"\n", " random_solutions = []\n", " for i in range(0, max_it):\n", " solution = np.random.permutation(self.N).tolist()\n", " random_solutions.append(solution)\n", " self.fitness_list.append(self.fitness(solution))\n", "\n", " self.best_solution = random_solutions[self.fitness_list.index(min(self.fitness_list))]\n", " return self.best_solution, self.fitness_list" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "tsp_file = './template/data/simple/ulysses16.tsp'" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyRandomModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.plot(fitness_list, 'o-')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Uniform Cost Search" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math\n", "import random\n", "from model.base_model import Model\n", "\n", "class MyUCSModel(Model):\n", " def __init__(self):\n", " super().__init__()\n", "\n", " def init(self, nodes):\n", " \"\"\"\n", " Put your initialization here.\n", " \"\"\"\n", " super().init(nodes)\n", "\n", " def getMST(self, node):\n", " MST = []\n", " distances = []\n", " for i in range(0, self.N):\n", " if i != node:\n", " MST.append(i)\n", " distances.append(self.dist(node, i))\n", " return [x for _,x in sorted(zip(distances, MST))]\n", "\n", " def fit(self, max_it=1000):\n", " \"\"\"\n", " Put your iteration process here.\n", " \"\"\"\n", "\n", " UCS_solutions = []\n", " UCS_losses = []\n", " \n", " for i in range(0, self.N):\n", " solution = [i]\n", " UCS_solutions.append(solution)\n", " UCS_losses.append(math.inf)\n", " \n", " MSTs = []\n", " for i in range(0, self.N):\n", " MSTs.append([-1] * self.N)\n", "\n", " # Breadth First: Set each city as starting point, then go to next city simultaneously\n", " min_loss = math.inf\n", " while(len(UCS_solutions[ UCS_losses.index(min(UCS_losses)) ]) != self.N):\n", " unvisited_list = list(range(0, self.N))\n", " min_loss = min(UCS_losses)\n", " # For each search path\n", " for i in range(0, self.N):\n", " if UCS_losses[i] == min_loss:\n", " cur_city = UCS_solutions[i][-1]\n", " unvisited_list = list( set(range(0, self.N)) - set(UCS_solutions[i]) )\n", " if MSTs[cur_city][0] == -1:\n", " MST = self.getMST(cur_city)\n", " MSTs[cur_city] = MST\n", "\n", " for j in MSTs[cur_city]:\n", " if(j in unvisited_list):\n", " UCS_solutions[i].append(j)\n", "\n", " N = len(UCS_solutions[i])\n", " cur_fit = 0\n", " for k in range(len(UCS_solutions[i])):\n", " coord_0, coord_1 = self.coords[UCS_solutions[i][k % N]], self.coords[UCS_solutions[i][(k + 1) % N]]\n", " cur_fit += math.sqrt((coord_0[0] - coord_1[0]) ** 2 + (coord_0[1] - coord_1[1]) ** 2)\n", " UCS_losses[i] = cur_fit\n", " # if(UCS_losses[i] < min_loss):\n", " # min_loss = UCS_losses[i]\n", " break\n", " self.best_solution = UCS_solutions[ UCS_losses.index(min(UCS_losses)) ]\n", " self.fitness_list.append(self.fitness(self.best_solution))\n", "\n", " return self.best_solution, self.fitness_list" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tsp_file = './template/data/simple/ulysses16.tsp'" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyUCSModel)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A star" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Heuristic" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math\n", "import random\n", "from model.base_model import Model\n", "\n", "class MyASModel(Model):\n", " def __init__(self):\n", " super().__init__()\n", "\n", " def init(self, nodes):\n", " \"\"\"\n", " Put your initialization here.\n", " \"\"\"\n", " super().init(nodes)\n", "\n", " def getMST(self, node):\n", " MST = []\n", " distances = []\n", " for i in range(0, self.N):\n", " if i != node:\n", " MST.append(i)\n", " distances.append(self.dist(node, i))\n", " return [x for _,x in sorted(zip(distances, MST))]\n", "\n", " def fit(self, max_it=1000):\n", " \"\"\"\n", " Put your iteration process here.\n", " \"\"\"\n", "\n", " MST_solutions = []\n", " \n", " for i in range(0, self.N):\n", " solution = [i]\n", " MST_solutions.append(solution)\n", " \n", " # Breadth First: Set each city as starting point, then go to next city simultaneously\n", " for step in range(0, self.N - 1):\n", " # print(\"[step]\", step)\n", " unvisited_list = list(range(0, self.N))\n", " # For each search path\n", " for i in range(0, self.N):\n", " cur_city = MST_solutions[i][-1]\n", " unvisited_list = list( set(range(0, self.N)) - set(MST_solutions[i]) )\n", " closest_neighbour = -1\n", " min_f = math.inf\n", " \n", " for j in unvisited_list:\n", " g = self.dist(cur_city, j)\n", " sub_unvisited_list = unvisited_list.copy()\n", " sub_unvisited_list.remove(j)\n", "\n", " sub_cur_city = self.getMST(j)[0]\n", " h = 0\n", " while len(sub_unvisited_list) > 0:\n", " if(len(unvisited_list) == 2):\n", " break\n", " else:\n", " for k in self.getMST(sub_cur_city):\n", " if k in sub_unvisited_list:\n", " h = h + self.dist(sub_cur_city, k)\n", " sub_cur_city = k\n", " sub_unvisited_list.remove(k)\n", " break\n", " # Get f(x) = g(x) + h(x)\n", "\n", " f = g + h\n", " if(f < min_f):\n", " closest_neighbour = j\n", " min_f = f\n", " MST_solutions[i].append(closest_neighbour)\n", "\n", " for i in range(0, self.N):\n", " self.fitness_list.append(self.fitness(MST_solutions[i]))\n", " \n", " self.best_solution = MST_solutions[ self.fitness_list.index(min(self.fitness_list)) ]\n", "\n", " return self.best_solution, self.fitness_list\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tsp_file = './template/data/simple/ulysses16.tsp'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyASModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "plt.plot(fitness_list, 'o-')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Hill Climbing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Heuristic Iteration" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math\n", "import random\n", "import numpy as np\n", "from model.base_model import Model\n", "\n", "class MyHillClimbModel(Model):\n", "\n", " def __init__(self):\n", " super().__init__()\n", "\n", " def init(self, nodes):\n", " \"\"\"\n", " Put your initialization here.\n", " \"\"\"\n", " super().init(nodes)\n", "\n", " def random_tour(self):\n", " return np.random.permutation(self.N).tolist()\n", "\n", " def all_pairs(self, size, shuffle=random.shuffle):\n", " r1 = list(range(size))\n", " r2 = list(range(size))\n", " if shuffle:\n", " shuffle(r1)\n", " shuffle(r2)\n", " for i in r1:\n", " for j in r2:\n", " yield (i,j)\n", "\n", " def move_operator(self, tour):\n", " '''generator to return all possible variations\n", " where the section between two cities are swapped'''\n", " for i,j in self.all_pairs(len(tour)):\n", " if i != j:\n", " copy=tour[:]\n", " if i < j:\n", " copy[i:j+1]=reversed(tour[i:j+1])\n", " else:\n", " copy[i+1:]=reversed(tour[:j])\n", " copy[:j]=reversed(tour[i+1:])\n", " if copy != tour: # no point returning the same tour\n", " yield copy\n", "\n", " def fit(self, max_it=100):\n", " \"\"\"\n", " Put your iteration process here.\n", " \"\"\"\n", "\n", " self.best_solution = self.random_tour()\n", " best_score = -self.fitness(self.best_solution)\n", "\n", " num_evaluations = 0\n", "\n", " while num_evaluations < max_it:\n", " # examine moves around our current position\n", " move_made = False\n", " for next_solution in self.move_operator(self.best_solution):\n", " if num_evaluations >= max_it:\n", " print(\"Max iteration reached:\", max_it)\n", " break\n", "\n", " # see if this move is better than the current\n", " next_score = -self.fitness(next_solution)\n", " num_evaluations += 1\n", " if next_score > best_score:\n", " self.best_solution = next_solution\n", " self.fitness_list.append(self.fitness(self.best_solution))\n", " best_score=next_score\n", " move_made=True\n", " break # depth first search\n", "\n", " if not move_made:\n", " break # we couldn't find a better move (must be at a local maximum)\n", "\n", " return self.best_solution, self.fitness_list" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tsp_file = './template/data/simple/ulysses16.tsp'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyHillClimbModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.plot(fitness_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Your Smart Model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math\n", "import random\n", "from model.base_model import Model\n", "\n", "class MyModel(Model):\n", " def __init__(self):\n", " super().__init__()\n", "\n", " def init(self, nodes):\n", " \"\"\"\n", " Put your initialization here.\n", " \"\"\"\n", " super().init(nodes)\n", "\n", " self.log(\"Nothing to initialize in your model now\")\n", "\n", " def fit(self, max_it=1000):\n", " \"\"\"\n", " Put your iteration process here.\n", " \"\"\"\n", " self.best_solution = np.random.permutation(self.N).tolist()\n", " self.fitness_list.append(self.fitness(self.best_solution))\n", "\n", " return self.best_solution, self.fitness_list" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test your Model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tsp_problem = './template/data/simple/ulysses16.tsp'" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyModel)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test All Dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tsp_path = './template'\n", "for root, _, files in os.walk(tsp_path + '/data'):\n", " if(files):\n", " for f in files:\n", " print(str(root) + \"/\" + f)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def plot_results(best_solutions, times, title):\n", " fig = plt.figure()\n", " nodes = [len(s) for s in best_solutions]\n", " data = np.array([[node, time] for node, time in sorted(zip(nodes, times))])\n", " plt.plot(data[:, 0], data[:, 1], 'o-')\n", " fig.suptitle(title, fontsize=20)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "print(\"Random Search\")\n", "best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyRandomModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot_results(best_solutions, times, \"Random Model\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "print(\"Uniform Cost Search\")\n", "best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyUCSModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot_results(best_solutions, times, \"Uniform Cost Search\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"A Star Search\")\n", "best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyASModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot_results(best_solutions, times, \"A Star Search\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "print(\"Hill-Climbing Search\")\n", "best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyHillClimbModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "plot_results(best_solutions, times, \"Hill-Climbing Search\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Conclusions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Simple\n", "# ulysses16: 77.12 (UCS-BFS), 77.02 (A-Star)\n", "# att48: 39236 (UCS-BFS), 47853 (A-Star)\n", "# st70: 761 (UCS-BFS), time-out (A-Star)\n", "\n", "# Medium\n", "# a280: 3088 (UCS-BFS), time-out (A-Star)\n", "# pcb442: 58952 (UCS-BFS), time-out (A-Star)\n", "\n", "# Difficult\n", "# dsj1000: time-out (UCS-BFS), 542,620,572 (Hill-Climbing)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

1. UCS is the slowest one, and gets the same result as BFS, DFS, DP

\n", "

1. A-Star can only solve problems with number of cities < 50.

\n", "

2. Hill-Climbing gets different results every time (Heuristic).

\n", "

3. Hill-Climbing is the fastest one till now. (faster than Dynamic Programming, but worse results).

" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }