├── 2opt.py ├── 3opt.py ├── GA.py ├── README.md ├── constheur.py └── run_TSP.py /2opt.py: -------------------------------------------------------------------------------- 1 | 2 | def CostCalculation(tour,cost_matrix): 3 | """ 4 | FUNCTION: CostCalculation 5 | 6 | DESCRIPTION: This fuction calculates the total cost of a route and returns its cost 7 | 8 | INPUT: (tour) - List containing the sequence of nodes visited 9 | (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j 10 | 11 | OUTPUT: (tour_cost) - Total cost of the input tour. 12 | """ 13 | tour_cost = 0 14 | for i in range(len(tour)-1): 15 | node_i = tour[i] 16 | node_j = tour[i+1] 17 | tour_cost = tour_cost + cost_matrix[node_i][node_j] 18 | return round(tour_cost,2) 19 | 20 | def SwapTwo(tour,i,k): 21 | """ 22 | FUNCTION: SwapTwo 23 | 24 | DESCRIPTION: This fuction recreates a tour swaping nodes in positions i and k. 25 | The functions breaks the tour in the position i-1, reconnects the position i-1 with position k and inverts the central tour to reconnect position i with k+1. 26 | 27 | INPUT: (tour) - List containing the sequence of nodes visited 28 | (i) - Position of the first node in the list 29 | (k) - Position of the second node in the list 30 | 31 | 32 | 33 | OUTPUT: (new_tour) - New tour recreated. 34 | """ 35 | 36 | first_route = tour[0:i] 37 | second_route = tour[i:k+1][::-1] 38 | third_route = tour[k+1:] 39 | new_tour = list() 40 | new_tour = first_route + second_route + third_route 41 | 42 | return new_tour 43 | 44 | def TwoOpt(tour, cost_matrix): 45 | """ 46 | FUNCTION: TwoOpt 47 | 48 | DESCRIPTION: This function applies the 2-opt algorithm to find a new tour with a lower cost than the input tour. 49 | The algorithm scans all nodes i,j and swaps two edges connecting the current tour. If an iprovement is found the tour is swapped 50 | and the new tour is returned to be evaluated for further improvements. The algorithms stops when no further improvement can be found by swapping two edges. 51 | 52 | INPUT: (tour) - List containing the sequence of nodes visited 53 | (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j 54 | 55 | OUTPUT: (tour) - New tour recreated. 56 | """ 57 | tour = tour[1:-1] 58 | size = len(tour) #length of the tour 59 | best_cost = CostCalculation([0]+tour+[0],cost_matrix) 60 | improve = 0 #auxiliary variable that stops the algorithm if no improvement is found 61 | while(improve <= 1): 62 | 63 | for i in range(0,size-1): # the depot (0) is not considered for swapping 64 | 65 | for k in range(i+1,size): # the depot (0) is not considered for swapping 66 | new_tour = SwapTwo(tour,i,k) 67 | new_cost = CostCalculation([0]+new_tour+[0],cost_matrix) 68 | if (new_cost < best_cost): 69 | 70 | tour = new_tour 71 | best_cost = new_cost 72 | improve = 0 73 | 74 | 75 | improve += 1 76 | return [0]+ tour +[0] 77 | 78 | 79 | def SwapTwoFast(tour,i,k): 80 | """ 81 | FUNCTION: SwapTwo 82 | 83 | DESCRIPTION: This fuction recreates a tour swaping nodes in positions i and k. 84 | The functions breaks the tour in the position i-1, reconnects the position i-1 with position k and inverts the central tour to reconnect position i with k+1. 85 | 86 | INPUT: (tour) - List containing the sequence of nodes visited 87 | (i) - Position of the first node in the list 88 | (k) - Position of the second node in the list 89 | 90 | 91 | 92 | OUTPUT: (new_tour) - New tour recreated. 93 | """ 94 | 95 | first_route = tour[0:i+1] 96 | second_route = tour[i+1:k+1][::-1] 97 | third_route = tour[k+1:] 98 | new_tour = list() 99 | new_tour = first_route + second_route + third_route 100 | 101 | return new_tour 102 | 103 | 104 | 105 | def TwoOptFast(tour,dist): 106 | size = len(tour) #length of the tour 107 | while True: 108 | minchange = 0 109 | for i in range(0,size-2): 110 | for j in range(i+2,size-1): 111 | change = dist[tour[i]][tour[j]] + dist[tour[i+1]][tour[j+1]]- dist[tour[i]][tour[i+1]] - dist[tour[j]][tour[j+1]] 112 | 113 | if minchange > change: 114 | 115 | 116 | minchange = change 117 | mini = i 118 | minj = j 119 | if (minchange >= 0): break 120 | tour = SwapTwoFast(tour, mini,minj) 121 | 122 | return tour,CostCalculation(tour,dist) 123 | 124 | 125 | def LSFast(tour,tour_cost,cost_matrix): 126 | # print "LS" 127 | size = len(tour) #length of the tour 128 | 129 | while (True): 130 | minchange = 0 131 | for i in range(1,size-1): 132 | for j in range(i+1,size-1): 133 | 134 | change_M1 = 0 135 | change_M2 = 0 136 | change_M3 = 0 137 | change_M4 = 0 138 | change_M5 = 0 139 | change_M6 = 0 140 | change_M7 = 0 141 | 142 | change_M1 = cost_matrix[tour[i-1]][tour[i+1]] + cost_matrix[tour[j]][tour[i]] + cost_matrix[tour[i]][tour[j+1]] - cost_matrix[tour[j]][tour[j+1]] - cost_matrix[tour[i]][tour[i+1]] - cost_matrix[tour[i-1]][tour[i]] 143 | 144 | 145 | if j >= i+2: 146 | change_M2 = cost_matrix[tour[i-1]][tour[i+2]] + cost_matrix[tour[j]][tour[i]] + cost_matrix[tour[j+1]][tour[i+1]] - cost_matrix[tour[i+1]][tour[i+2]] - cost_matrix[tour[i-1]][tour[i]] - cost_matrix[tour[j]][tour[j+1]] 147 | 148 | 149 | if j >= i+2: 150 | change_M3 = cost_matrix[tour[i-1]][tour[i+2]] + cost_matrix[tour[j]][tour[i+1]] + cost_matrix[tour[j+1]][tour[i]] - cost_matrix[tour[i+1]][tour[i+2]] - cost_matrix[tour[i-1]][tour[i]] - cost_matrix[tour[j]][tour[j+1]] 151 | 152 | if i+1 != j: 153 | enter_M4 = cost_matrix[tour[i-1]][tour[j]] + cost_matrix[tour[j]][tour[i+1]] + cost_matrix[tour[j-1]][tour[i]] + cost_matrix[tour[i]][tour[j+1]] 154 | leave_M4 = cost_matrix[tour[i-1]][tour[i]] + cost_matrix[tour[i]][tour[i+1]] +cost_matrix[tour[j]][tour[j+1]]+cost_matrix[tour[j-1]][tour[j]] 155 | change_M4 = enter_M4 - leave_M4 156 | else: 157 | enter_M4 = cost_matrix[tour[i-1]][tour[j]] + cost_matrix[tour[i]][tour[j+1]] 158 | leave_M4 = cost_matrix[tour[i-1]][tour[i]] + cost_matrix[tour[j]][tour[j+1]] 159 | change_M4 = enter_M4 - leave_M4 160 | if j > i+1: 161 | if j == i+2: 162 | enter_M5 = cost_matrix[tour[i-1]][tour[j]] + cost_matrix[tour[j]][tour[i]] + cost_matrix[tour[i+1]][tour[j+1]] 163 | leave_M5 = cost_matrix[tour[i-1]][tour[i]] + cost_matrix[tour[i+1]][tour[i+2]] +cost_matrix[tour[j]][tour[j+1]] 164 | change_M5 = enter_M5 - leave_M5 165 | else: 166 | enter_M5 = cost_matrix[tour[i-1]][tour[j]] + cost_matrix[tour[j]][tour[i+2]] + cost_matrix[tour[j-1]][tour[i]] + cost_matrix[tour[i+1]][tour[j+1]] 167 | leave_M5 = cost_matrix[tour[i-1]][tour[i]] + cost_matrix[tour[i+1]][tour[i+2]] +cost_matrix[tour[j]][tour[j+1]]+cost_matrix[tour[j-1]][tour[j]] 168 | change_M5 = enter_M5 - leave_M5 169 | 170 | if j < size-2 and j >= i+2: 171 | if j == i+2: 172 | enter_M6 = cost_matrix[tour[j+1]][tour[i]] + cost_matrix[tour[i+1]][tour[j+2]]+ cost_matrix[tour[i-1]][tour[j]] 173 | leave_M6 = cost_matrix[tour[i-1]][tour[i]] + cost_matrix[tour[i+1]][tour[i+2]]+cost_matrix[tour[j+1]][tour[j+2]] 174 | change_M6 = enter_M6 - leave_M6 175 | else: 176 | enter_M6 = cost_matrix[tour[i-1]][tour[j]] +cost_matrix[tour[j+1]][tour[i+2]] + cost_matrix[tour[j-1]][tour[i]] + cost_matrix[tour[i+1]][tour[j+2]] 177 | leave_M6 = cost_matrix[tour[i-1]][tour[i]] + cost_matrix[tour[i+1]][tour[i+2]] + cost_matrix[tour[j-1]][tour[j]] + cost_matrix[tour[j+1]][tour[j+2]] 178 | change_M6 = enter_M6 - leave_M6 179 | 180 | change_M7 = cost_matrix[tour[i]][tour[j]] + cost_matrix[tour[i+1]][tour[j+1]]- cost_matrix[tour[i]][tour[i+1]] - cost_matrix[tour[j]][tour[j+1]] 181 | 182 | change = min(change_M1,change_M2,change_M3,change_M4,change_M5,change_M6,change_M7) 183 | 184 | if change == change_M1: 185 | t = 1 186 | elif change == change_M2: 187 | t = 2 188 | elif change == change_M3: 189 | t = 3 190 | elif change == change_M4: 191 | t = 4 192 | elif change == change_M5: 193 | t = 5 194 | elif change == change_M6: 195 | t = 6 196 | elif change == change_M7: 197 | t = 7 198 | 199 | if minchange > round(change,2): 200 | # print "pure:",change 201 | # print "round:",round(change,2) 202 | minchange = change 203 | mini = i 204 | minj = j 205 | mint = t 206 | 207 | 208 | if (minchange >= 0): break 209 | if mint == 1: 210 | new_tour = tour[:mini] + tour[mini+1:minj+1] +[tour[mini]]+ tour[minj+1:] 211 | tour = new_tour 212 | elif mint == 2: 213 | new_tour = tour[:mini] +tour[mini+2:minj+1]+[tour[mini],tour[mini+1]]+ tour[minj+1:] 214 | tour = new_tour 215 | elif mint == 3: 216 | new_tour = tour[:mini] +tour[mini+2:minj+1]+[tour[mini+1],tour[mini]]+ tour[minj+1:] 217 | tour = new_tour 218 | elif mint == 4: 219 | new_tour = list(tour) 220 | aux = new_tour[mini] 221 | new_tour[mini] = new_tour[minj] 222 | new_tour[minj] = aux 223 | 224 | tour = new_tour 225 | elif mint == 5: 226 | new_tour = tour[:mini]+[tour[minj]]+ tour[mini+2:minj]+tour[mini:mini+2]+tour[minj+1:] 227 | tour = new_tour 228 | elif mint == 6: 229 | new_tour = list(tour) 230 | aux = new_tour[mini] 231 | new_tour[mini] = new_tour[minj] 232 | new_tour[minj] = aux 233 | 234 | aux = new_tour[mini+1] 235 | new_tour[mini+1] = new_tour[minj+1] 236 | new_tour[minj+1] = aux 237 | 238 | tour = new_tour 239 | 240 | elif mint == 7: 241 | new_tour = SwapTwoFast(tour,mini,minj) 242 | tour = new_tour 243 | tour_cost = tour_cost + minchange 244 | # print "LS Done" 245 | return tour,tour_cost 246 | -------------------------------------------------------------------------------- /3opt.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | def CostCalculation(tour,cost_matrix): 4 | """ 5 | FUNCTION: CostCalculation 6 | 7 | DESCRIPTION: This fuction calculates the total cost of a route and returns its cost 8 | 9 | INPUT: (tour) - List containing the sequence of nodes visited 10 | (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j 11 | 12 | OUTPUT: (tour_cost) - Total cost of the input tour. 13 | """ 14 | tour_cost = 0 15 | for i in range(len(tour)-1): 16 | node_i = tour[i] 17 | node_j = tour[i+1] 18 | tour_cost = tour_cost + cost_matrix[node_i][node_j] 19 | return tour_cost 20 | 21 | 22 | def SwapThree(tour,a,c,e,choose): 23 | """ 24 | FUNCTION: SwapThree 25 | 26 | DESCRIPTION: This fuction recreates a route by disconnecting and reconnecting 3 edges ab, cd 27 | and ef (such that the result is still a complete and feasible tour). 28 | This function only takes into account pure 3-opt moves. No 2-opt moves are allowed. 29 | 30 | INPUT: (tour) - List containing the sequence of nodes visited 31 | (a) - Position of the first node in the list 32 | (c) - Position of the second node in the list 33 | (e) - Position of the third node in the list 34 | 35 | OUTPUT: (tour_cost) - Total cost of the input tour. 36 | """ 37 | # nodes are sorted to allow a simpler implementation 38 | a, c, e = sorted([a, c, e]) 39 | b, d, f = a+1, c+1, e+1 40 | 41 | new_tour = list() 42 | # four different reconnections of tours are considered 43 | if choose == 1: 44 | new_tour = tour[:a+1] + tour[c:b-1:-1] + tour[e:d-1:-1] + tour[f:] # 3-opt 45 | elif choose == 2: 46 | new_tour = tour[:a+1] + tour[d:e+1] + tour[b:c+1] + tour[f:] # 3-opt 47 | elif choose == 3: 48 | new_tour = tour[:a+1] + tour[d:e+1] + tour[c:b-1:-1] + tour[f:] # 3-opt 49 | elif choose == 4: 50 | new_tour = tour[:a+1] + tour[e:d-1:-1] + tour[b:c+1] + tour[f:] # 3-opt 51 | 52 | return new_tour 53 | 54 | 55 | def ThreeOpt(tour, cost_matrix): 56 | """ 57 | FUNCTION: ThreeOpt 58 | 59 | DESCRIPTION: This function applies the 3-opt algorithm to find a new tour with a lower cost than the input tour. 60 | The algorithm scans all nodes a,c,e and swaps three edges connecting the current tour. All four different reconnections of the three edges are attempted and the algorithm is stopped at the first improvement. 61 | If an iprovement is found the tour is swapped and the new tour is used in the evaluation of further improvements. 62 | The algorithm stops when no further improvement can be found by swapping three edges considering one of the 4 possibilities. 63 | 64 | INPUT: (tour) - List containing the sequence of nodes visited 65 | (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j 66 | 67 | OUTPUT: (tour) - New tour recreated. 68 | """ 69 | 70 | size = len(tour) #length of the tour 71 | which = random.choice([1,2,3,4]) #One of all four possibilities of reconnecting the tour are tested 72 | 73 | best_cost = CostCalculation(tour,cost_matrix) 74 | 75 | improve = 0 76 | while(improve <= 0): 77 | for a in range(1,size-2): 78 | for c in range(a+1,size-1): 79 | for e in range(c+1,size): 80 | new_tour = SwapThree(tour,a,c,e,which) 81 | new_cost = CostCalculation(new_tour,cost_matrix) 82 | if (new_cost < best_cost): 83 | tour = new_tour 84 | best_cost = new_cost 85 | improve = 0 86 | 87 | improve += 1 88 | return tour 89 | 90 | 91 | def ThreeOptFast(tour,dist): 92 | """ 93 | FUNCTION: ThreeOpt 94 | 95 | DESCRIPTION: This function applies the 3-opt algorithm to find a new tour with a lower cost than the input tour. 96 | The algorithm scans all nodes a,c,e and swaps three edges connecting the current tour. All four different reconnections of the three edges are attempted and the algorithm is stopped at the first improvement. 97 | If an iprovement is found the tour is swapped and the new tour is used in the evaluation of further improvements. 98 | The algorithm stops when no further improvement can be found by swapping three edges considering one of the 4 possibilities. 99 | 100 | INPUT: (tour) - List containing the sequence of nodes visited 101 | (dist) - Cost matrix (full) with the associated cost of moving from node i to node j 102 | 103 | OUTPUT: (tour) - New tour recreated. 104 | """ 105 | size = len(tour) #length of the tour 106 | tour_cost = CostCalculation(tour,dist) 107 | a = random.randint(0,size-4) 108 | print "3 opt starting at", a 109 | while True: 110 | minchange = 0 111 | for i in range(a,size-3): 112 | for j in range(i+2,size-2): 113 | for k in range(j+2,size-1): 114 | 115 | change_1 = dist[tour[i]][tour[j]] + dist[tour[i+1]][tour[k]] + dist[tour[j+1]][tour[k+1]] - dist[tour[i]][tour[i+1]] - dist[tour[j]][tour[j+1]] - dist[tour[k]][tour[k+1]] 116 | change_2 = dist[tour[i]][tour[j+1]] + dist[tour[k]][tour[i+1]] + dist[tour[j]][tour[k+1]] - dist[tour[i]][tour[i+1]] - dist[tour[j]][tour[j+1]] - dist[tour[k]][tour[k+1]] 117 | change_3 = dist[tour[i]][tour[j+1]] + dist[tour[j]][tour[k]] + dist[tour[i+1]][tour[k+1]] - dist[tour[i]][tour[i+1]] - dist[tour[j]][tour[j+1]] - dist[tour[k]][tour[k+1]] 118 | change_4 = dist[tour[i]][tour[k]] + dist[tour[j+1]][tour[i+1]] + dist[tour[j]][tour[k+1]] - dist[tour[i]][tour[i+1]] - dist[tour[j]][tour[j+1]] - dist[tour[k]][tour[k+1]] 119 | best_move = min(change_1,change_2,change_3,change_4) 120 | change = best_move 121 | if best_move == change_1: 122 | which = 1 123 | elif best_move == change_2: 124 | which = 2 125 | elif best_move == change_3: 126 | which = 3 127 | else: 128 | which = 4 129 | if minchange > round(change,2): 130 | 131 | minchange = change 132 | 133 | mini = i 134 | minj = j 135 | mink = k 136 | move = which 137 | if (minchange >= 0): break 138 | tour = SwapThree(tour, mini,minj,mink,move) 139 | tour_cost = tour_cost + minchange 140 | return tour,tour_cost 141 | -------------------------------------------------------------------------------- /GA.py: -------------------------------------------------------------------------------- 1 | import random 2 | import numpy as np 3 | import time as tm 4 | import matplotlib.pyplot as plt 5 | import constheur as const 6 | import math 7 | import bisect 8 | opt2 = __import__('2opt') 9 | opt3 = __import__('3opt') 10 | 11 | 12 | 13 | def FirstTourCreation(cost_matrix): 14 | """ 15 | FUNCTION: FirstTourCreation 16 | 17 | DESCRIPTION: This function creates initial solutions for the TSP. Random solutions are created. 18 | 19 | Random Solutions: Solutions selected randomly are formed by a permutation of the client's nodes appending the depot (0) at first and last positions. 20 | 21 | INPUT: (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j. 22 | 23 | OUTPUT: (tour) - List containing the TSP tour. 24 | """ 25 | 26 | random_walk = list() 27 | random_walk = range(1,len(cost_matrix)) 28 | random.shuffle(random_walk) 29 | 30 | 31 | tour = [0]+random_walk+[0] 32 | 33 | 34 | return tour 35 | 36 | def CostCalculation(tour,cost_matrix): 37 | """ 38 | FUNCTION: CostCalculation 39 | 40 | DESCRIPTION: This function calculates the total cost of a route and returns its cost 41 | 42 | INPUT: (tour) - List containing the sequence of nodes visited 43 | (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j 44 | 45 | OUTPUT: (tour_cost) - Total cost of the input tour. 46 | """ 47 | tour_cost = 0 48 | for i in range(len(tour)-1): 49 | node_i = tour[i] 50 | node_j = tour[i+1] 51 | tour_cost = tour_cost + cost_matrix[node_i][node_j] 52 | return round(tour_cost,2) 53 | 54 | 55 | def Fitness(cost,scale = 1.0e4): 56 | """ 57 | FUNCTION: Fitness 58 | 59 | DESCRIPTION: This function calculates the tour fitness. In this case the tour fitness is defined as the tour cost 60 | 61 | INPUT: (cost) - Cost of the tour 62 | (scale) - Scale factor 63 | 64 | OUTPUT: (fitness) - Total fitness of the input tour. 65 | """ 66 | 67 | return round(cost,2) 68 | 69 | def GetFittest(population): 70 | """ 71 | FUNCTION: GetFittest 72 | 73 | DESCRIPTION: This function returns the fittest given a population 74 | 75 | INPUT: (population) - Current population 76 | 77 | OUTPUT: First postion in the population 78 | """ 79 | return population[0] 80 | 81 | 82 | 83 | def GetPopulationSize(population): 84 | """ 85 | FUNCTION: GetPopulationSize 86 | 87 | DESCRIPTION: This function returns the size given a population 88 | 89 | INPUT: (population) - Current population 90 | 91 | OUTPUT: Size of the population 92 | """ 93 | return len(population) 94 | 95 | 96 | 97 | 98 | def BiContains(pop, item): 99 | """ 100 | FUNCTION: BiContains 101 | 102 | DESCRIPTION: Bisection to find an item in a list. 103 | 104 | INPUT: (pop) - Current population 105 | (item) - Lookup item 106 | 107 | OUTPUT: True - If item in pop. False - Otherwise 108 | """ 109 | fit, tour,cost = zip(*pop) 110 | index = bisect.bisect_left(cost, item) 111 | return (item <= cost[-1]) and (cost[index] == item) 112 | 113 | 114 | 115 | def InitialPopulation(n,cost_matrix): 116 | """ 117 | FUNCTION: InitialPopulation 118 | 119 | DESCRIPTION: This function creates a list of N members to form a population 120 | 121 | INPUT: (n) - Number of individuals required in the initial population 122 | (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j. 123 | 124 | 125 | OUTPUT: (sorted(pop)) - Sorted population by Fitness value 126 | """ 127 | pop = list() 128 | #Clarke and Wright 129 | tour_cw = const.ClarkeWright(cost_matrix) 130 | # print len(tour_cw) 131 | cost_cw = CostCalculation(tour_cw,cost_matrix) 132 | fitness_cw = Fitness(cost_cw) 133 | pop.append((fitness_cw,tour_cw,cost_cw)) 134 | 135 | #Nearest Neighbour 136 | tour_nn = const.NN(cost_matrix) 137 | # print len(tour_nn) 138 | cost_nn = CostCalculation(tour_nn,cost_matrix) 139 | fitness_nn = Fitness(cost_nn) 140 | if fitness_nn != fitness_cw: 141 | pop.append((fitness_nn,tour_nn,cost_nn)) 142 | 143 | #Random Insertion 144 | tour_ri = const.RandomInsertion(cost_matrix) 145 | # print len(tour_ri) 146 | cost_ri = CostCalculation(tour_ri,cost_matrix) 147 | fitness_ri = Fitness(cost_ri) 148 | if fitness_ri != fitness_cw and fitness_ri != fitness_nn: 149 | pop.append((fitness_ri,tour_ri,cost_ri)) 150 | 151 | pop = sorted(pop) 152 | 153 | i = 3 154 | count = 0 155 | 156 | while (i < n): 157 | tour = FirstTourCreation(cost_matrix) 158 | cost = CostCalculation(tour,cost_matrix) 159 | fitness = Fitness(cost) 160 | 161 | insert = 1 162 | if len(pop) != 0: 163 | if (BiContains(pop,cost)): 164 | insert = 0 165 | 166 | if insert == 1: 167 | pop.append((fitness,tour,cost)) 168 | i = i+1 169 | 170 | count = count + 1 171 | if count >= 4*n: 172 | raise ValueError("Could not form the entire population!") 173 | 174 | pop = sorted(pop) 175 | return pop 176 | 177 | 178 | def Rotate(l,n): 179 | """ 180 | FUNCTION: Rotate 181 | 182 | DESCRIPTION: This function rotates a list given a new start position. 183 | 184 | INPUT: (l) - List containing a TSP tour 185 | (n) - New position of the first element 186 | 187 | 188 | OUTPUT: Rotated list 189 | """ 190 | return l[-n:] + l[:-n] 191 | 192 | def OXCrossover(parent_1,parent_2): 193 | """ 194 | FUNCTION: OXCrossover 195 | 196 | DESCRIPTION: This function performs an ordered crossover between two tours. 197 | First, a random slice is swapped between the two tours, as in a two-point crossover. Second, repeated cities 198 | not in the swapped area are removed, and the remaining integers are added 199 | from the other tour, in the order that they appear starting from the end 200 | index of the swapped section. 201 | 202 | INPUT: (parent_1) - List containing a TSP tour 203 | (parent_2) - List containing a TSP tour 204 | 205 | 206 | OUTPUT: (child_1,child_2) - Two offsprings generated by the input parents 207 | """ 208 | 209 | parent_1 = list(parent_1[1:-1]) 210 | parent_2 = list(parent_2[1:-1]) 211 | 212 | n = len(parent_1) 213 | 214 | 215 | start = random.randint(0,n-1) 216 | end = random.randint(start+1,n) 217 | 218 | child_1 = list() 219 | child_2 = list() 220 | 221 | 222 | child_1[start:end] = parent_1[start:end] 223 | child_2[start:end] = parent_2[start:end] 224 | 225 | 226 | 227 | for i in range(0,n): 228 | current_index = (end + i) % n 229 | 230 | current_node_parent_1 = parent_1[current_index] 231 | current_node_parent_2 = parent_2[current_index] 232 | 233 | if current_node_parent_2 not in child_1: 234 | child_1.append(current_node_parent_2) 235 | 236 | if current_node_parent_1 not in child_2: 237 | child_2.append(current_node_parent_1) 238 | 239 | child_1 = Rotate(child_1,start) 240 | child_2 = Rotate(child_2,start) 241 | 242 | child_1 = [0]+child_1+[0] 243 | child_2 = [0]+child_2+[0] 244 | return child_1,child_2 245 | 246 | def SelectParent(pop): 247 | """ 248 | FUNCTION: SelectParent 249 | 250 | DESCRIPTION: This function performs the parent selection, given a population. 251 | Parents are selected randomly. 252 | 253 | INPUT: (pop) - Population of the Genetic Algortihm 254 | 255 | OUTPUT: ([fit,tour,cost]) - An entry o the GA population containing the fitness, tour and cost 256 | """ 257 | index = random.choice(range(0,len(pop))) 258 | return pop[index] 259 | 260 | 261 | 262 | 263 | def Mutation(child,child_cost,cost_matrix): 264 | """ 265 | FUNCTION: Mutation 266 | 267 | DESCRIPTION: This function performs the mutation in the GA. Mutation is performed by firt attempting a LS move. 268 | a 3-opt move is attempted with probability (p). 269 | 270 | INPUT: (child) - TSP tour 271 | (child_cost) - cost of the TSP tour 272 | (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j. 273 | 274 | OUTPUT: (mutation) - An entry o the GA population containing the tour 275 | (mutation_cost) - An entry o the GA population containing the cost 276 | """ 277 | 278 | 279 | mutation,mutation_cost = opt2.LSFast(child,child_cost,cost_matrix) 280 | 281 | r = random.uniform(0,1) 282 | if r < 0.5: 283 | mutation,mutation_cost = opt3.ThreeOptFast(mutation,cost_matrix) 284 | 285 | return mutation,mutation_cost 286 | 287 | def GA(cost_matrix,population_size, population, mutation_rate, number_success, number_unsuccess ): 288 | """ 289 | FUNCTION: GA 290 | 291 | DESCRIPTION: This function performs the Genetic Algorithm. 292 | The algorithm selects two parents and generates two offsprings (children) using the OX crossover procedure. 293 | The child is selected and mutation can happen with probability = mutation rate. 294 | The new child is added to the new population if there is no copies present in the population (to maintain variability). 295 | Thealgorithm stops when the entire population success or no_improve their max. 296 | 297 | INPUT: (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j. 298 | (population_size) - Size of the desired population 299 | (population) - current population 300 | (mutation_rate) - Probability of having a mutation in the best offspring 301 | (number_success) - max number of sucessful offspring 302 | (number_unsuccess) - max number of unsucessful offspring 303 | 304 | 305 | OUTPUT: (population) - A GA population containing fitness, tour and cost for al ts members. 306 | """ 307 | 308 | success = 0 309 | no_improve = 0 310 | while (success < number_success and no_improve < number_unsuccess): 311 | current_best = population[0] 312 | parent_1_candidate_1 = SelectParent(population) 313 | parent_1_candidate_2 = SelectParent(population) 314 | 315 | 316 | if parent_1_candidate_1[2] < parent_1_candidate_2[2]: 317 | parent_1 = parent_1_candidate_1[1] 318 | 319 | else: 320 | parent_1 = parent_1_candidate_2[1] 321 | 322 | parent_2_candidate_1 = SelectParent(population) 323 | parent_2_candidate_2 = SelectParent(population) 324 | 325 | 326 | if parent_2_candidate_1[2] < parent_2_candidate_2[2]: 327 | parent_2 = parent_2_candidate_1[1] 328 | else: 329 | parent_2 = parent_2_candidate_2[1] 330 | 331 | 332 | 333 | child_1,child_2 = OXCrossover(parent_1,parent_2) 334 | 335 | which_child = random.choice([1,2]) 336 | 337 | if which_child == 1 : 338 | child = list(child_1) 339 | else: 340 | child = list(child_2) 341 | 342 | rand = random.uniform(0, 1) 343 | 344 | child_cost = CostCalculation(child,cost_matrix) 345 | if rand < mutation_rate: 346 | child,child_cost = Mutation(child,child_cost,cost_matrix) 347 | 348 | child_fitness = Fitness(child_cost) 349 | 350 | k = random.randint(math.floor(population_size/2.0),population_size-1) 351 | 352 | add = 1 353 | if BiContains(population,child_cost): 354 | add = 0 355 | 356 | if add == 1 and child_cost < population[k][2] : 357 | population[k] = (child_fitness,child,child_cost) 358 | success = success + 1 359 | 360 | population = sorted(population) 361 | if population[0] == current_best: 362 | no_improve += 1 363 | # print no_improve 364 | 365 | print "number of success....", success 366 | print "no improvement....", no_improve 367 | 368 | return population 369 | 370 | 371 | def PrintResults(matrix,fittest,solutions,time,plot = True): 372 | """ 373 | FUNCTION: PrintResults 374 | 375 | DESCRIPTION: This function prints the results of the GA. 376 | 377 | INPUT: (matrix) - Cost matrix (full) with the associated cost of moving from node i to node j. 378 | (fittest) - [fit,tour,cost] of the best solution found 379 | (solutions) - list with solutionf at each generation 380 | (time) - Running time of the algorithm 381 | (plot) - Boolean to define show a graph of the solution's evolution 382 | 383 | OUTPUT: None. 384 | """ 385 | print "The TSP has",len(matrix), "nodes, including the depot (0) and", len(matrix)-1,"clients." 386 | print "The first population's best solution was:",solutions[0] 387 | print "The algorithm took ",round(time,2),"(s) to run." 388 | print "It has generated", len(solutions), "restarts." 389 | print "The best tour found is", fittest[1] 390 | print "The best cost found is", fittest[2] 391 | 392 | if plot: 393 | print "\n" 394 | print "Solution Value x Restart" 395 | plt.plot(solutions) 396 | plt.show() 397 | plt.close() 398 | 399 | 400 | 401 | 402 | def ImproveGA(pop,cost_matrix): 403 | """ 404 | FUNCTION: ImproveGA 405 | Every time the GA is restarted this function is called to improve some of its solutions. 406 | The function attempts to create 8 new solutions and checks for each one of them if they are better then the worst solution. 407 | If so, include the solution. If not, we crossover with the entire population until we find a solution to substitue. 408 | 409 | DESCRIPTION: 410 | INPUT: (cost_matrix) - Cost matrix (full) with the associated cost of moving from node i to node j. 411 | 412 | (pop) - current population 413 | 414 | 415 | 416 | OUTPUT: (new_pop) - A GA population containing fitness, tour and cost for al ts members. 417 | """ 418 | 419 | new_tours = [] 420 | for i in range(0,8): 421 | tour = FirstTourCreation(cost_matrix) 422 | cost = CostCalculation(tour,cost_matrix) 423 | fitness = Fitness(cost) 424 | new_tours.append((fitness,tour,cost)) 425 | new_tours = sorted(new_tours) 426 | 427 | new_pop = list(pop) 428 | 429 | for fit,tour,cost in new_tours: 430 | worst = new_pop[-1] 431 | if cost < worst[2] and BiContains(new_pop,cost) == False: 432 | new_pop[-1] = (fit,tour,cost) 433 | 434 | new_pop = sorted(new_pop) 435 | 436 | else: 437 | for pop_fit,pop_tour,pop_cost in new_pop: 438 | 439 | child_1,child_2 = OXCrossover(tour,pop_tour) 440 | child_1_cost = CostCalculation(child_1,cost_matrix) 441 | child_2_cost = CostCalculation(child_2,cost_matrix) 442 | if child_1_cost < child_2_cost: 443 | 444 | child = child_1 445 | child_cost = child_1_cost 446 | else: 447 | child = child_2 448 | child_cost = child_2_cost 449 | if (child_cost < worst[2] and BiContains(new_pop,child_cost) == False): 450 | new_pop[-1] = (Fitness(child_cost),child,child_cost) 451 | new_pop = sorted(new_pop) 452 | break 453 | 454 | return new_pop 455 | 456 | 457 | 458 | 459 | 460 | 461 | def main(matrix,pop_size,num_of_restarts = 5, max_restart_no_improvement = 5,mutation_rate = 0.005, num_of_success = 500,number_of_unsuccess = 250,show= True ): 462 | """ 463 | FUNCTION: main 464 | 465 | DESCRIPTION: This function runs the Genetic Algorithm. The GA needs a set o parameters to be initiated. 466 | The cost_matrix, the population size, the number of generations, the maximum number of generations where no improvement is seen, 467 | mutation rate, elitism and printing the solution. The algorithm first generates an initail population. Sobsequeten populations are generated 468 | until the maximum number of generations or the maximum number of generations with no improvements is reached. The best solutions of each generations are saved. 469 | 470 | INPUT: (matrix) - Cost matrix (full) with the associated cost of moving from node i to node j. 471 | (pop_size) - Number of distinct individual in each population 472 | (num_of_restarts) - Max. number of restarts (default = 5) 473 | (max_restart_no_improvement) - Max. number of restarts with no improvemnt (default = 5). 474 | (mutation_rate) - Probability of having a mutation in the best offspring 475 | (number_success) - max number of sucessful offspring 476 | (number_unsuccess) - max number of unsucessful offspring 477 | 478 | 479 | (show) - Boolean that prints the results on the screen 480 | OUTPUT: None. 481 | """ 482 | time_start = tm.time() 483 | 484 | 485 | 486 | best_solution = [] 487 | 488 | pop = InitialPopulation(pop_size,matrix) 489 | best_solution.append(GetFittest(pop)[2]) 490 | print "Population: 0 created" 491 | 492 | 493 | count_no_improve = 0 494 | for i in range(1,num_of_restarts+1): 495 | print "GA run: ",i, "created" 496 | if i > 1: 497 | pop = ImproveGA(pop,matrix) 498 | pop = GA(matrix,pop_size,pop,mutation_rate,num_of_success,number_of_unsuccess) 499 | 500 | best_solution.append(GetFittest(pop)[2]) 501 | 502 | if best_solution[i] == best_solution[i-1]: 503 | count_no_improve += 1 504 | else: 505 | count_no_improve = 0 506 | 507 | if count_no_improve == max_restart_no_improvement: 508 | break 509 | 510 | time_finish = tm.time() 511 | total_time = time_finish - time_start 512 | if show: 513 | PrintResults(matrix,GetFittest(pop),best_solution,total_time) 514 | 515 | return GetFittest(pop),total_time 516 | 517 | 518 | 519 | def GenerateMatrix(a_text_file = False, a_matrix = False): 520 | """ 521 | FUNCTION: GenerateMatrix 522 | 523 | DESCRIPTION: This function transforms a np.array matrix in a list. 524 | 525 | INPUT: (a_text_file) - Text file cost matrix (full) with the associated cost of moving from node i to node j. 526 | (a_matrix) - Python cost matrix (full) with the associated cost of moving from node i to node j. 527 | OUTPUT: None. 528 | """ 529 | 530 | if a_text_file: 531 | matrix = np.genfromtxt(a_text_file, dtype='float64') 532 | mean = np.nanmean(matrix) 533 | matrix[np.isnan(matrix)] = mean 534 | matrix = matrix.tolist() 535 | for i in range(0,len(matrix)): 536 | for j in range(0,len(matrix)): 537 | matrix[i][j] = round(matrix[i][j],2) 538 | return matrix 539 | 540 | else: 541 | matrix = a_matrix 542 | mean = np.nanmean(matrix) 543 | matrix[np.isnan(matrix)] = mean 544 | matrix = matrix.tolist() 545 | for i in range(0,len(matrix)): 546 | for j in range(0,len(matrix)): 547 | matrix[i][j] = round(matrix[i][j],2) 548 | 549 | 550 | return matrix 551 | 552 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Genetic Algorithm for a Green Vehicle Routing Problem 2 | 3 | Implementation of the paper "A Genetic Algorithm for a Green Vehicle Routing Problem" Paulo R de Oliveira da Costa, Stefano Mauceri, Paula Carroll and Fabiano Pallonetto. 4 | https://www.sciencedirect.com/science/article/abs/pii/S1571065318300088 5 | -------------------------------------------------------------------------------- /constheur.py: -------------------------------------------------------------------------------- 1 | import random 2 | import numpy as np 3 | import networkx as nx 4 | 5 | def NN(A): 6 | """Nearest neighbor algorithm. 7 | A is an NxN array indicating distance between N locations 8 | start is the index of the starting location 9 | Returns the path and cost of the found solution 10 | """ 11 | A = np.array(A) 12 | path = [0] 13 | cost = 0 14 | N = A.shape[0] 15 | mask = np.ones(N, dtype=bool) # boolean values indicating which 16 | # locations have not been visited 17 | mask[0] = False 18 | 19 | for i in range(N-1): 20 | last = path[-1] 21 | next_ind = np.argmin(A[last][mask]) # find minimum of remaining locations 22 | next_loc = np.arange(N)[mask][next_ind] # convert to original location 23 | path.append(next_loc) 24 | mask[next_loc] = False 25 | cost += A[last, next_loc] 26 | 27 | 28 | return path+[0] 29 | 30 | def InsertionCost(matrix,i,k,j): 31 | return matrix[i][k] + matrix[k][j] - matrix[i][j] 32 | 33 | def RandomInsertion(matrix): 34 | n = len(matrix) 35 | nodes = set(range(0,n)) 36 | 37 | start_node = random.randint(1,n-1) 38 | tour =[0,start_node,0] 39 | 40 | while (len(tour) < n+1): 41 | best_cost = np.Inf 42 | tour_set = set(tour) 43 | remaining_nodes = nodes - tour_set 44 | k = random.choice(list(remaining_nodes)) 45 | 46 | for i in range(0,len(tour)-1): 47 | j=i+1 48 | 49 | cost = InsertionCost(matrix,tour[i],k,tour[j]) 50 | if cost < best_cost: 51 | best_cost = cost 52 | start = i 53 | end = j 54 | tour = tour[:start+1]+[k]+tour[end:] 55 | return tour 56 | 57 | def ClarkeWright(matrix): 58 | n = len(matrix) 59 | # print "size", n 60 | depot = 0 61 | 62 | tours = [] 63 | for node in range(1,n): 64 | tours.append([node,depot,node]) 65 | # print "tours", tours 66 | savings=[] 67 | 68 | for i in range(0,n-1): 69 | for j in range(i+1,n): 70 | saving = matrix[0][i] + matrix[0][j] - matrix[i][j] 71 | savings.append((saving,i,j)) 72 | 73 | savings = sorted(savings,reverse=True) 74 | # print savings 75 | new_tour =[] 76 | while True: 77 | for save,i,j in savings: 78 | if new_tour == []: 79 | tour_1 = tours[i-1] 80 | tour_2 = tours[j-1] 81 | new_tour = tour_1[1:]+tour_2[:-1] 82 | 83 | # print new_tour 84 | elif new_tour[-2] == j and i not in new_tour: 85 | new_tour = new_tour[:-1]+tours[i-1][:-1] 86 | # print new_tour 87 | elif new_tour[1] == j and i not in new_tour: 88 | new_tour = tours[i-1][1:]+new_tour[1:] 89 | # print new_tour 90 | elif new_tour[-2] == i and j not in new_tour: 91 | new_tour = new_tour[:-1]+tours[j-1][:-1] 92 | # print new_tour 93 | elif new_tour[1] == i and j not in new_tour: 94 | new_tour = tours[j-1][1:]+new_tour[1:] 95 | # print new_tour 96 | if len(new_tour) >= n+1:break 97 | # print len(new_tour) 98 | # print len(set(new_tour)) 99 | return new_tour 100 | 101 | def MSTPreOrder(matrix): 102 | numpy_matrix = np.array(matrix) 103 | G = nx.from_numpy_matrix(numpy_matrix) 104 | T = nx.minimum_spanning_tree(G) 105 | return list(nx.dfs_preorder_nodes(T,0))+[0] -------------------------------------------------------------------------------- /run_TSP.py: -------------------------------------------------------------------------------- 1 | import GA 2 | 3 | if __name__ == '__main__': 4 | 5 | drivers = ["H430","M200","M2631","M452356","K510","N232631","P2652","T520","W420"] 6 | #drivers = ["W420"] 7 | 8 | path = '' 9 | 10 | 11 | final = [] 12 | for driver in drivers: 13 | results = [] 14 | print "driver", driver 15 | MR = GA.GenerateMatrix(path+str(driver)+"_distance.txt") 16 | if len(MR) < 100: 17 | results.append(GA.main(MR,30,3,3,0.1,1000,1000)) 18 | MS = GA.GenerateMatrix(path+str(driver)+"_matrix_with_speed.txt") 19 | results.append(GA.main(MS,30,3,3,0.1,1000,1000)) 20 | MG = GA.GenerateMatrix(path+str(driver)+"_matrix_with_load_gradient.txt") 21 | results.append(GA.main(MG,30,3,3,0.1,1000,1000)) 22 | ME =GA.GenerateMatrix(path+str(driver)+"_EU_2020.txt") 23 | print results 24 | table = [] 25 | for sol in results: 26 | table.append((GA.CostCalculation(sol[0][1],MR),GA.CostCalculation(sol[0][1],ME),GA.CostCalculation(sol[0][1],MS),GA.CostCalculation(sol[0][1],MG))) 27 | final.append(table) 28 | --------------------------------------------------------------------------------