2020-10-01 16:03:04 +00:00
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": true,
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
2020-10-19 15:25:30 +00:00
"## First Lab\n",
2020-10-12 09:45:37 +00:00
"\n",
"What we are going to do today:\n",
"- read TSP data\n",
"- define euclidean distance function\n",
"- define a ProblemInstance python class \n",
"- store nodes in an instance of the class defined before\n",
"- plot raw data\n",
"- generate naive solution \n",
"- check if the solution is valid\n",
"- evaluate solution!#\n",
"\n",
"NOTE: I've marked all the code that you will have to fill with a `# TODO` comment\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
2020-11-01 18:24:26 +00:00
"This cell below is simply importing some useful stuff for later"
2020-10-12 09:45:37 +00:00
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import glob\n",
"import numpy as np\n",
"from matplotlib import pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Read TSP data\n",
"In this Cup you will have to deal with predefined set of problems. These problems are located in the `problems` folder.\n",
"\n",
"First lets get list them out"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ch130.tsp\n",
"d198.tsp\n",
"eil76.tsp\n",
"fl1577.tsp\n",
"kroA100.tsp\n",
"lin318.tsp\n",
"pcb442.tsp\n",
"pr439.tsp\n",
"rat783.tsp\n",
"u1060.tsp\n"
]
}
],
"source": [
"problems = glob.glob('../problems/*.tsp')\n",
2020-11-01 18:24:26 +00:00
"# example_problem = [\"../problems/eil76.tsp\"]\n",
2020-10-12 09:45:37 +00:00
"for prob in problems:\n",
" print(prob[12:])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Checking by hand if all of the 10 problems are in the folder would be a waste of time so we can write a line of code just to check if they are all there"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"print(np.all([n[12:] in ['fl1577.tsp','pr439.tsp','ch130.tsp','rat783.tsp','d198.tsp', 'kroA100.tsp','u1060.tsp','lin318.tsp','eil76.tsp','pcb442.tsp'] for n in problems]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### File format\n",
"All the problems are stored in a `.tsp` (this file is actually a renamed `.txt` file, so you could open them with your favorite text editor)\n",
"\n",
"As we will see in a bit all the problems files are composed of different sections:\n",
"* `NAME`: the shortned name of the problem\n",
"* `COMMENT`: a comment area that can contain the full name of the problem\n",
"* `TYPE`: this defines the type of problem at hand, in our case is always TSP\n",
"* `DIMENSION`: this states the problem dimension\n",
"* `EDGE_WEIGHT_TYPE`: this section states the types of weights applied to edges, in our case it is always EUC_2D or the weights are giveng using the euclidean distance in 2 dimension\n",
"* `BEST_KNOWN`: this states the best known result obtained, note that as the Prof said, it is unlikely to get a better performance than this\n",
"* `NODE_COORD_SECTION`: finally we have the section that states the triplets that defines the problems points. These triplets are (point_number, x,y).\n",
"\n",
"Now that we know all of that, lets print the content of a single problem"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['NAME : eil76', 'COMMENT : 76-city problem (Christofides/Eilon)', 'TYPE : TSP', 'DIMENSION : 76', 'EDGE_WEIGHT_TYPE : EUC_2D', 'BEST_KNOWN : 538', 'NODE_COORD_SECTION', '1 22 22', '2 36 26', '3 21 45', '4 45 35', '5 55 20', '6 33 34', '7 50 50', '8 55 45', '9 26 59', '10 40 66', '11 55 65', '12 35 51', '13 62 35', '14 62 57', '15 62 24', '16 21 36', '17 33 44', '18 9 56', '19 62 48', '20 66 14', '21 44 13', '22 26 13', '23 11 28', '24 7 43', '25 17 64', '26 41 46', '27 55 34', '28 35 16', '29 52 26', '30 43 26', '31 31 76', '32 22 53', '33 26 29', '34 50 40', '35 55 50', '36 54 10', '37 60 15', '38 47 66', '39 30 60', '40 30 50', '41 12 17', '42 15 14', '43 16 19', '44 21 48', '45 50 30', '46 51 42', '47 50 15', '48 48 21', '49 12 38', '50 15 56', '51 29 39', '52 54 38', '53 55 57', '54 67 41', '55 10 70', '56 6 25', '57 65 27', '58 40 60', '59 70 64', '60 64 4', '61 36 6', '62 30 20', '63 20 30', '64 15 5', '65 50 70', '66 57 72', '67 45 42', '68 38 33', '69 50 4', '70 66 8', '71 59 5', '72 35 60', '73 27 24', '74 40 20', '75 40 37', '76 40 40', 'EOF']\n"
]
}
],
"source": [
"example_problem = \"../problems/eil76.tsp\"\n",
"with open(example_problem,\"r\") as exprob:\n",
" print(exprob.read().splitlines())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Euclidean Distance\n",
"Since all of our problems are using the euclidean distance between points for the edges weights.\n",
"We will now define a function that computes the euclidean distance. This distance will also be used to build the distance matrix"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def distance_euc(point_i, point_j): # TODO\n",
" rounding = 0\n",
2020-11-01 18:24:26 +00:00
" x_i = point_i[0]\n",
" y_i = point_i[1]\n",
" x_j, y_j = point_j[0], point_j[1]\n",
" distance = np.sqrt((x_i - x_j) ** 2 + (y_i- y_j) ** 2)\n",
2020-10-12 09:45:37 +00:00
" return round(distance, rounding)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's test it"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"4.0"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"point_1 = (2, 2)\n",
"point_2 = (5, 5)\n",
"distance_euc(point_1, point_2)\n",
"# Expected output is 4.0 with rounding to 0 "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Reading and storing the data\n",
"We will now define a Class called `ProblemInstance`\n",
"\n",
"in the Constructor of the class (`__init__()`method of a class in Python) you will have to implement the code for:\n",
"* reading the raw data\n",
"* store the metadata\n",
"* read all the point and store them\n",
"* code the method that creates the distance matrix between points\n",
"* \\[optional\\] check if the problem loaded has an optimal and in that case store the optimal solution\n",
"* \\[optional\\] code the plotting method\n"
]
},
{
"cell_type": "code",
2020-11-01 18:24:26 +00:00
"execution_count": null,
2020-10-12 09:45:37 +00:00
"metadata": {},
"outputs": [],
"source": [
"from src.utils import distance_euc\n",
"\n",
"class ProblemInstance:\n",
"\n",
" def __init__(self, name_tsp):\n",
" self.exist_opt = False\n",
" self.optimal_tour = None\n",
" self.dist_matrix = None\n",
" \n",
" # read raw data \n",
" # TODO\n",
2020-11-01 18:24:26 +00:00
" with open(name_tsp) as f_o:\n",
" data= f_o.read()\n",
" self.lines = data.splitlines()\n",
" \n",
"# file_object = open(name_tsp)\n",
"# data = file_object.read()\n",
"# file_object.close()\n",
"# self.lines = data.splitlines()\n",
2020-10-12 09:45:37 +00:00
"\n",
" # store metadata set information \n",
" # TODO\n",
2020-11-01 18:24:26 +00:00
" self.name = self.lines[0].split(' ')[2]\n",
" # here we expect the name of the problem\n",
" self.nPoints = np.int(self.lines[3].split(' ')[2])\n",
" self.best_sol = np.float(self.lines[5].split(' ')[2])\n",
" # here the lenght of the best solution\n",
2020-10-12 09:45:37 +00:00
" \n",
" # read all data points and store them \n",
" # TODO\n",
" self.points = np.zeros((self.nPoints, 3)) # this is the structure where we will store the pts data \n",
2020-11-01 18:24:26 +00:00
" for i in range(self.nPoints):\n",
" line_i = self.line[7 + i].split(' ')\n",
" self.points[i, 0] = int(line_i[0])\n",
" self.points[i, 1] = line_i[1]\n",
" self.points[i, 2] = line_i[2]\n",
2020-10-12 09:45:37 +00:00
" \n",
" self.create_dist_matrix()\n",
" \n",
" # TODO [optional]\n",
" # if the problem is one with a optimal solution, that solution is loaded\n",
" if name_tsp in [\"../problems/eil76.tsp\", \"../problems/kroA100.tsp\"]:\n",
" self.exist_opt = True\n",
2020-11-01 18:24:26 +00:00
" file_object = open(name_tsp.replace(\".tsp\", \".opt.tour\"))\n",
" data = file_object.read()\n",
" file_object.close()\n",
" lines = data.splitlines()\n",
2020-10-12 09:45:37 +00:00
"\n",
" # read all data points and store them\n",
" self.optimal_tour = np.zeros(self.nPoints, dtype=np.int)\n",
2020-11-01 18:24:26 +00:00
" for i in range(self.nPoints):\n",
" line_i = lines[5 + i].split(' ')\n",
" self.optimal_tour[i] = int(line_i[0]) - 1\n",
2020-10-12 09:45:37 +00:00
"\n",
" def print_info(self):\n",
" print(\"\\n#############################\\n\")\n",
" print('name: ' + self.name)\n",
" print('nPoints: ' + str(self.nPoints))\n",
" print('best_sol: ' + str(self.best_sol))\n",
" print('exist optimal: ' + str(self.exist_opt))\n",
"\n",
" def plot_data(self,show_numbers=False): # todo [optional]\n",
" plt.figure(figsize=(8, 8))\n",
" plt.title(self.name)\n",
" plt.scatter(self.points[:, 1], self.points[:, 2])\n",
" if show_numbers:\n",
" for i, txt in enumerate(np.arange(self.nPoints)): # tour_found[:-1]\n",
" plt.annotate(txt, (self.points[i, 1], self.points[i, 2]))\n",
" plt.show()\n",
"\n",
" def create_dist_matrix(self): # TODO\n",
2020-11-01 18:24:26 +00:00
" self.dist_matrix = np.zeros((self.nPoints, self.nPoints))\n",
2020-10-12 09:45:37 +00:00
" \n",
2020-11-01 18:24:26 +00:00
" for i in range(self.nPoints):\n",
" for j in range(i, self.nPoints):\n",
" self.dist_matrix[i, j] = distance_euc(self.points[i][1:3], self.points[j][1:3])\n",
" self.dist_matrix += self.dist_matrix.T\n"
2020-10-12 09:45:37 +00:00
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"------------------------\n",
"Now we can test our Class with an example problem"
]
},
{
"cell_type": "code",
2020-11-01 18:24:26 +00:00
"execution_count": null,
2020-10-12 09:45:37 +00:00
"metadata": {},
"outputs": [],
"source": [
"example_problem = \"../problems/eil76.tsp\"\n",
"p_inst=ProblemInstance(example_problem)"
]
},
{
"cell_type": "code",
2020-11-01 18:24:26 +00:00
"execution_count": null,
2020-10-12 09:45:37 +00:00
"metadata": {},
2020-11-01 18:24:26 +00:00
"outputs": [],
2020-10-12 09:45:37 +00:00
"source": [
"p_inst.print_info()\n",
"p_inst.plot_data()\n",
"#Expected output\n",
"\"\"\"\n",
"#############################\n",
"\n",
"name: eil76\n",
"nPoints: 76\n",
"best_sol: 538.0\n",
"exist optimal: True\n",
"\n",
"\"\"\""
]
},
{
"cell_type": "code",
2020-11-01 18:24:26 +00:00
"execution_count": null,
2020-10-12 09:45:37 +00:00
"metadata": {
"scrolled": false
},
2020-11-01 18:24:26 +00:00
"outputs": [],
2020-10-12 09:45:37 +00:00
"source": [
"p_inst.plot_data(show_numbers=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"-------------\n",
"### Random solver \n",
"Now we will code the random solver and test it with a class called `SolverTSP` that takes the solvers and the problem instance and act as a framework to compute the solution and gives us some additional information.\n",
"We will also need to code the `evaluate_solution` method of the the `SolverTSP` class"
]
},
{
"cell_type": "code",
2020-11-01 18:24:26 +00:00
"execution_count": null,
2020-10-12 09:45:37 +00:00
"metadata": {},
"outputs": [],
"source": [
"def random_method(instance_): # TODO\n",
" return solution\n",
"available_methods = {\"random\": random_method} # this is here because the SolverTSP will check for the available methods"
]
},
{
"cell_type": "code",
2020-11-01 18:24:26 +00:00
"execution_count": null,
2020-10-12 09:45:37 +00:00
"metadata": {},
"outputs": [],
"source": [
"from time import time as t\n",
"\n",
"class SolverTSP:\n",
" def __init__(self, algorithm_name, problem_instance):\n",
" self.duration = np.inf\n",
" self.found_length = np.inf\n",
" self.algorithm_name = algorithm_name\n",
" self.name_method = \"initialized with \" + algorithm_name\n",
" self.solved = False\n",
" self.problem_instance = problem_instance\n",
" self.solution = None\n",
"\n",
" def compute_solution(self, verbose=True, return_value=True):\n",
" self.solved = False\n",
" if verbose:\n",
" print(f\"### solving with {self.algorithm_name} ####\")\n",
" start_time = t()\n",
" self.solution = available_methods[self.algorithm_name](self.problem_instance)\n",
" assert self.check_if_solution_is_valid(self.solution), \"Error the solution is not valid\"\n",
" end_time = t()\n",
" self.duration = np.around(end_time - start_time, 3)\n",
" if verbose:\n",
" print(f\"### solved ####\")\n",
" self.solved = True\n",
" self.evaluate_solution()\n",
" self._gap()\n",
" if return_value:\n",
" return self.solution\n",
"\n",
" def plot_solution(self):\n",
" assert self.solved, \"You can't plot the solution, you need to compute it first!\"\n",
" plt.figure(figsize=(8, 8))\n",
" self._gap()\n",
" plt.title(f\"{self.problem_instance.name} solved with {self.name_method} solver, gap {self.gap}\")\n",
" ordered_points = self.problem_instance.points[self.solution]\n",
" plt.plot(ordered_points[:, 1], ordered_points[:, 2], 'b-')\n",
" plt.show()\n",
"\n",
" def check_if_solution_is_valid(self, solution):\n",
" rights_values = np.sum([self.check_validation(i, solution) for i in np.arange(self.problem_instance.nPoints)])\n",
" if rights_values == self.problem_instance.nPoints:\n",
" return True\n",
" else:\n",
" return False \n",
" def check_validation(self, node , solution):\n",
" if np.sum(solution == node) == 1:\n",
" return 1\n",
" else:\n",
" return 0\n",
"\n",
" def evaluate_solution(self, return_value=False):\n",
" total_length = 0\n",
" from_node = self.solution[0] # starting_node\n",
" # TODO\n",
" # [...] compute total_lenght of the solution \n",
" self.found_length = total_length\n",
" if return_value:\n",
" return total_length\n",
"\n",
" def _gap(self):\n",
" self.evaluate_solution(return_value=False)\n",
" self.gap = np.round(\n",
" ((self.found_length - self.problem_instance.best_sol) / self.problem_instance.best_sol) * 100, 2)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"----------------------------\n",
"Now we will test our code"
]
},
{
"cell_type": "code",
2020-11-01 18:24:26 +00:00
"execution_count": null,
2020-10-12 09:45:37 +00:00
"metadata": {},
2020-11-01 18:24:26 +00:00
"outputs": [],
2020-10-12 09:45:37 +00:00
"source": [
"solver_name=\"random\"\n",
"# here I'm repeating this two lines just to remind you which problem we are using\n",
"example_problem = \"../problems/eil76.tsp\"\n",
"p_inst = ProblemInstance(example_problem)\n",
"\n",
"# TODO\n",
"# create an instance of SolverTSP\n",
"# compute a solution\n",
"# print the information as for the output\n",
"# plot the solution\n",
"\n",
"# this is the output expected and after that the solution's plot\n",
"\"\"\"\n",
"### solving with random ####\n",
"### solved ####\n",
"the total length for the solution found is 2424.0\n",
"while the optimal length is 538.0\n",
"the gap is 350.56%\n",
"the solution is found in 0.0 seconds\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"--------------------\n",
"Finally since our example problem has an optimal solution we can plot it"
]
},
{
"cell_type": "code",
2020-11-01 18:24:26 +00:00
"execution_count": null,
2020-10-12 09:45:37 +00:00
"metadata": {},
2020-11-01 18:24:26 +00:00
"outputs": [],
2020-10-12 09:45:37 +00:00
"source": [
"solver = SolverTSP(\"optimal\", p_inst)\n",
"solver.solved = True\n",
"solver.solution = np.concatenate([p_inst.optimal_tour, [p_inst.optimal_tour[0]]])\n",
"solver.plot_solution()"
2020-10-01 16:03:04 +00:00
]
}
],
"metadata": {
"kernelspec": {
2020-10-12 09:45:37 +00:00
"display_name": "PyCharm (AI2020BsC)",
2020-10-01 16:03:04 +00:00
"language": "python",
2020-10-12 09:45:37 +00:00
"name": "pycharm-61970693"
2020-10-01 16:03:04 +00:00
},
"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.3"
}
},
"nbformat": 4,
"nbformat_minor": 1
2020-11-01 18:24:26 +00:00
}