{ "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 hard**." ] }, { "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", " 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": {}, "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": {}, "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, MyRandomModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.plot(fitness_list, 'o-')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Genetic Algorithm" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math\n", "import random\n", "from model.base_model import Model\n", "from random import randint, sample\n", "\n", "class Gene: # City\n", " def __init__(self, name, lat, lng):\n", " self.name = name\n", " self.lat = lat\n", " self.lng = lng\n", "\n", " def get_distance_to(self, dest):\n", " return math.sqrt( (self.lng - dest.lng) ** 2 + (self.lat - dest.lat) ** 2 )\n", "\n", "class Individual: # Route: possible solution to TSP\n", " def __init__(self, genes):\n", " assert(len(genes) > 3)\n", " self.genes = genes\n", " self.__reset_params()\n", "\n", " def swap(self, gene_1, gene_2):\n", " self.genes[0]\n", " a, b = self.genes.index(gene_1), self.genes.index(gene_2)\n", " self.genes[b], self.genes[a] = self.genes[a], self.genes[b]\n", " self.__reset_params()\n", "\n", " def add(self, gene):\n", " self.genes.append(gene)\n", " self.__reset_params()\n", "\n", " @property\n", " def fitness(self):\n", " if self.__fitness == 0:\n", " self.__fitness = 1 / self.travel_cost # Normalize travel cost\n", " return self.__fitness\n", "\n", " @property\n", " def travel_cost(self): # Get total travelling cost\n", " if self.__travel_cost == 0:\n", " for i in range(len(self.genes)):\n", " origin = self.genes[i]\n", " if i == len(self.genes) - 1:\n", " dest = self.genes[0]\n", " else:\n", " dest = self.genes[i+1]\n", "\n", " self.__travel_cost += origin.get_distance_to(dest)\n", "\n", " return self.__travel_cost\n", "\n", " def __reset_params(self):\n", " self.__travel_cost = 0\n", " self.__fitness = 0\n", "\n", "class Population: # Population of individuals\n", " def __init__(self, individuals):\n", " self.individuals = individuals\n", "\n", " @staticmethod\n", " def gen_individuals(sz, genes):\n", " individuals = []\n", " for _ in range(sz):\n", " individuals.append(Individual(sample(genes, len(genes))))\n", " return Population(individuals)\n", "\n", " def add(self, route):\n", " self.individuals.append(route)\n", "\n", " def rmv(self, route):\n", " self.individuals.remove(route)\n", "\n", " def get_fittest(self):\n", " fittest = self.individuals[0]\n", " for route in self.individuals:\n", " if route.fitness > fittest.fitness:\n", " fittest = route\n", "\n", " return fittest\n", "\n", "class MyGAModel(Model):\n", " def __init__(self):\n", " super().__init__()\n", " self.iteration = 0\n", "\n", " def init(self, nodes):\n", " super().init(nodes)\n", "\n", " def evolve(self, pop, tourn_size, mut_rate):\n", " new_generation = Population([])\n", " pop_size = len(pop.individuals)\n", " elitism_num = pop_size // 4\n", "\n", " # Elitism\n", " for _ in range(elitism_num):\n", " fittest = pop.get_fittest()\n", " new_generation.add(fittest)\n", " pop.rmv(fittest)\n", "\n", " # Crossover\n", " for _ in range(elitism_num, pop_size):\n", " parent_1 = self.selection(new_generation, tourn_size)\n", " parent_2 = self.selection(new_generation, tourn_size)\n", " child = self.crossover(parent_1, parent_2)\n", " new_generation.add(child)\n", "\n", " # Mutation\n", " for i in range(elitism_num, pop_size):\n", " self.mutate(new_generation.individuals[i], mut_rate)\n", "\n", " return new_generation\n", "\n", " def crossover(self, parent_1, parent_2):\n", " def fill_with_parent1_genes(child, parent, genes_n):\n", " start_at = randint(0, len(parent.genes)-genes_n-1)\n", " finish_at = start_at + genes_n\n", " for i in range(start_at, finish_at):\n", " child.genes[i] = parent_1.genes[i]\n", "\n", " def fill_with_parent2_genes(child, parent):\n", " j = 0\n", " for i in range(0, len(parent.genes)):\n", " if child.genes[i] == None:\n", " while parent.genes[j] in child.genes:\n", " j += 1\n", " child.genes[i] = parent.genes[j]\n", " j += 1\n", "\n", " genes_n = len(parent_1.genes)\n", " child = Individual([None for _ in range(genes_n)])\n", " fill_with_parent1_genes(child, parent_1, genes_n // 2)\n", " fill_with_parent2_genes(child, parent_2)\n", "\n", " return child\n", "\n", " def mutate(self, individual, rate):\n", " for _ in range(len(individual.genes)):\n", " if random.random() < rate:\n", " sel_genes = sample(individual.genes, 2)\n", " individual.swap(sel_genes[0], sel_genes[1])\n", "\n", " def selection(self, population, competitors_n):\n", " return Population(sample(population.individuals, competitors_n)).get_fittest()\n", "\n", " def fit(self, max_it=100):\n", " \"\"\"\n", " Execute simulated annealing algorithm.\n", " \"\"\"\n", " pop_size = 1000\n", " mut_rate = 0.9\n", " tourn_size = 100\n", "\n", " genes = [Gene(num, city[0], city[1]) for num, city in enumerate(self.coords)]\n", " self.genes = genes\n", "\n", " population = Population.gen_individuals(pop_size, genes)\n", " \n", "\n", " for it in range(0, max_it):\n", " mut_rate = mut_rate * 0.95\n", " if mut_rate < 0.05:\n", " mut_rate = 0.05\n", " population = self.evolve(population, tourn_size, mut_rate)\n", " cost = population.get_fittest().travel_cost\n", "\n", " it += 1\n", " self.fitness_list.append(cost)\n", " print(\"[step] \", it, \" [mut] \", mut_rate, \" [best] \", self.fitness_list[self.fitness_list.index(min(self.fitness_list))])\n", "\n", " self.best_solution = [gene.name for gene in population.get_fittest().genes]\n", "\n", " return self.best_solution, self.fitness_list" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tsp_problem = \"./template/data/simple/ulysses16.tsp\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "print(\"Genetic Algorithm\")\n", "best_solution, fitness_list, time = TSP_Bench_ONE(tsp_problem, MyGAModel, timeout=300)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "plt.plot(fitness_list, 'o-')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plotTSP([best_solution], load_data(tsp_problem), num_iters=1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print (\"Best Fitness:\\t\", fitness(best_solution, ulysses16))\n", "print (\"Best Fitness:\\t\", fitness(best_ulysses16, ulysses16))" ] }, { "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": true }, "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": {}, "outputs": [], "source": [ "print(\"Random Search\")\n", "best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyRandomModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "plot_results(best_solutions, times, \"Random Model\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Genetic Algorithm\")\n", "best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyGAModel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot_results(best_solutions, times, \"Genetic Algorithm\")" ] }, { "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 }