-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTSP.py
More file actions
203 lines (161 loc) · 7.68 KB
/
Copy pathTSP.py
File metadata and controls
203 lines (161 loc) · 7.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
##
# Bespoke module created to solve the 2D generalised travelling salesman
# problem via a genetic algorithm, generalised to allow for comparisons of
# GA parameters.
# Future additions to add multiple selection and crossover processes.
#%% PREAMBLE
import numpy as np
import random
import matplotlib.pyplot as plt
#%% FUNCTION DEFINITIONS
def makeTowns(nTowns, xMin, xMax, yMin, yMax, RNG):
towns = []
if RNG == False:
random.seed(a=1)
for i in range(nTowns):
towns.append([random.uniform(xMin, xMax), random.uniform(yMin, yMax)])
random.seed()
return towns
def createPopulation(numberOfTowns, popSize):
population = []
sampleList = range(numberOfTowns)
for i in range(popSize):
population.append(random.sample(sampleList, numberOfTowns))
return population
def calcFitness(idv, towns):
# fitness is the cumulative Euclidean distance between each city in the
# route order, including the distance between the last and first cities.
fitness = 0 #initialise fitness
for i in range(len(idv)-1):
xDiff = towns[idv[i]][0] - towns[idv[i+1]][0]
yDiff = towns[idv[i]][1] - towns[idv[i+1]][1]
fitness += np.sqrt(xDiff**2 + yDiff**2)
# Add length from last city, back to first city
xDiff = towns[idv[0]][0] - towns[idv[-1]][0]
yDiff = towns[idv[0]][1] - towns[idv[-1]][1]
fitness += np.sqrt(xDiff**2 + yDiff**2)
return fitness
def determineFitnesses(population, towns):
fitnesses = []
for i in range(len(population)):
fitnesses.append(calcFitness(population[i], towns))
return fitnesses
def initialise(nTowns, nPop, xLowerBound, xUpperBound, yLowerBound,
yUpperBound, RNG):
towns = makeTowns(nTowns, xLowerBound, xUpperBound, yLowerBound,
yUpperBound, RNG) # create towns
population = createPopulation(nTowns, nPop) # create initial population
fitnesses = determineFitnesses(population, towns) # determine fitnesses
# of initial population
return [towns, population, fitnesses]
def elitism(population, fitnessList, eliteRate):
A = list(range(len(population))) # create range of population
zipped = zip(fitnessList, A) # zip together pop range and fitnesses
B = sorted(zipped) # sort by fitnesses
topNum = int(np.round(eliteRate*len(population)))
elites = []
others = []
for i in range(topNum):
elites.append(population[B[i][1]])
for j in range(len(population)-topNum):
others.append(population[B[j][1]])
return [elites, others]
def selection(others):
#
selected = others
return selected
def crossover(matingPool):
# single point crossover
newPop = []
for i in range(int(len(matingPool)/2)):
[parentA, parentB] = random.sample(matingPool,2) # pick parents
matingPool.remove(parentA) # remove parents from mating pool
matingPool.remove(parentB)
crossPoint = random.randint(0, len(parentA)-1) # pick crossover point.
# Crossover for child A
childA = [len(parentA)+1]*len(parentA) # make child
childA[crossPoint:] = parentB[crossPoint:] # add in section from parent
childA[:crossPoint] = [gene for gene in parentA if gene not in childA]
# list comprehension, find genes in parentA no already in child, and
# keep in original order, add them to the start of the child.
# Repeat for child B
childB = [len(parentB)+1]*len(parentB) # make child
childB[crossPoint:] = parentA[crossPoint:] # add in section from parent
childB[:crossPoint] = [gene for gene in parentB if gene not in childB]
# list comprehension, find genes in parentA no already in child, and
# keep in original order, add them to the start of the child.
newPop.append(childA) # Add children to new population
newPop.append(childB)
return newPop
def mutation(population, mutationRate):
newPop = population[:]
for i in range(len(population)): # For each individual in pop
if random.random() <= mutationRate: # decide if mutation occurs
a = random.randint(0, len(population[i])-1) # pick where mutation occurs 1
b = random.randint(0, len(population[i])-1) # pick second
gene1 = population[i][a] # first city to swap
gene2 = population[i][b] # second city to swap
newPop[i][a] = gene2 # swap cities
newPop[i][b] = gene1
return newPop
def runGA(population, towns, eliteRate, mutationRate, iterationMax,
convergenceCriteria):
# Run the algorithm
bestIndividuals = []
bestFitnesses = []
globalBestFitness = [1e12]
residual, iteration = 0,0
while (residual > convergenceCriteria) + (iteration > iterationMax) == 0:
fitnesses = determineFitnesses(population=population[:], towns=towns) # determine fitnesses
[elites, others] = elitism(population=population[:],
fitnessList=fitnesses, eliteRate=eliteRate)
# determine elites & others where others = population - elites
bestIndividuals.append(elites[0]) # Add best current individual to list
# Calculate and store the current best fitness for convergence.
currentBestFitness = determineFitnesses(
population=[elites[0]], towns=towns)
bestFitnesses.append(currentBestFitness)
selected = selection(others=others[:]) # carry out selection on others
crossed = crossover(matingPool=selected[:]) # carry out crossover on
# selected individuals.
newPop = elites + crossed # newPopulation is the elite individuals
# plus the result of the crossover
newPop = mutation(population=newPop[:], mutationRate=mutationRate)
# carry out mutation on the new population
# compare the last X values of the best individuals, where X is
# controlled by convergenceCriteria. If the best fitnesses hasn't
# changed assume converged.
if len(bestIndividuals)>convergenceCriteria:
lastX = bestIndividuals[-convergenceCriteria:]
lastXF = determineFitnesses(population=lastX[:], towns=towns)
if np.sum(lastXF/lastXF[-1]) == convergenceCriteria:
residual = 1
if currentBestFitness < globalBestFitness:
residual = 0
globalBestFitness = currentBestFitness
else:
residual += 1
population = newPop[:]
iteration += 1
finalFitnesses = determineFitnesses(population=population[:], towns=towns)
return population, finalFitnesses, bestIndividuals, bestFitnesses
def plotCitiesRoute(towns, individual, colour, figNum, route):
plt.figure(figNum)
i = 0
for town in towns:
plt.scatter(town[0],town[1],s=50, c='r', marker='H')
cityName = 'city%s'%i
plt.annotate(cityName, town)
i += 1
plt.grid('on')
plt.axis([0,100,0,100])
if route == True:
for i in range(len(individual)):
fromTown = individual[i]
if i == len(individual)-1:
toTown = individual[0]
else:
toTown = individual[i+1]
dx = towns[toTown][0]-towns[fromTown][0]
dy = towns[toTown][1]-towns[fromTown][1]
plt.arrow(towns[fromTown][0], towns[fromTown][1], dx, dy, color=colour)