├── City Swap Algorithm ├── citySwapAlgorithm.py ├── dataset.csv └── testCode.py ├── Genetic Algorithm ├── Genetic Algorithm.py └── dataset.csv ├── README.md └── Simulated Annealing Algorithm ├── Simulated Annealing Algorithm.py └── dataset.csv /City Swap Algorithm/citySwapAlgorithm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import numpy as np 5 | import math as math 6 | 7 | def objective_calculator(solution,dataset): #Calculates the objective function value (cost) of any solution (tour) 8 | cost = 0 9 | for i in range(len(solution)-2): 10 | cost += euclid_calculator(solution[i], solution[i+1],dataset) #To the euclid_calculator function, send the cities in the solution whose objective function value will be calculated, two at a time 11 | 12 | return cost 13 | 14 | 15 | def euclid_calculator(city_1, city_2, dataset): #Calculates the euclidean distance between any two cities in the dataset 16 | 17 | return math.sqrt((dataset.loc[city_1-1,"x"]-dataset.loc[city_2-1,"x"])**2 + (dataset.loc[city_1-1,"y"]-dataset.loc[city_2-1,"y"])**2) #Calculates the euclidean distance with formula using the city coordinates in the "dataset" dataframe 18 | 19 | 20 | def city_swap(city_1,city_2,current_solution,dataset): 21 | 22 | tour_choice=current_solution.copy() #In these lines, an array called tour_choice is created to try the swap on that array first. 23 | keeper=tour_choice[city_1].copy() 24 | tour_choice[city_1]=tour_choice[city_2].copy() 25 | tour_choice[city_2]=keeper 26 | 27 | if objective_calculator(tour_choice,dataset) < objective_calculator(current_solution,dataset): #The objective function values ​​of the new tour we tried and the previous tour are compared, checking if the new solution is better 28 | print("Current cost: ",objective_calculator(tour_choice,dataset)) #The objective function value of the tour, which is the better solution, is printed 29 | current_solution=tour_choice #The better tour found is assigned to current_solution i.e. kept in that variable 30 | print("Current tour:", current_solution) #The new solution is printed 31 | print("-------------------------------------------------------------") 32 | return current_solution 33 | 34 | 35 | def main(dataset): #Argument of the main function is the dataset of the coordinates of the cities in "euclidean space" 36 | np.random.seed(28) #You can choose random seed 37 | partly_initial_solution= np.random.permutation(range(1,len(dataset)+1)) #Randomly sorts the city numbers and creates the starting tour (starting solution) 38 | initial_solution = np.append(partly_initial_solution, [partly_initial_solution[0]]) #Adds the city at the beginning of the tour to the end of the tour to make the salesman return to where he started 39 | 40 | current_solution = initial_solution #Assigns the initial solution to the current solution 41 | for k in range(10): #Trying to swap all cities with each other using nested for loops (You can change that "10" as you wish) 42 | for i in range(1,len(dataset)-1): 43 | for j in range(i+1,len(dataset)): 44 | current_solution = city_swap(i,j,current_solution,dataset) #The cities to be swapped in the loop are sent to the city_swap function, with the current solution and dataset 45 | 46 | print("Results:") 47 | print("-> Cost of best solution: ", objective_calculator(current_solution,dataset)) 48 | print("-> Best Tour Founded: ", current_solution) 49 | return(current_solution) -------------------------------------------------------------------------------- /City Swap Algorithm/dataset.csv: -------------------------------------------------------------------------------- 1 | no,x,y 2 | 1,37,52 3 | 2,49,49 4 | 3,52,64 5 | 4,20,26 6 | 5,40,30 7 | 6,21,47 8 | 7,17,63 9 | 8,31,62 10 | 9,52,33 11 | 10,51,21 12 | 11,42,41 13 | 12,31,32 14 | 13,5,25 15 | 14,12,42 16 | 15,36,16 17 | 16,52,41 18 | 17,27,23 19 | 18,17,33 20 | 19,13,13 21 | 20,57,58 22 | 21,62,42 23 | 22,42,57 24 | 23,16,57 25 | 24,8,52 26 | 25,7,38 27 | 26,27,68 28 | 27,30,48 29 | 28,43,67 30 | 29,58,48 31 | 30,58,27 32 | 31,37,69 33 | 32,38,46 34 | 33,46,10 35 | 34,61,33 36 | 35,62,63 37 | 36,63,69 38 | 37,32,22 39 | 38,45,35 40 | 39,59,15 41 | 40,5,6 42 | 41,10,17 43 | 42,21,10 44 | 43,5,64 45 | 44,30,15 46 | 45,39,10 47 | 46,32,39 48 | 47,25,32 49 | 48,25,55 50 | 49,48,28 51 | 50,56,37 52 | 51,30,40 53 | -------------------------------------------------------------------------------- /City Swap Algorithm/testCode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import time #Required library to calculate Computational Time 5 | import pandas as pd #Required library to use DataFrames 6 | 7 | 8 | import citySwapAlgorithm #Import the code of the algorithm to the test code 9 | 10 | dataset = pd.read_csv('dataset.csv') #Import the csv file containing the data 11 | 12 | 13 | start_time = time.time() #Keeps the start time 14 | solution = citySwapAlgorithm.main(dataset) #Runs our algorithm with dataset as argument 15 | comp_time = time.time() - start_time #Keeps the difference between the end time and the start time 16 | print(f"-> Computational Time: {comp_time} seconds") #Prints Computational Time 17 | -------------------------------------------------------------------------------- /Genetic Algorithm/Genetic Algorithm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import numpy as np 5 | import pandas as pd 6 | import time #Required library to calculate Computational Time 7 | import random 8 | 9 | 10 | dataset = pd.read_csv('dataset.csv') #Import the excel file containing the data 11 | 12 | cities=np.array(dataset) 13 | 14 | 15 | def calculate_distance(cities , solution): 16 | solution_for_distance_calculation = np.append(solution, [solution[0]], axis=0) #Appends the city index at the beginning of the solution array to the end of the array 17 | distance = 0 18 | next_city_index_founder=0 19 | 20 | for i in solution_for_distance_calculation: #i will hold first city indexes 21 | next_city_index_founder += 1 22 | if next_city_index_founder < len(solution_for_distance_calculation): 23 | next_city_index=solution_for_distance_calculation[next_city_index_founder] #Find the second city indexes here 24 | distance += np.sqrt(((cities[next_city_index,0]-cities[i,0])**2)+((cities[next_city_index,1]-cities[i,1])**2)) #First city and second city indexes are used when calculating euclidean distance 25 | 26 | return distance 27 | 28 | def parent_selection(population, number_of_pairs_M): 29 | current_parents = [] 30 | 31 | #Parent selection from a population 32 | parent_counter = 1 33 | 34 | while parent_counter <= 2*number_of_pairs_M: #We will select twice as many parents as the desired number of parent "pairs" i.e. M, so a parent will be selected every time this loop is iterated 35 | 36 | random_float = random.uniform(0,population["fitness"].sum()) #A float number is randomly selected in the range of 0 and the sum of fitness values 37 | cumulative_counter = 0 #Variable to assign the larger number in the cumulative test 38 | 39 | for solution, fitness in population.itertuples(index=False): 40 | 41 | cumulative_counter_copy = cumulative_counter #cumulative_counter_copy is the variable to assign the smaller number in the cumulative test 42 | cumulative_counter += fitness 43 | 44 | if cumulative_counter_copy <= random_float <= cumulative_counter: #If the randomly generated float number is in the cumulative range, the parent in question is selected 45 | 46 | append_checker = True #But first, check if the solution in question is already in the current parent list 47 | for parent in current_parents: 48 | if parent is solution: 49 | append_checker = False 50 | 51 | 52 | if append_checker == True: #If the solution in question is not found in the current parent list, it is appended 53 | current_parents.append(solution) 54 | parent_counter += 1 55 | 56 | return current_parents 57 | 58 | 59 | def crossover(current_parents, crossover_probability): 60 | children = [] #Children created with crossover will be kept in this list 61 | for parent_index_holder in range(1, len(current_parents)): #Loop created to iterate from the second parent 62 | if random.uniform(0,1) < crossover_probability: #Crossover to parent pairs with the probability specified in the crossover_probability argument 63 | 64 | parent_1 = current_parents[parent_index_holder-1] 65 | parent_2 = current_parents[parent_index_holder] 66 | 67 | left_bound = random.randint(1, len(current_parents[0])) #left border of crossover is randomly determined 68 | right_bound = random.randint(left_bound, len(current_parents[0])) #right border of crossover is randomly determined 69 | 70 | #Child creation as a result of crossover is done here 71 | child =np.array([]) #An empty array is created to create its child 72 | for j in range(left_bound): #The part of the child from the beginning to the left bound comes from parent 1 73 | child = np.append(child, parent_1[j]) 74 | 75 | for k in range(left_bound,right_bound): #The part of child between left bound and right bound comes from parent 2 76 | child = np.append(child, parent_2[k]) 77 | 78 | for l in range(right_bound, len(parent_1)): #The part of the child from the right bound to the end comes from parent 1 79 | child = np.append(child, parent_1[l]) 80 | 81 | #Mappings for currently created children are created here 82 | maps_list = [] 83 | for m in range(left_bound, right_bound): 84 | maps_list.append([parent_1[m],parent_2[m]]) 85 | 86 | #Fix the infeasible child here 87 | child = infeasible_child_fixer(child, maps_list) 88 | 89 | #Created child are appended to the children array 90 | children.append(child) 91 | 92 | return children 93 | 94 | def infeasible_child_fixer(child, maps_list): 95 | #print("Mappings: ",maps_list) #You can print mappings for current child if you want 96 | #print("Ve child ilk hali bu: " , child) #You can print current child before fixing 97 | 98 | i=1 99 | while i==1: 100 | 101 | controlled_city_index_holder = -1 102 | for controlled_city in child: #The number of each city in child will be checked 103 | controlled_city_index_holder += 1 104 | 105 | city_counter = 0 #This variable will keep the number of currently checked city in that child solution 106 | for city in child: #The number of currently checked city is found in this for loop 107 | if city == controlled_city: 108 | city_counter += 1 109 | 110 | if city_counter < 2: 111 | 112 | will_break = False 113 | 114 | if city_counter > 1: #If controlled city is more than 1 in the current child solution; we need to replace controlled_city with the city it is mapped to 115 | for a_map in maps_list: 116 | if a_map[0] == controlled_city: #Mapping where controlled_city is located 117 | 118 | child[controlled_city_index_holder] = a_map[1] #Replace the controlled city in the child solution with the other city in that mapping 119 | 120 | will_break = True 121 | 122 | maps_list.remove(a_map) #Used mapping is removed from the mapping list 123 | 124 | break 125 | 126 | elif a_map[1] == controlled_city: #Mapping where controlled_city is located 127 | 128 | child[controlled_city_index_holder] = a_map[0] #Replace the controlled city in the child solution with the other city in that mapping 129 | 130 | will_break = True 131 | 132 | maps_list.remove(a_map) #Used mapping is removed from the mapping list 133 | 134 | break 135 | 136 | 137 | if will_break: 138 | break 139 | #print("This is the new version of the child solution after the change: ",child) #You can print the new version of the child solution after the change 140 | 141 | #There was a change in the child solution, so we have to start the checking process from the beginning 142 | 143 | #But first we check if the child solution is fixed 144 | child_fixed = True 145 | city_counts = [] 146 | for city in child: 147 | count = 0 148 | 149 | for check in child: 150 | if city == check: 151 | count += 1 152 | 153 | city_counts.append([city, count]) 154 | #print("Here is the list of cities in the new version of the child solution and how many they are:", city_counts) #You can print the list of cities in the new version of the child solution and how many they are 155 | 156 | #Check if any city is more than 1 in the new child solution 157 | for count in city_counts: 158 | if count[1] > 1: 159 | child_fixed = False 160 | 161 | #If the child solution is fixed, we finish checking it. 162 | if child_fixed: 163 | i=2 164 | break 165 | 166 | #print("Fixed version of that child solution: ", child) #You can print the fixed version of that child solution 167 | return child 168 | 169 | 170 | #Apply mutation to child solutions, with a probability, by inverting a random part of it 171 | def mutate_children(children, mutation_probability): 172 | children_after_mutation = [] 173 | 174 | for child in children: 175 | if random.uniform(0, 1) <= mutation_probability: 176 | left_bound = random.randint(0,len(child)) 177 | right_bound = random.randint(left_bound,len(child)) 178 | child[left_bound:right_bound] = child[left_bound:right_bound][::-1] 179 | children_after_mutation.append(child) 180 | else: 181 | children_after_mutation.append(child) 182 | 183 | return children_after_mutation 184 | 185 | def generation_creator(population, mutated_children, cities): 186 | #A dataframe named "children" containing children and fitness values is created 187 | integer_mutated_children = [] 188 | mutated_children_fitnesses = [] 189 | for child in mutated_children: 190 | child = child.astype(int) 191 | integer_mutated_children.append(child) 192 | distance = calculate_distance(cities,child) 193 | fitness = 1/distance 194 | mutated_children_fitnesses.append(fitness) 195 | children = pd.DataFrame(list(zip(integer_mutated_children,mutated_children_fitnesses)),columns=['solution','fitness']) 196 | children.sort_values(by='fitness',axis=0,inplace=True,ascending=False) 197 | 198 | #The best half of the children are selected to be included in the population 199 | choosen_children_number = round(len(children)/2) 200 | choosen_children = children.head(choosen_children_number) 201 | 202 | #From the worst members of the current population, as many solutions as children to be added are discarded 203 | population = population.head(len(population)-choosen_children_number) 204 | 205 | #Selected children are added to the remaining solutions in the population; new population is also sorted by fitness 206 | new_population = pd.concat([population, choosen_children]) 207 | new_population.sort_values(by='fitness',axis=0,inplace=True,ascending=False) 208 | 209 | return new_population 210 | 211 | 212 | 213 | 214 | 215 | def main(generation_number, number_of_individuals, number_of_pairs_M, crossover_probability, mutation_probability): 216 | 217 | k = 0 #keeps the current generation number 218 | 219 | #A dataframe named "population" containing initial population and fitness values is created 220 | solutions = [] 221 | fitnesses = [] 222 | for i in range(0,number_of_individuals): #for loop's range is number_of_individuals, since there will be as many individuals in the population as are entered as argument 223 | solution=np.random.permutation(len(cities)) 224 | solutions.append(solution) 225 | distance = calculate_distance(cities,solution) 226 | fitness = 1/distance #The fitness value of a solution (i.e. an individual) is calculated with 1/distance 227 | fitnesses.append(fitness) 228 | population = pd.DataFrame(list(zip(solutions,fitnesses)),columns=['solution','fitness']) 229 | population.sort_values(by='fitness',axis=0,inplace=True,ascending=False) #Individuals in the population are ranked in descending order of fitness values 230 | 231 | print("Initial population: ") #Initial population is printed 232 | print(population) 233 | 234 | #Genetic search starts (new generations will be produced as many as the desired generation number) 235 | for i in range(generation_number): 236 | k+=1 237 | 238 | #parents are created to produce the next generation 239 | current_parents = parent_selection(population, number_of_pairs_M) 240 | 241 | 242 | #Child solutions are created by crossover 243 | children = crossover(current_parents, crossover_probability) 244 | 245 | 246 | #Child solutions are mutated with a probability 247 | mutated_children = mutate_children(children, mutation_probability) #inversion mutation uyguladık 248 | 249 | 250 | #Replacement is done and new generation is created 251 | population = generation_creator(population, mutated_children, cities) 252 | print("--------------------------") 253 | print("Generation number: ",k ) 254 | 255 | print(population) 256 | for solution, fitness in population.itertuples(index=False): 257 | print("Best solution founded: ", np.append(solution, [solution[0]], axis=0)) 258 | print("Cost of that solution: ", calculate_distance(cities , solution) ) 259 | 260 | break 261 | 262 | 263 | start_time = time.time() #Keeps the start time 264 | 265 | main(1700, 100, 25, 0.7, 0.5) #(generation number, number of individuals in a generation, Number of parent "pairs" to be selected in parent selection, crossover probability for a parent pair, mutation probability for a child solution) 266 | 267 | comp_time = time.time() - start_time #Subtracts the start time from the end time and keeps the result 268 | print(f"-> Computational Time: {comp_time} seconds") #Prints computational time 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | -------------------------------------------------------------------------------- /Genetic Algorithm/dataset.csv: -------------------------------------------------------------------------------- 1 | no,x,y 2 | 1,37,52 3 | 2,49,49 4 | 3,52,64 5 | 4,20,26 6 | 5,40,30 7 | 6,21,47 8 | 7,17,63 9 | 8,31,62 10 | 9,52,33 11 | 10,51,21 12 | 11,42,41 13 | 12,31,32 14 | 13,5,25 15 | 14,12,42 16 | 15,36,16 17 | 16,52,41 18 | 17,27,23 19 | 18,17,33 20 | 19,13,13 21 | 20,57,58 22 | 21,62,42 23 | 22,42,57 24 | 23,16,57 25 | 24,8,52 26 | 25,7,38 27 | 26,27,68 28 | 27,30,48 29 | 28,43,67 30 | 29,58,48 31 | 30,58,27 32 | 31,37,69 33 | 32,38,46 34 | 33,46,10 35 | 34,61,33 36 | 35,62,63 37 | 36,63,69 38 | 37,32,22 39 | 38,45,35 40 | 39,59,15 41 | 40,5,6 42 | 41,10,17 43 | 42,21,10 44 | 43,5,64 45 | 44,30,15 46 | 45,39,10 47 | 46,32,39 48 | 47,25,32 49 | 48,25,55 50 | 49,48,28 51 | 50,56,37 52 | 51,30,40 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3-heuristic-algorithms-in-Python-for-Travelling-Salesman-Problem 2 | As alternative heuristic techniques; genetic algorithm, simulated annealing algorithm and city swap algorithm are implemented in Python for Travelling Salesman Problem. Details on implementation and test results can be found in this repository. 3 | 4 | All 3 algorithms have been tested as a solution to the Traveling Salesman Problem. In our problem, it is assumed that the 2-dimensional Euclidean space is valid. That is, distances will be calculated as 2-dimensional euclidean distances. In our problem, coordinates of 51 cities are used as dataset from a csv file. If you want, you can enter new city coordinates in the csv file or edit the already entered ones and run the algorithms again. The programs have been written to allow you to edit the dataset as you wish. 5 | And the best known solution's cost for this symmetric TSP is 426. 6 | 7 | # On how to use the codes 8 | You can edit the csv file to use any of the 3 codes with your own dataset (i.e. to change city coordinates or to add or remove new cities). 9 | 10 | To use the City Swap Algorithm code, you need to run the "testCode.py" file in the folder. The only argument you can manipulate in this code is the "dataset", and we've already mentioned how you can change it. 11 | 12 | To use the Simulated Annealing Algorithm code with your desired argument values; you can write the start temperature you want to the second argument in the main function, the desired cooling rate value (between 0-1) to the third argument in the main function, the lower bound of the temperature to the fourth, and the tolerance value of the local search to the fifth. 13 | 14 | To use the Genetic Algorithm code with the argument values you want; the first of the arguments in the main function is how many generations you want to create, the second is the number of individuals in one generation, the third is the number of parent "pairs" to be selected in parent selection, the fourth is the crossover probability for a parent pair value, the fifth is mutation probability of a child solution. 15 | 16 | Note: You can find detailed explanations of how the codes work in the comments on the lines of codes. 17 | 18 | # Comparison of algorithm performances in terms of "closeness of the best solution to the optimum" and "computational time" 19 | 20 | 1) The output of the City Swap Algorithm code is as shown below. 21 | 22 | ![image](https://user-images.githubusercontent.com/82934361/169900647-fb10fa0b-7619-471e-beaa-a8dbc55808cd.png) 23 | 24 | 2) When the following arguments are written to the main function, the output of the Simulated Annealing Algorithm code is as shown below. 25 | ![image](https://user-images.githubusercontent.com/82934361/169907189-3f5c0958-81c2-4234-83af-659759b58fba.png) 26 | 27 | ![image](https://user-images.githubusercontent.com/82934361/169902750-0e2cca4e-47df-413d-9473-574648d906ba.png) 28 | 29 | 3) When the following arguments are written to the main function, the output of the Genetic Algorithm code is as shown below. 30 | ![image](https://user-images.githubusercontent.com/82934361/169906970-3ba84ad1-7a45-4680-8194-c6ff123c3829.png) 31 | 32 | ![image](https://user-images.githubusercontent.com/82934361/169904072-0fdfb5aa-def6-477f-bee4-d0d7df74c07d.png) 33 | 34 | 4) When the following arguments are written to the main function, the output of the Genetic Algorithm code is as shown below. 35 | ![image](https://user-images.githubusercontent.com/82934361/169905468-c6044ea8-5ced-4332-9b26-611bdbd3dfe1.png) 36 | 37 | ![image](https://user-images.githubusercontent.com/82934361/169905598-890672d5-e243-46fb-9c24-503942d37c66.png) 38 | 39 | 40 | Note that the best known solution for this symmetrical TSP costs 426. 41 | 42 | As you can see in output 4, the Genetic Algorithm found the closest solution to optimum, but it took about 3 times longer than Simulated Annealing Algorithm and 12 times longer than City Swap Algorithm to reach this solution. 43 | 44 | As you can see in the 3rd output, when we changed the arguments we used in the Genetic Algorithm code, the computational time of the Genetic Algorithm became about 10 seconds shorter than the computational time of the Simulated Annealing Algorithm. However, as you can see in output 2, the Simulated Annealing Algorithm was able to produce a better solution by running 10 seconds longer. 45 | 46 | As you can see in the 1st output, the City Swap Algorithm took much less time than the remaining two algorithms, but the best solution it found is also much worse than the remaining two algorithms. 47 | 48 | Note: It should be noted that City Swap Algorithm is a greedy improvement heuristic algorithm. The situation we observed as a result of the codes we wrote confirms our theoretical knowledge about improvement algorithms. A greedy algorithm is any algorithm that follows the method of making the locally optimal choice at each stage. In many problems, a greedy strategy does not produce an optimal solution, but a greedy heuristic can find locally optimal solutions that approach a globally optimal solution in as short a time as possible. So basically in short, greedy improvement algorithms like City Swap Algorithm can be used to find a good solution to a problem as fast as possible. 49 | 50 | 51 | -------------------------------------------------------------------------------- /Simulated Annealing Algorithm/Simulated Annealing Algorithm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import numpy as np 5 | import pandas as pd 6 | import time #Required library to calculate Computational Time 7 | 8 | start_time = time.time() #Keeps the start time 9 | 10 | np.random.seed(40) 11 | 12 | dataset = pd.read_csv('dataset.csv') #Imports the csv file containing the data 13 | 14 | cities=np.array(dataset) 15 | 16 | 17 | def calculate_distance(cities , solution): 18 | solution_for_distance_calculation = np.append(solution, [solution[0]], axis=0) #Appends the city index at the beginning of the solution array to the end of the array 19 | distance = 0 20 | next_city_index_founder=0 21 | 22 | for i in solution_for_distance_calculation: #i will hold first city indexes 23 | next_city_index_founder += 1 24 | if next_city_index_founder < len(solution_for_distance_calculation): 25 | next_city_index=solution_for_distance_calculation[next_city_index_founder] #Find the second city indexes here 26 | distance += np.sqrt(((cities[next_city_index,0]-cities[i,0])**2)+((cities[next_city_index,1]-cities[i,1])**2)) #First city and second city indexes are used when calculating euclidean distance 27 | 28 | return distance 29 | 30 | def generate_solution(current_solution): #A new solution will be created by swapping two random cities in the current solution 31 | idx1 , idx2 = np.random.choice(len(current_solution),2) 32 | current_solution_copy = current_solution.copy() 33 | current_solution_copy[idx2], current_solution_copy[idx1] = current_solution_copy[idx1], current_solution_copy[idx2] 34 | return current_solution_copy 35 | 36 | 37 | 38 | def main(dataset, T, cooling_rate, T_lower_bound, tolerance): 39 | 40 | current_solution = np.random.permutation(range(len(dataset))) #A random initial solution is created using city indexes 41 | h=0 #Keeps the number of iterations 42 | 43 | while T>T_lower_bound: #We want the algorithm to run when the temperature is greater than T_lower_bound 44 | h+=1 45 | while True: #Local search will be done here; different solutions will be tried for the "same" temperature value, until new solutions give very close values ​​to the current solution (that is, until the difference in costs between the new potential solution and the current solution is less than the tolerance) 46 | potential_solution = generate_solution(current_solution) #The potential solution is created using the generate_solution function 47 | potential_distance = calculate_distance(cities , potential_solution) #The cost of the potential solution is calculated with the calculate_distance function 48 | current_distance = calculate_distance(cities , current_solution) #The cost of the current solution is calculated with the calculate_distance function 49 | 50 | 51 | if potential_distance < current_distance: #If the potential solution is better, the potential solution is accepted 52 | current_solution = potential_solution 53 | 54 | 55 | elif np.random.random() < np.exp(-(potential_distance - current_distance)/T): #Potential solution has a chance to be accepted based on a probability even if it is worse 56 | current_solution = potential_solution 57 | 58 | 59 | if np.abs(potential_distance-current_distance) < tolerance: #Local search will run until a potential solution gives a cost value very close to the current solution 60 | break 61 | 62 | 63 | T = T*cooling_rate #The temperature is updated depending on the cooling rate 64 | 65 | print("------------------------------") 66 | print("Current solution: ") 67 | print(np.append(current_solution, [current_solution[0]], axis=0)) 68 | print("Current distance: ") 69 | print(current_distance) 70 | 71 | print("--------RESULTS---------") 72 | print("Best tour founded for salesman: ", np.append(current_solution, [current_solution[0]], axis=0)) 73 | print("Distance of tour founded: ", current_distance) 74 | print("Iterations: ",h ) 75 | comp_time = time.time() - start_time #Keeps the difference between the end time and the start time 76 | print(f"-> Computational Time: {comp_time} seconds") #Prints Computational Time 77 | 78 | main(dataset,100,0.999, 0.01, 1) #(Dataset including city coordinates, Starting temperature, Cooling rate, Lower bound of temperature, Tolerance of local search) You can set these arguments as you want 79 | -------------------------------------------------------------------------------- /Simulated Annealing Algorithm/dataset.csv: -------------------------------------------------------------------------------- 1 | "x","y" 2 | "37","52" 3 | "49","49" 4 | "52","64" 5 | "20","26" 6 | "40","30" 7 | "21","47" 8 | "17","63" 9 | "31","62" 10 | "52","33" 11 | "51","21" 12 | "42","41" 13 | "31","32" 14 | "5","25" 15 | "12","42" 16 | "36","16" 17 | "52","41" 18 | "27","23" 19 | "17","33" 20 | "13","13" 21 | "57","58" 22 | "62","42" 23 | "42","57" 24 | "16","57" 25 | "8","52" 26 | "7","38" 27 | "27","68" 28 | "30","48" 29 | "43","67" 30 | "58","48" 31 | "58","27" 32 | "37","69" 33 | "38","46" 34 | "46","10" 35 | "61","33" 36 | "62","63" 37 | "63","69" 38 | "32","22" 39 | "45","35" 40 | "59","15" 41 | "5","6" 42 | "10","17" 43 | "21","10" 44 | "5","64" 45 | "30","15" 46 | "39","10" 47 | "32","39" 48 | "25","32" 49 | "25","55" 50 | "48","28" 51 | "56","37" 52 | "30","40" 53 | --------------------------------------------------------------------------------