├── .github └── ISSUE_TEMPLATE │ └── feature_request.md ├── LICENSE ├── README.md ├── ga_interactive.py ├── images ├── TSP_cost_plot.png └── TSP_route.png └── instances ├── att48.tsp ├── bays29.tsp ├── berlin52.tsp ├── burma14.tsp ├── gr24.tsp ├── kroA100.tsp ├── mona-lisa100K.tsp ├── st70.tsp ├── ulysses16.tsp └── ulysses22.tsp /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Renato Maynard Etchepare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TSP Genetic Algorithm in Python 2 | 3 | This repository contains a **Genetic Algorithm (GA)** implementation for solving the **Traveling Salesman Problem (TSP)**. It uses the following Python libraries: 4 | 5 | - [**tsplib95**](https://pypi.org/project/tsplib95/) to parse TSPLIB-compatible TSP instances. 6 | - [**DEAP**](https://deap.readthedocs.io/en/master/) to build and evolve the population. 7 | - [**matplotlib**](https://matplotlib.org/) (for plotting and optional animation). 8 | - [**numpy**](https://numpy.org/) (optional, for representing routes). 9 | - [**IPython**](https://ipython.org/) (needed if you want to see the animations inline in a Jupyter notebook). 10 | 11 | ## Example 12 | 13 | Below are the plots generated by this project: 14 | 15 | 16 | 17 | 21 | 25 | 26 |
18 | TSP Route
19 | Figure 1: TSP Route 20 |
22 | TSP cost plot
23 | Figure 2: TSP Cost vs Average plot 24 |
27 | 28 | 29 | ## Table of Contents 30 | 31 | 1. [Features](#features) 32 | 2. [Installation](#installation) 33 | 3. [Usage](#usage) 34 | 4. [Code Overview](#code-overview) 35 | - [Global Variables](#global-variables) 36 | - [AnimationTSP Class](#animationtsp-class) 37 | - [TSPInstance Class](#tspinstance-class) 38 | - [Genetic Algorithm Functions](#genetic-algorithm-functions) 39 | - [Helper Functions](#helper-functions) 40 | - [Main Function](#main-function) 41 | 5. [Notes on Plotting and Animations](#notes-on-plotting-and-animations) 42 | 6. [License](#license) 43 | 44 | --- 45 | 46 | ## Features 47 | 48 | - **Simple GA** and an **Advanced GA** mode: 49 | - *Simple GA* uses a basic approach for selection, crossover, and mutation. 50 | - *Advanced GA* includes larger population sizes, more generations, and can integrate local search steps (such as 2-Opt). 51 | - **Multiple mutation/perturbation** methods are provided, including: 52 | - Swapping two random cities. 53 | - Swapping neighbors. 54 | - Reversing sub-routes. 55 | - 2-Opt local improvement (commented out but can be activated). 56 | - **Visualization**: 57 | - Plots the best cost and average cost over generations. 58 | - Optionally animates the evolving route (in Jupyter/IPython environments). 59 | - **Interactive menu** instead of command-line arguments, making it more straightforward to use within a Python environment or when running the script directly. 60 | 61 | --- 62 | 63 | ## Installation 64 | 65 | 1. **Clone** or **download** this repository. 66 | 2. Install the required libraries: 67 | ```bash 68 | pip install tsplib95 deap matplotlib numpy ipython 69 | 70 | ## Usage 71 | 72 | 1. Run the script (e.g., python ga_interactive.py), or open it in an environment like Jupyter Notebook and run all cells. 73 | 2. You will see a **menu**: 74 | ``` 75 | ========================================= 76 | TSP GA Solver - Interactive Menu 77 | ========================================= 78 | Enable route plotting/animation? (`0=No`, `1=Yes`): 79 | Enter the file path of the TSP instance (e.g., `kroA100.tsp`): 80 | Enter random seed (integer), e.g. `42`: 81 | Choose GA version (`0=Simple`, `1=Advanced`): 82 | Use numpy arrays for individuals? (`0=No`, `1=Yes`): 83 | ``` 84 | 3. Enter the requested inputs: 85 | - Enable route plotting/animation? 86 | - `1` enables a line plot of best/average costs and, if using a Jupyter environment, an animation of the route. 87 | - `0` disables plotting and animation. 88 | 89 | - File path of the TSP instance: e.g., `kroA100.tsp` 90 | 91 | - Random seed: Any integer to make results reproducible (e.g., `73`). 92 | 93 | - GA version: 94 | - `0` runs the simple GA. 95 | - `1` runs the advanced GA (larger population, more generations, etc.). 96 | 97 | - Use numpy arrays: 98 | - `1` to store individuals (routes) as numpy.ndarray 99 | - `0` to store individuals as a standard Python list. 100 | 101 | 4. The algorithm will begin executing. You will see logs of each generation (especially in the advanced version). 102 | 103 | 5. After finishing, it will output: 104 | - Best route cost found. 105 | - Execution time (in seconds). 106 | - Plots of cost evolution (if plotting is enabled). 107 | - An animation of the best route per generation (in Jupyter/IPython) if you chose `1` in the first question and you have the environment to display it. 108 | 109 | ## Code Overview 110 | 111 | The code is structured as follows: 112 | 113 | ```python 114 | ga_interactive.py 115 | └── (entry point) main() 116 | ``` 117 | 118 | 119 | ### Global Variables 120 | 121 | - **INF**: A large constant (`9999999`) used to represent infinite distance in the distance matrix. 122 | - **dist_matrix**: A global 2D list that holds pairwise distances between cities. 123 | - **use_numpy**: A global integer set from the user’s input in the menu (`0` or `1`). If `1`, routes are stored as NumPy arrays. 124 | 125 | ### AnimationTSP Class 126 | 127 | ```python 128 | class AnimationTSP: 129 | ... 130 | ``` 131 | - **Purpose:** Creates and manages an animation of the TSP route as it evolves. 132 | 133 | - **Methods:** 134 | - **init(self, history, x_coords, y_coords, costs):** Receives the full solution history (each solution is a permutation of city indices), corresponding cost list, and the x/y coordinates of each city. 135 | - **init_animation(self):** Initializes the plot with city nodes. 136 | - **update_animation(self, frame):** Updates the route lines for each frame in the history. 137 | - **animate_routes(self):** Puts everything together and shows the animation in Jupyter using FuncAnimation. 138 | 139 | ### TSPInstance Class 140 | 141 | ```python 142 | class TSPInstance: 143 | ... 144 | ``` 145 | - **Purpose**: 146 | - Loads a TSP instance from a TSPLIB file. 147 | - Extracts node coordinates if they exist (for 2D-based problems: `EUC_2D`, `GEO`, `ATT`). 148 | - Generates the global distance matrix (`dist_matrix`) to quickly retrieve distances. 149 | - **Key methods**: 150 | - **init(self, plot_route, instance_file):** Sets up the plotting option and reads the TSP file. Checks if coordinates can be plotted. 151 | - **generate_distance_matrix(self):** Builds the matrix `dist_matrix[i][j]` = distance from city `i` to `j`. 152 | 153 | ### Genetic Algorithm Functions 154 | 1. **Fitness Evaluation** 155 | ```python 156 | def total_cost(route): 157 | ... 158 | ``` 159 | - Computes the total distance of the closed tour represented by `route`. 160 | 2. **Initialization** 161 | ```python 162 | def nearest_neighbor(n): 163 | ... 164 | ``` 165 | - Randomly chooses a start city, then with probability 0.4 applies a Nearest Neighbor approach; otherwise fully shuffles the cities. 166 | - Returns either a Python list or a NumPy array of the city permutation (depending on `use_numpy`). 167 | 3. **Mutation (and Perturbations)** 168 | ```python 169 | def mutate(route): 170 | ... 171 | ``` 172 | - Applies one of several custom perturbation methods (e.g., reversing a sub-route). 173 | - You can switch out different mutation methods (e.g., `perturbation_swap_two`, `perturbation_swap_neighbors`, `two_opt`). 174 | 4. **GA Routines** 175 | - ga_simple 176 | - Population size = 50 177 | - Generations = 200 178 | - Uses `eaSimple` from DEAP for a straightforward evolution loop. 179 | - ga_advanced 180 | - Larger population (100) 181 | - More generations (1000) 182 | - Custom loop (with optional local searches, more detailed logs, etc). 183 | ### Helper Functions 184 | - `two_opt(route)`: An optional local search to reverse edges in the route if it yields improvement. 185 | - `perturbation_swap_two`, `perturbation_swap_neighbors`, `perturbation_reverse_subroute`: Different ways to shuffle or reverse parts of the route. 186 | - `plot_evolution(min_values, avg_values)`: Creates a line chart showing how the best cost and average cost evolve over generations. 187 | 188 | ### Main Function 189 | ```python 190 | def main(): 191 | ... 192 | ``` 193 | - Presents a **menu** of questions to the user: 194 | 1. Enable route plotting/animation? (`0` or `1`) 195 | 2. TSP instance file path (e.g., `kroA100.tsp`) 196 | 3. Seed (integer) 197 | 4. Version of GA: `0 = simple`, `1 = advanced` 198 | 5. use_numpy: `0` or `1` 199 | - Creates a `TSPInstance` based on these answers, generates the distance matrix, and then calls `ga_simple` or `ga_advanced`. 200 | 201 | ### Notes on Plotting and Animations 202 | 1. **Ploting:** The script uses `matplotlib.pyplot` to show the best and average cost across generations. 203 | - If `plot_flag == 1`, a figure will pop up at the end of the run. 204 | 2. **Animation:** The route animation (showing how the best route changes) works best in a **Jupyter/IPython** environment. 205 | - If run in a standard terminal, you might **not** see the inline animation (you can still save it to a file, if you modify the code). 206 | - The `FuncAnimation` calls `to_jshtml()`, so an inline notebook display is ideal. 207 | 208 | ### License 209 | This repository is licensed under the MIT License. You are free to modify, share, and use this code for your own projects. 210 | 211 | --- 212 | 213 | Enjoy solving TSP instances with this Genetic Algorithm! If you have any questions, feel free to open an issue or submit a pull request. 214 | -------------------------------------------------------------------------------- /ga_interactive.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from matplotlib.animation import FuncAnimation 3 | from IPython.display import HTML, display, Markdown 4 | import numpy as np 5 | import tsplib95 6 | import random 7 | import time 8 | from deap import algorithms 9 | from deap import base 10 | from deap import creator 11 | from deap import tools 12 | plt.rcParams['animation.embed_limit'] = 2**128 13 | 14 | # Global variables 15 | INF = 9999999 16 | dist_matrix = [] 17 | use_numpy = 0 # 0 = do not use numpy arrays for individuals, 1 = use numpy arrays 18 | 19 | class AnimationTSP: 20 | """ 21 | This class creates an animated visualization of a TSP solution improving over time. 22 | """ 23 | 24 | def __init__(self, history, x_coords, y_coords, costs): 25 | """ 26 | :param history: A list of solutions (each solution is a list of city indices). 27 | :param x_coords: List of x-coordinates of each city. 28 | :param y_coords: List of y-coordinates of each city. 29 | :param costs: A list of costs corresponding to each solution in history. 30 | """ 31 | 32 | # Ensure 'history' is a list of lists 33 | if isinstance(history[0], list): 34 | self.history = history 35 | else: 36 | self.history = [h.tolist() for h in history] 37 | 38 | self.costs = costs 39 | self.points = np.column_stack((x_coords, y_coords)) 40 | 41 | self.fig, self.ax = plt.subplots() 42 | self.line, = plt.plot([], [], lw=2) 43 | self.title = self.ax.text( 44 | 0.8, 1.035, "", 45 | bbox={'facecolor': 'w', 'alpha': 0.5, 'pad': 5}, 46 | transform=self.ax.transAxes, 47 | ha="center" 48 | ) 49 | 50 | def init_animation(self): 51 | """ 52 | Initializes the animation with empty lines and plots the city nodes. 53 | """ 54 | # Plot the city nodes from the first solution in the history 55 | x_plot = [self.points[i][0] for i in self.history[0]] 56 | y_plot = [self.points[i][1] for i in self.history[0]] 57 | plt.plot(x_plot, y_plot, 'co') 58 | 59 | # Adjust axes with a small margin 60 | extra_x = (max(x_plot) - min(x_plot)) * 0.05 61 | extra_y = (max(y_plot) - min(y_plot)) * 0.05 62 | self.ax.set_xlim(min(x_plot) - extra_x, max(x_plot) + extra_x) 63 | self.ax.set_ylim(min(y_plot) - extra_y, max(y_plot) + extra_y) 64 | 65 | # Initialize the route line as empty 66 | self.line.set_data([], []) 67 | return self.line, 68 | 69 | def update_animation(self, frame): 70 | """ 71 | For each frame, update the plot with the route of that generation/iteration. 72 | """ 73 | route = self.history[frame] 74 | x_plot = [self.points[i, 0] for i in route + [route[0]]] 75 | y_plot = [self.points[i, 1] for i in route + [route[0]]] 76 | 77 | self.title.set_text(f"Iteration {frame}, Cost {self.costs[frame]}") 78 | self.line.set_data(x_plot, y_plot) 79 | return self.line 80 | 81 | def animate_routes(self): 82 | """ 83 | Creates and displays the animation in a Jupyter environment. 84 | """ 85 | # Setting how many frames to skip to create a shorter animation 86 | div = len(self.history) // 3 if len(self.history) > 3 else 1 87 | step = len(self.history) // div if div != 0 else 1 88 | 89 | ani = FuncAnimation( 90 | self.fig, 91 | self.update_animation, 92 | frames=range(0, len(self.history), step), 93 | init_func=self.init_animation, 94 | interval=3, 95 | repeat=False 96 | ) 97 | 98 | plt.title("TSP Route Animation") 99 | 100 | # Convert animation to HTML for display 101 | ani.interactive = True 102 | html_anim = ani.to_jshtml() 103 | display(HTML(html_anim)) 104 | 105 | 106 | class TSPInstance: 107 | """ 108 | Loads a TSP instance via tsplib95 and prepares data (coordinates, distance matrix). 109 | """ 110 | 111 | def __init__(self, plot_route, instance_file): 112 | """ 113 | :param plot_route: Boolean (0 or 1) to indicate whether to plot/animate the route. 114 | :param instance_file: File path to the TSP instance (TSPLIB format). 115 | """ 116 | self.plot_route = bool(plot_route) 117 | self.plot_enabled = self.plot_route 118 | 119 | self.coord_x = [] 120 | self.coord_y = [] 121 | self.problem = tsplib95.load(instance_file) 122 | self.info = self.problem.as_keyword_dict() 123 | self.n = len(self.problem.get_graph()) 124 | 125 | # If the instance can be plotted (EUC_2D, GEO, ATT), save city coordinates 126 | if self.plot_route and self._can_plot(): 127 | for i in range(1, self.n + 1): 128 | x, y = self.info['NODE_COORD_SECTION'][i] 129 | self.coord_x.append(x) 130 | self.coord_y.append(y) 131 | else: 132 | self.plot_route = False 133 | 134 | def _can_plot(self): 135 | """ 136 | Checks if the TSP instance has a coordinate-based distance (e.g., EUC_2D, GEO, ATT). 137 | """ 138 | dist_type = self.info['EDGE_WEIGHT_TYPE'] 139 | if dist_type in ['EUC_2D', 'GEO', 'ATT']: 140 | return True 141 | else: 142 | print("Plotting is not supported for this EDGE_WEIGHT_TYPE.") 143 | return False 144 | 145 | def generate_distance_matrix(self): 146 | """ 147 | Generate the global distance matrix (dist_matrix) for the TSP. 148 | """ 149 | global dist_matrix 150 | dist_matrix = [[INF for _ in range(self.n)] for _ in range(self.n)] 151 | start_node = list(self.problem.get_nodes())[0] 152 | 153 | # Adjust if the node indices start at 1 or 0 154 | if start_node == 0: 155 | for i in range(self.n): 156 | for j in range(self.n): 157 | if i != j: 158 | dist_matrix[i][j] = self.problem.get_weight(i, j) 159 | else: 160 | # If nodes start at 1 instead of 0 161 | for i in range(self.n): 162 | for j in range(self.n): 163 | if i != j: 164 | dist_matrix[i][j] = self.problem.get_weight(i + 1, j + 1) 165 | 166 | 167 | def distance(i, j): 168 | """ 169 | Returns the distance between city i and city j using the global distance matrix. 170 | """ 171 | return dist_matrix[i][j] 172 | 173 | 174 | def total_cost(route): 175 | """ 176 | Evaluates the total cost of a TSP route (closed tour). 177 | """ 178 | csum = 0 179 | for k in range(len(route) - 1): 180 | csum += distance(route[k], route[k + 1]) 181 | # Add cost from last city back to the first city 182 | csum += distance(route[-1], route[0]) 183 | return (csum,) 184 | 185 | 186 | def nearest_neighbor(n): 187 | """ 188 | Generates a TSP route using the Nearest Neighbor heuristic with probability 0.4, 189 | otherwise shuffles cities randomly. 190 | 191 | :param n: Number of cities. 192 | :return: A route (list or numpy array) representing a permutation of cities. 193 | """ 194 | start = random.randrange(0, n) 195 | if random.uniform(0, 1) < 0.4: 196 | current = start 197 | route = [start] 198 | selected = [False] * n 199 | selected[current] = True 200 | 201 | while len(route) < n: 202 | min_dist = INF 203 | next_city = None 204 | for candidate in range(n): 205 | if not selected[candidate] and candidate != current: 206 | cost_val = distance(current, candidate) 207 | if cost_val < min_dist: 208 | min_dist = cost_val 209 | next_city = candidate 210 | 211 | route.append(next_city) 212 | selected[next_city] = True 213 | current = next_city 214 | else: 215 | route = list(range(n)) 216 | random.shuffle(route) 217 | 218 | if use_numpy: 219 | return np.array(route) 220 | else: 221 | return route 222 | 223 | 224 | def two_opt(route): 225 | """ 226 | 2-Opt local search: tries to improve the route by reversing segments. 227 | """ 228 | n = len(route) 229 | improved = False 230 | best_delta = 0 231 | cut_count = 0 232 | 233 | # Shift the route starting point randomly 234 | k = random.randint(0, n - 1) 235 | if use_numpy: 236 | route = np.hstack((route[k:], route[:k])) # rotate with numpy 237 | else: 238 | route = route[k:] + route[:k] 239 | 240 | for i in range(n - 2): 241 | for j in range(i + 1, n - 1): 242 | old_cost = distance(route[i], route[i + 1]) + distance(route[j], route[j + 1]) 243 | new_cost = distance(route[i], route[j]) + distance(route[i + 1], route[j + 1]) 244 | delta = new_cost - old_cost 245 | 246 | if delta < best_delta: 247 | best_delta = delta 248 | min_i, min_j = i, j 249 | cut_count += 1 250 | # Only make one improving swap 251 | if cut_count == 1: 252 | improved = True 253 | 254 | if improved: 255 | break 256 | 257 | if cut_count > 0: 258 | segment = route[min_i + 1: min_j + 1] 259 | route[min_i + 1: min_j + 1] = segment[::-1] 260 | 261 | 262 | def perturbation_swap_two(route): 263 | """ 264 | Perturbation: randomly swap two distinct cities in the route. 265 | """ 266 | i, j = 0, 0 267 | n = len(route) 268 | while i == j: 269 | i = random.randint(0, n - 1) 270 | j = random.randint(0, n - 1) 271 | 272 | route[i], route[j] = route[j], route[i] 273 | 274 | 275 | def perturbation_swap_neighbors(route): 276 | """ 277 | Perturbation: choose one city at random and swap it with its immediate neighbor. 278 | """ 279 | n = len(route) 280 | i = random.randint(0, n - 1) 281 | j = i + 1 if i < n - 1 else 0 282 | 283 | route[i], route[j] = route[j], route[i] 284 | 285 | 286 | def perturbation_reverse_subroute(route): 287 | """ 288 | Perturbation 2: choose two random points i, j (i < j) and reverse the subroute between them. 289 | """ 290 | i, j = 0, 0 291 | n = len(route) 292 | while i >= j: 293 | i = random.randint(0, n - 1) 294 | j = random.randint(0, n - 1) 295 | 296 | route[i:j] = route[i:j][::-1] 297 | 298 | 299 | def mutate(route): 300 | """ 301 | Custom mutation strategy that applies a subroute reversal (2). 302 | Other perturbations are commented out but can be included if desired. 303 | """ 304 | # Examples of different perturbations: 305 | # perturbation_swap_two(route) 306 | # perturbation_swap_neighbors(route) 307 | # two_opt(route) 308 | perturbation_reverse_subroute(route) 309 | return (route,) 310 | 311 | 312 | def ga_simple(tsp_instance, seed): 313 | """ 314 | Simple GA to solve TSP. 315 | :param tsp_instance: TSPInstance object. 316 | :param seed: Random seed to control reproducibility. 317 | """ 318 | population_size = 50 319 | max_gens = 200 320 | cx_prob = 0.9 321 | mut_prob = 0.4 322 | tournament_size = 4 323 | n_cities = tsp_instance.n 324 | 325 | random.seed(seed) 326 | 327 | # Create the Fitness and Individual classes 328 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) 329 | if use_numpy: 330 | creator.create("Individual", np.ndarray, fitness=creator.FitnessMin) 331 | else: 332 | creator.create("Individual", list, fitness=creator.FitnessMin) 333 | 334 | toolbox = base.Toolbox() 335 | 336 | # Register functions 337 | toolbox.register("indices", nearest_neighbor, n_cities) 338 | toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.indices) 339 | toolbox.register("population", tools.initRepeat, list, toolbox.individual) 340 | 341 | toolbox.register("evaluate", total_cost) 342 | toolbox.register("select", tools.selTournament, tournsize=tournament_size) 343 | toolbox.register("mate", tools.cxOrdered) 344 | toolbox.register("mutate", mutate) 345 | 346 | # Generate initial population 347 | pop = toolbox.population(n=population_size) 348 | if use_numpy: 349 | hof = tools.HallOfFame(1, similar=np.array_equal) 350 | else: 351 | hof = tools.HallOfFame(1) 352 | 353 | stats = tools.Statistics(lambda ind: ind.fitness.values) 354 | stats.register("avg", np.mean) 355 | stats.register("std", np.std) 356 | stats.register("min", np.min) 357 | stats.register("max", np.max) 358 | 359 | start_time = time.time() 360 | final_population, logbook = algorithms.eaSimple( 361 | pop, toolbox, cx_prob, mut_prob, max_gens, 362 | stats=stats, halloffame=hof 363 | ) 364 | end_time = time.time() 365 | 366 | min_list, avg_list = logbook.select("min", "avg") 367 | 368 | print(f"Best route cost: {min(min_list)}") 369 | print(f"Execution time : {end_time - start_time}") 370 | 371 | if tsp_instance.plot_enabled: 372 | plot_evolution(min_list, avg_list) 373 | 374 | 375 | def ga_advanced(tsp_instance, seed): 376 | """ 377 | Advanced GA to solve TSP with custom selection, crossover, mutation, and partial 2-Opt local search steps. 378 | :param tsp_instance: TSPInstance object. 379 | :param seed: Random seed to control reproducibility. 380 | """ 381 | population_size = 100 382 | max_gens = 1000 383 | cx_prob = 0.9 384 | mut_prob = 0.1 385 | tournament_size = 4 386 | n_cities = tsp_instance.n 387 | 388 | random.seed(seed) 389 | 390 | # Define Fitness and Individual 391 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) 392 | if use_numpy: 393 | creator.create("Individual", np.ndarray, fitness=creator.FitnessMin) 394 | else: 395 | creator.create("Individual", list, fitness=creator.FitnessMin) 396 | 397 | toolbox = base.Toolbox() 398 | 399 | toolbox.register("indices", nearest_neighbor, n_cities) 400 | toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.indices) 401 | toolbox.register("population", tools.initRepeat, list, toolbox.individual) 402 | 403 | toolbox.register("evaluate", total_cost) 404 | toolbox.register("select", tools.selTournament, tournsize=tournament_size) 405 | toolbox.register("mate", tools.cxOrdered) 406 | toolbox.register("mutate", mutate) 407 | 408 | # Build initial population 409 | pop = toolbox.population(n=population_size) 410 | if use_numpy: 411 | hof = tools.HallOfFame(1, similar=np.array_equal) 412 | else: 413 | hof = tools.HallOfFame(1) 414 | 415 | stats = tools.Statistics(lambda ind: ind.fitness.values) 416 | stats.register("avg", np.mean) 417 | stats.register("std", np.std) 418 | stats.register("min", np.min) 419 | stats.register("max", np.max) 420 | 421 | logbook = tools.Logbook() 422 | logbook.header = "gen", "evals", "std", "min", "avg", "max" 423 | 424 | # Evaluate initial population 425 | start_time = time.time() 426 | fitnesses = list(map(toolbox.evaluate, pop)) 427 | for ind, fit_val in zip(pop, fitnesses): 428 | ind.fitness.values = fit_val 429 | 430 | gen = 0 431 | solutions_history = [] 432 | costs_history = [] 433 | 434 | record = stats.compile(pop) 435 | logbook.record(gen=gen, evals=len(pop), **record) 436 | print(logbook[-1]["gen"], logbook[-1]["avg"], logbook[-1]["min"]) 437 | 438 | # Evolve 439 | while gen < max_gens: 440 | gen += 1 441 | 442 | # Selection 443 | offspring = toolbox.select(pop, len(pop)) 444 | offspring = list(map(toolbox.clone, offspring)) 445 | 446 | # Crossover 447 | for child1, child2 in zip(offspring[::2], offspring[1::2]): 448 | if random.random() < cx_prob: 449 | toolbox.mate(child1, child2) 450 | del child1.fitness.values 451 | del child2.fitness.values 452 | 453 | # Mutation 454 | for mutant in offspring: 455 | if random.random() < mut_prob: 456 | toolbox.mutate(mutant) 457 | del mutant.fitness.values 458 | 459 | # Re-evaluate mutated/crossover offsprings 460 | invalid_inds = [ind for ind in offspring if not ind.fitness.valid] 461 | fitnesses = map(toolbox.evaluate, invalid_inds) 462 | for ind, fit_val in zip(invalid_inds, fitnesses): 463 | ind.fitness.values = fit_val 464 | 465 | # Replace population 466 | pop[:] = offspring 467 | hof.update(offspring) 468 | 469 | record = stats.compile(offspring) 470 | logbook.record(gen=gen, evals=len(offspring), **record) 471 | print(logbook[-1]["gen"], logbook[-1]["avg"], logbook[-1]["min"]) 472 | 473 | # Store best route of this generation 474 | best_ind = tools.selBest(offspring, k=1)[0] 475 | solutions_history.append(best_ind) 476 | costs_history.append(int(logbook[-1]["min"])) 477 | 478 | end_time = time.time() 479 | 480 | print(f"Best route cost: {min(costs_history)}") 481 | print(f"Execution time : {end_time - start_time}") 482 | 483 | # If route plotting is enabled, animate the route 484 | if tsp_instance.plot_route: 485 | anim = AnimationTSP(solutions_history, tsp_instance.coord_x, tsp_instance.coord_y, costs_history) 486 | anim.animate_routes() 487 | 488 | # If cost plot is enabled, plot cost evolution 489 | if tsp_instance.plot_enabled: 490 | min_values, avg_values = logbook.select("min", "avg") 491 | plot_evolution(min_values, avg_values) 492 | 493 | 494 | def plot_evolution(min_values, avg_values): 495 | """ 496 | Plots the evolution of the best and average cost over generations. 497 | """ 498 | plt.figure() 499 | plot1, = plt.plot(min_values, 'c-', label='Best Cost') 500 | plot2, = plt.plot(avg_values, 'b-', label='Average Cost') 501 | plt.legend(handles=[plot1, plot2], frameon=True) 502 | plt.ylabel('Cost') 503 | plt.xlabel('Generations') 504 | plt.title("Generations vs. Cost - TSP") 505 | plt.xlim((0, len(min_values))) 506 | plt.show() 507 | 508 | 509 | def main(): 510 | """ 511 | Main function that uses a small menu to collect user input (rather than sys.argv). 512 | """ 513 | print("=========================================") 514 | print(" TSP GA Solver - Interactive Menu") 515 | print("=========================================") 516 | 517 | # Gather user input 518 | plot_flag = int(input("Enable route plotting/animation? (0=No, 1=Yes): ")) 519 | instance_file = input("Enter the file path of the TSP instance (e.g., kroA100.tsp): ") 520 | seed_value = int(input("Enter random seed (integer), e.g. 42: ")) 521 | version = int(input("Choose GA version (0=Simple, 1=Advanced): ")) 522 | 523 | global use_numpy 524 | use_numpy = int(input("Use numpy arrays for individuals? (0=No, 1=Yes): ")) 525 | 526 | # Create TSP instance 527 | tsp_instance = TSPInstance(plot_flag, instance_file) 528 | tsp_instance.generate_distance_matrix() 529 | 530 | # Run the chosen GA version 531 | if version == 0: 532 | ga_simple(tsp_instance, seed_value) 533 | else: 534 | ga_advanced(tsp_instance, seed_value) 535 | 536 | 537 | if __name__ == "__main__": 538 | main() 539 | -------------------------------------------------------------------------------- /images/TSP_cost_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RenatoMaynard/TSP-Genetic-Algorithm/a58243c058240602b2dca6f62ec94ce923ac1d73/images/TSP_cost_plot.png -------------------------------------------------------------------------------- /images/TSP_route.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RenatoMaynard/TSP-Genetic-Algorithm/a58243c058240602b2dca6f62ec94ce923ac1d73/images/TSP_route.png -------------------------------------------------------------------------------- /instances/att48.tsp: -------------------------------------------------------------------------------- 1 | NAME : att48 2 | COMMENT : 48 capitals of the US (Padberg/Rinaldi) 3 | TYPE : TSP 4 | DIMENSION : 48 5 | EDGE_WEIGHT_TYPE : ATT 6 | NODE_COORD_SECTION 7 | 1 6734 1453 8 | 2 2233 10 9 | 3 5530 1424 10 | 4 401 841 11 | 5 3082 1644 12 | 6 7608 4458 13 | 7 7573 3716 14 | 8 7265 1268 15 | 9 6898 1885 16 | 10 1112 2049 17 | 11 5468 2606 18 | 12 5989 2873 19 | 13 4706 2674 20 | 14 4612 2035 21 | 15 6347 2683 22 | 16 6107 669 23 | 17 7611 5184 24 | 18 7462 3590 25 | 19 7732 4723 26 | 20 5900 3561 27 | 21 4483 3369 28 | 22 6101 1110 29 | 23 5199 2182 30 | 24 1633 2809 31 | 25 4307 2322 32 | 26 675 1006 33 | 27 7555 4819 34 | 28 7541 3981 35 | 29 3177 756 36 | 30 7352 4506 37 | 31 7545 2801 38 | 32 3245 3305 39 | 33 6426 3173 40 | 34 4608 1198 41 | 35 23 2216 42 | 36 7248 3779 43 | 37 7762 4595 44 | 38 7392 2244 45 | 39 3484 2829 46 | 40 6271 2135 47 | 41 4985 140 48 | 42 1916 1569 49 | 43 7280 4899 50 | 44 7509 3239 51 | 45 10 2676 52 | 46 6807 2993 53 | 47 5185 3258 54 | 48 3023 1942 55 | EOF 56 | -------------------------------------------------------------------------------- /instances/bays29.tsp: -------------------------------------------------------------------------------- 1 | NAME: bays29 2 | TYPE: TSP 3 | COMMENT: 29 cities in Bavaria, street distances (Groetschel,Juenger,Reinelt) 4 | DIMENSION: 29 5 | EDGE_WEIGHT_TYPE: EXPLICIT 6 | EDGE_WEIGHT_FORMAT: FULL_MATRIX 7 | DISPLAY_DATA_TYPE: TWOD_DISPLAY 8 | EDGE_WEIGHT_SECTION 9 | 0 107 241 190 124 80 316 76 152 157 283 133 113 297 228 129 348 276 188 150 65 341 184 67 221 169 108 45 167 10 | 107 0 148 137 88 127 336 183 134 95 254 180 101 234 175 176 265 199 182 67 42 278 271 146 251 105 191 139 79 11 | 241 148 0 374 171 259 509 317 217 232 491 312 280 391 412 349 422 356 355 204 182 435 417 292 424 116 337 273 77 12 | 190 137 374 0 202 234 222 192 248 42 117 287 79 107 38 121 152 86 68 70 137 151 239 135 137 242 165 228 205 13 | 124 88 171 202 0 61 392 202 46 160 319 112 163 322 240 232 314 287 238 155 65 366 300 175 307 57 220 121 97 14 | 80 127 259 234 61 0 386 141 72 167 351 55 157 331 272 226 362 296 232 164 85 375 249 147 301 118 188 60 185 15 | 316 336 509 222 392 386 0 233 438 254 202 439 235 254 210 187 313 266 154 282 321 298 168 249 95 437 190 314 435 16 | 76 183 317 192 202 141 233 0 213 188 272 193 131 302 233 98 344 289 177 216 141 346 108 57 190 245 43 81 243 17 | 152 134 217 248 46 72 438 213 0 206 365 89 209 368 286 278 360 333 284 201 111 412 321 221 353 72 266 132 111 18 | 157 95 232 42 160 167 254 188 206 0 159 220 57 149 80 132 193 127 100 28 95 193 241 131 169 200 161 189 163 19 | 283 254 491 117 319 351 202 272 365 159 0 404 176 106 79 161 165 141 95 187 254 103 279 215 117 359 216 308 322 20 | 133 180 312 287 112 55 439 193 89 220 404 0 210 384 325 279 415 349 285 217 138 428 310 200 354 169 241 112 238 21 | 113 101 280 79 163 157 235 131 209 57 176 210 0 186 117 75 231 165 81 85 92 230 184 74 150 208 104 158 206 22 | 297 234 391 107 322 331 254 302 368 149 106 384 186 0 69 191 59 35 125 167 255 44 309 245 169 327 246 335 288 23 | 228 175 412 38 240 272 210 233 286 80 79 325 117 69 0 122 122 56 56 108 175 113 240 176 125 280 177 266 243 24 | 129 176 349 121 232 226 187 98 278 132 161 279 75 191 122 0 244 178 66 160 161 235 118 62 92 277 55 155 275 25 | 348 265 422 152 314 362 313 344 360 193 165 415 231 59 122 244 0 66 178 198 286 77 362 287 228 358 299 380 319 26 | 276 199 356 86 287 296 266 289 333 127 141 349 165 35 56 178 66 0 112 132 220 79 296 232 181 292 233 314 253 27 | 188 182 355 68 238 232 154 177 284 100 95 285 81 125 56 66 178 112 0 128 167 169 179 120 69 283 121 213 281 28 | 150 67 204 70 155 164 282 216 201 28 187 217 85 167 108 160 198 132 128 0 88 211 269 159 197 172 189 182 135 29 | 65 42 182 137 65 85 321 141 111 95 254 138 92 255 175 161 286 220 167 88 0 299 229 104 236 110 149 97 108 30 | 341 278 435 151 366 375 298 346 412 193 103 428 230 44 113 235 77 79 169 211 299 0 353 289 213 371 290 379 332 31 | 184 271 417 239 300 249 168 108 321 241 279 310 184 309 240 118 362 296 179 269 229 353 0 121 162 345 80 189 342 32 | 67 146 292 135 175 147 249 57 221 131 215 200 74 245 176 62 287 232 120 159 104 289 121 0 154 220 41 93 218 33 | 221 251 424 137 307 301 95 190 353 169 117 354 150 169 125 92 228 181 69 197 236 213 162 154 0 352 147 247 350 34 | 169 105 116 242 57 118 437 245 72 200 359 169 208 327 280 277 358 292 283 172 110 371 345 220 352 0 265 178 39 35 | 108 191 337 165 220 188 190 43 266 161 216 241 104 246 177 55 299 233 121 189 149 290 80 41 147 265 0 124 263 36 | 45 139 273 228 121 60 314 81 132 189 308 112 158 335 266 155 380 314 213 182 97 379 189 93 247 178 124 0 199 37 | 167 79 77 205 97 185 435 243 111 163 322 238 206 288 243 275 319 253 281 135 108 332 342 218 350 39 263 199 0 38 | EOF 39 | -------------------------------------------------------------------------------- /instances/berlin52.tsp: -------------------------------------------------------------------------------- 1 | NAME: berlin52 2 | TYPE: TSP 3 | COMMENT: 52 locations in Berlin (Groetschel) 4 | DIMENSION: 52 5 | EDGE_WEIGHT_TYPE: EUC_2D 6 | NODE_COORD_SECTION 7 | 1 565.0 575.0 8 | 2 25.0 185.0 9 | 3 345.0 750.0 10 | 4 945.0 685.0 11 | 5 845.0 655.0 12 | 6 880.0 660.0 13 | 7 25.0 230.0 14 | 8 525.0 1000.0 15 | 9 580.0 1175.0 16 | 10 650.0 1130.0 17 | 11 1605.0 620.0 18 | 12 1220.0 580.0 19 | 13 1465.0 200.0 20 | 14 1530.0 5.0 21 | 15 845.0 680.0 22 | 16 725.0 370.0 23 | 17 145.0 665.0 24 | 18 415.0 635.0 25 | 19 510.0 875.0 26 | 20 560.0 365.0 27 | 21 300.0 465.0 28 | 22 520.0 585.0 29 | 23 480.0 415.0 30 | 24 835.0 625.0 31 | 25 975.0 580.0 32 | 26 1215.0 245.0 33 | 27 1320.0 315.0 34 | 28 1250.0 400.0 35 | 29 660.0 180.0 36 | 30 410.0 250.0 37 | 31 420.0 555.0 38 | 32 575.0 665.0 39 | 33 1150.0 1160.0 40 | 34 700.0 580.0 41 | 35 685.0 595.0 42 | 36 685.0 610.0 43 | 37 770.0 610.0 44 | 38 795.0 645.0 45 | 39 720.0 635.0 46 | 40 760.0 650.0 47 | 41 475.0 960.0 48 | 42 95.0 260.0 49 | 43 875.0 920.0 50 | 44 700.0 500.0 51 | 45 555.0 815.0 52 | 46 830.0 485.0 53 | 47 1170.0 65.0 54 | 48 830.0 610.0 55 | 49 605.0 625.0 56 | 50 595.0 360.0 57 | 51 1340.0 725.0 58 | 52 1740.0 245.0 59 | EOF 60 | 61 | -------------------------------------------------------------------------------- /instances/burma14.tsp: -------------------------------------------------------------------------------- 1 | NAME: burma14 2 | TYPE: TSP 3 | COMMENT: 14-Staedte in Burma (Zaw Win) 4 | DIMENSION: 14 5 | EDGE_WEIGHT_TYPE: GEO 6 | EDGE_WEIGHT_FORMAT: FUNCTION 7 | DISPLAY_DATA_TYPE: COORD_DISPLAY 8 | NODE_COORD_SECTION 9 | 1 16.47 96.10 10 | 2 16.47 94.44 11 | 3 20.09 92.54 12 | 4 22.39 93.37 13 | 5 25.23 97.24 14 | 6 22.00 96.05 15 | 7 20.47 97.02 16 | 8 17.20 96.29 17 | 9 16.30 97.38 18 | 10 14.05 98.12 19 | 11 16.53 97.38 20 | 12 21.52 95.59 21 | 13 19.41 97.13 22 | 14 20.09 94.55 23 | EOF 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /instances/gr24.tsp: -------------------------------------------------------------------------------- 1 | NAME: gr24 2 | TYPE: TSP 3 | COMMENT: 24-city problem (Groetschel) 4 | DIMENSION: 24 5 | EDGE_WEIGHT_TYPE: EXPLICIT 6 | EDGE_WEIGHT_FORMAT: LOWER_DIAG_ROW 7 | EDGE_WEIGHT_SECTION 8 | 0 257 0 187 196 0 91 228 158 0 150 112 9 | 96 120 0 80 196 88 77 63 0 130 167 59 10 | 101 56 25 0 134 154 63 105 34 29 22 0 11 | 243 209 286 159 190 216 229 225 0 185 86 124 12 | 156 40 124 95 82 207 0 214 223 49 185 123 13 | 115 86 90 313 151 0 70 191 121 27 83 47 14 | 64 68 173 119 148 0 272 180 315 188 193 245 15 | 258 228 29 159 342 209 0 219 83 172 149 79 16 | 139 134 112 126 62 199 153 97 0 293 50 232 17 | 264 148 232 203 190 248 122 259 227 219 134 0 18 | 54 219 92 82 119 31 43 58 238 147 84 53 19 | 267 170 255 0 211 74 81 182 105 150 121 108 20 | 310 37 160 145 196 99 125 173 0 290 139 98 21 | 261 144 176 164 136 389 116 147 224 275 178 154 22 | 190 79 0 268 53 138 239 123 207 178 165 367 23 | 86 187 202 227 130 68 230 57 86 0 261 43 24 | 200 232 98 200 171 131 166 90 227 195 137 69 25 | 82 223 90 176 90 0 175 128 76 146 32 76 26 | 47 30 222 56 103 109 225 104 164 99 57 112 27 | 114 134 0 250 99 89 221 105 189 160 147 349 28 | 76 138 184 235 138 114 212 39 40 46 136 96 29 | 0 192 228 235 108 119 165 178 154 71 136 262 30 | 110 74 96 264 187 182 261 239 165 151 221 0 31 | 121 142 99 84 35 29 42 36 220 70 126 55 32 | 249 104 178 60 96 175 153 146 47 135 169 0 33 | EOF 34 | -------------------------------------------------------------------------------- /instances/kroA100.tsp: -------------------------------------------------------------------------------- 1 | NAME: kroA100 2 | TYPE: TSP 3 | COMMENT: 100-city problem A (Krolak/Felts/Nelson) 4 | DIMENSION: 100 5 | EDGE_WEIGHT_TYPE : EUC_2D 6 | NODE_COORD_SECTION 7 | 1 1380 939 8 | 2 2848 96 9 | 3 3510 1671 10 | 4 457 334 11 | 5 3888 666 12 | 6 984 965 13 | 7 2721 1482 14 | 8 1286 525 15 | 9 2716 1432 16 | 10 738 1325 17 | 11 1251 1832 18 | 12 2728 1698 19 | 13 3815 169 20 | 14 3683 1533 21 | 15 1247 1945 22 | 16 123 862 23 | 17 1234 1946 24 | 18 252 1240 25 | 19 611 673 26 | 20 2576 1676 27 | 21 928 1700 28 | 22 53 857 29 | 23 1807 1711 30 | 24 274 1420 31 | 25 2574 946 32 | 26 178 24 33 | 27 2678 1825 34 | 28 1795 962 35 | 29 3384 1498 36 | 30 3520 1079 37 | 31 1256 61 38 | 32 1424 1728 39 | 33 3913 192 40 | 34 3085 1528 41 | 35 2573 1969 42 | 36 463 1670 43 | 37 3875 598 44 | 38 298 1513 45 | 39 3479 821 46 | 40 2542 236 47 | 41 3955 1743 48 | 42 1323 280 49 | 43 3447 1830 50 | 44 2936 337 51 | 45 1621 1830 52 | 46 3373 1646 53 | 47 1393 1368 54 | 48 3874 1318 55 | 49 938 955 56 | 50 3022 474 57 | 51 2482 1183 58 | 52 3854 923 59 | 53 376 825 60 | 54 2519 135 61 | 55 2945 1622 62 | 56 953 268 63 | 57 2628 1479 64 | 58 2097 981 65 | 59 890 1846 66 | 60 2139 1806 67 | 61 2421 1007 68 | 62 2290 1810 69 | 63 1115 1052 70 | 64 2588 302 71 | 65 327 265 72 | 66 241 341 73 | 67 1917 687 74 | 68 2991 792 75 | 69 2573 599 76 | 70 19 674 77 | 71 3911 1673 78 | 72 872 1559 79 | 73 2863 558 80 | 74 929 1766 81 | 75 839 620 82 | 76 3893 102 83 | 77 2178 1619 84 | 78 3822 899 85 | 79 378 1048 86 | 80 1178 100 87 | 81 2599 901 88 | 82 3416 143 89 | 83 2961 1605 90 | 84 611 1384 91 | 85 3113 885 92 | 86 2597 1830 93 | 87 2586 1286 94 | 88 161 906 95 | 89 1429 134 96 | 90 742 1025 97 | 91 1625 1651 98 | 92 1187 706 99 | 93 1787 1009 100 | 94 22 987 101 | 95 3640 43 102 | 96 3756 882 103 | 97 776 392 104 | 98 1724 1642 105 | 99 198 1810 106 | 100 3950 1558 107 | EOF 108 | -------------------------------------------------------------------------------- /instances/st70.tsp: -------------------------------------------------------------------------------- 1 | NAME: st70 2 | TYPE: TSP 3 | COMMENT: 70-city problem (Smith/Thompson) 4 | DIMENSION: 70 5 | EDGE_WEIGHT_TYPE : EUC_2D 6 | NODE_COORD_SECTION 7 | 1 64 96 8 | 2 80 39 9 | 3 69 23 10 | 4 72 42 11 | 5 48 67 12 | 6 58 43 13 | 7 81 34 14 | 8 79 17 15 | 9 30 23 16 | 10 42 67 17 | 11 7 76 18 | 12 29 51 19 | 13 78 92 20 | 14 64 8 21 | 15 95 57 22 | 16 57 91 23 | 17 40 35 24 | 18 68 40 25 | 19 92 34 26 | 20 62 1 27 | 21 28 43 28 | 22 76 73 29 | 23 67 88 30 | 24 93 54 31 | 25 6 8 32 | 26 87 18 33 | 27 30 9 34 | 28 77 13 35 | 29 78 94 36 | 30 55 3 37 | 31 82 88 38 | 32 73 28 39 | 33 20 55 40 | 34 27 43 41 | 35 95 86 42 | 36 67 99 43 | 37 48 83 44 | 38 75 81 45 | 39 8 19 46 | 40 20 18 47 | 41 54 38 48 | 42 63 36 49 | 43 44 33 50 | 44 52 18 51 | 45 12 13 52 | 46 25 5 53 | 47 58 85 54 | 48 5 67 55 | 49 90 9 56 | 50 41 76 57 | 51 25 76 58 | 52 37 64 59 | 53 56 63 60 | 54 10 55 61 | 55 98 7 62 | 56 16 74 63 | 57 89 60 64 | 58 48 82 65 | 59 81 76 66 | 60 29 60 67 | 61 17 22 68 | 62 5 45 69 | 63 79 70 70 | 64 9 100 71 | 65 17 82 72 | 66 74 67 73 | 67 10 68 74 | 68 48 19 75 | 69 83 86 76 | 70 84 94 77 | EOF 78 | -------------------------------------------------------------------------------- /instances/ulysses16.tsp: -------------------------------------------------------------------------------- 1 | NAME: ulysses16.tsp 2 | TYPE: TSP 3 | COMMENT: Odyssey of Ulysses (Groetschel/Padberg) 4 | DIMENSION: 16 5 | EDGE_WEIGHT_TYPE: GEO 6 | DISPLAY_DATA_TYPE: COORD_DISPLAY 7 | NODE_COORD_SECTION 8 | 1 38.24 20.42 9 | 2 39.57 26.15 10 | 3 40.56 25.32 11 | 4 36.26 23.12 12 | 5 33.48 10.54 13 | 6 37.56 12.19 14 | 7 38.42 13.11 15 | 8 37.52 20.44 16 | 9 41.23 9.10 17 | 10 41.17 13.05 18 | 11 36.08 -5.21 19 | 12 38.47 15.13 20 | 13 38.15 15.35 21 | 14 37.51 15.17 22 | 15 35.49 14.32 23 | 16 39.36 19.56 24 | EOF 25 | 26 | -------------------------------------------------------------------------------- /instances/ulysses22.tsp: -------------------------------------------------------------------------------- 1 | NAME: ulysses22.tsp 2 | TYPE: TSP 3 | COMMENT: Odyssey of Ulysses (Groetschel/Padberg) 4 | DIMENSION: 22 5 | EDGE_WEIGHT_TYPE: GEO 6 | DISPLAY_DATA_TYPE: COORD_DISPLAY 7 | NODE_COORD_SECTION 8 | 1 38.24 20.42 9 | 2 39.57 26.15 10 | 3 40.56 25.32 11 | 4 36.26 23.12 12 | 5 33.48 10.54 13 | 6 37.56 12.19 14 | 7 38.42 13.11 15 | 8 37.52 20.44 16 | 9 41.23 9.10 17 | 10 41.17 13.05 18 | 11 36.08 -5.21 19 | 12 38.47 15.13 20 | 13 38.15 15.35 21 | 14 37.51 15.17 22 | 15 35.49 14.32 23 | 16 39.36 19.56 24 | 17 38.09 24.36 25 | 18 36.09 23.00 26 | 19 40.44 13.57 27 | 20 40.33 14.15 28 | 21 40.37 14.23 29 | 22 37.57 22.56 30 | EOF 31 | 32 | --------------------------------------------------------------------------------