├── .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 |
18 | 
19 | Figure 1: TSP Route
20 | |
21 |
22 | 
23 | Figure 2: TSP Cost vs Average plot
24 | |
25 |
26 |
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 |
--------------------------------------------------------------------------------