forked from 7ossam81/EvoloPy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGA.py
411 lines (328 loc) · 11.8 KB
/
GA.py
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
"""
Created on Sat Feb 24 20:18:05 2019
@author: Raneem
"""
import numpy
import random
import time
import sys
from solution import solution
def crossoverPopulaton(population, scores, popSize, crossoverProbability, keep):
"""
The crossover of all individuals
Parameters
----------
population : list
The list of individuals
scores : list
The list of fitness values for each individual
popSize: int
Number of chrmosome in a population
crossoverProbability: float
The probability of crossing a pair of individuals
keep: int
Number of best individuals to keep without mutating for the next generation
Returns
-------
N/A
"""
#initialize a new population
newPopulation = numpy.empty_like(population)
newPopulation[0:keep] = population[0:keep]
#Create pairs of parents. The number of pairs equals the number of individuals divided by 2
for i in range(keep, popSize, 2):
#pair of parents selection
parent1, parent2 = pairSelection(population, scores, popSize)
crossoverLength = min(len(parent1), len(parent2))
parentsCrossoverProbability = random.uniform(0.0, 1.0)
if parentsCrossoverProbability < crossoverProbability:
offspring1, offspring2 = crossover(crossoverLength, parent1, parent2)
else:
offspring1 = parent1.copy()
offspring2 = parent2.copy()
#Add offsprings to population
newPopulation[i] = numpy.copy(offspring1)
newPopulation[i + 1] = numpy.copy(offspring2)
return newPopulation
def mutatePopulaton(population, popSize, mutationProbability, keep, lb, ub):
"""
The mutation of all individuals
Parameters
----------
population : list
The list of individuals
popSize: int
Number of chrmosome in a population
mutationProbability: float
The probability of mutating an individual
keep: int
Number of best individuals to keep without mutating for the next generation
lb: int
lower bound limit
ub: int
Upper bound limit
Returns
-------
N/A
"""
for i in range(keep, popSize):
#Mutation
offspringMutationProbability = random.uniform(0.0, 1.0)
if offspringMutationProbability < mutationProbability:
mutation(population[i], len(population[i]), lb, ub)
def elitism(population, scores, bestIndividual, bestScore):
"""
This melitism operator of the population
Parameters
----------
population : list
The list of individuals
scores : list
The list of fitness values for each individual
bestIndividual : list
An individual of the previous generation having the best fitness value
bestScore : float
The best fitness value of the previous generation
Returns
-------
N/A
"""
# get the worst individual
worstFitnessId = selectWorstIndividual(scores)
#replace worst cromosome with best one from previous generation if its fitness is less than the other
if scores[worstFitnessId] > bestScore:
population[worstFitnessId] = numpy.copy(bestIndividual)
scores[worstFitnessId] = numpy.copy(bestScore)
def selectWorstIndividual(scores):
"""
It is used to get the worst individual in a population based n the fitness value
Parameters
----------
scores : list
The list of fitness values for each individual
Returns
-------
int
maxFitnessId: The individual id of the worst fitness value
"""
maxFitnessId = numpy.where(scores == numpy.max(scores))
maxFitnessId = maxFitnessId[0][0]
return maxFitnessId
def pairSelection(population, scores, popSize):
"""
This is used to select one pair of parents using roulette Wheel Selection mechanism
Parameters
----------
population : list
The list of individuals
scores : list
The list of fitness values for each individual
popSize: int
Number of chrmosome in a population
Returns
-------
list
parent1: The first parent individual of the pair
list
parent2: The second parent individual of the pair
"""
parent1Id = rouletteWheelSelectionId(scores, popSize)
parent1 = population[parent1Id].copy()
parent2Id = rouletteWheelSelectionId(scores, popSize)
parent2 = population[parent2Id].copy()
return parent1, parent2
def rouletteWheelSelectionId(scores, popSize):
"""
A roulette Wheel Selection mechanism for selecting an individual
Parameters
----------
scores : list
The list of fitness values for each individual
popSize: int
Number of chrmosome in a population
Returns
-------
id
individualId: The id of the individual selected
"""
##reverse score because minimum value should have more chance of selection
reverse = max(scores) + min(scores)
reverseScores = reverse - scores.copy()
sumScores = sum(reverseScores)
pick = random.uniform(0, sumScores)
current = 0
for individualId in range(popSize):
current += reverseScores[individualId]
if current > pick:
return individualId
def crossover(individualLength, parent1, parent2):
"""
The crossover operator of a two individuals
Parameters
----------
individualLength: int
The maximum index of the crossover
parent1 : list
The first parent individual of the pair
parent2 : list
The second parent individual of the pair
Returns
-------
list
offspring1: The first updated parent individual of the pair
list
offspring2: The second updated parent individual of the pair
"""
# The point at which crossover takes place between two parents.
crossover_point = random.randint(0, individualLength - 1)
# The new offspring will have its first half of its genes taken from the first parent and second half of its genes taken from the second parent.
offspring1 = numpy.concatenate([parent1[0:crossover_point],parent2[crossover_point:]])
# The new offspring will have its first half of its genes taken from the second parent and second half of its genes taken from the first parent.
offspring2 = numpy.concatenate([parent2[0:crossover_point],parent1[crossover_point:]])
return offspring1, offspring2
def mutation(offspring, individualLength, lb, ub):
"""
The mutation operator of a single individual
Parameters
----------
offspring : list
A generated individual after the crossover
individualLength: int
The maximum index of the crossover
lb: int
lower bound limit
ub: int
Upper bound limit
Returns
-------
N/A
"""
mutationIndex = random.randint(0, individualLength - 1)
mutationValue = random.uniform(lb, ub)
offspring[mutationIndex] = mutationValue
def clearDups(Population, lb, ub):
"""
It removes individuals duplicates and replace them with random ones
Parameters
----------
objf : function
The objective function selected
lb: int
lower bound limit
ub: int
Upper bound limit
Returns
-------
list
newPopulation: the updated list of individuals
"""
newPopulation = numpy.unique(Population, axis=0)
oldLen = len(Population)
newLen = len(newPopulation)
if newLen < oldLen:
nDuplicates = oldLen - newLen
newPopulation = numpy.append(newPopulation, numpy.random.uniform(0,1,(nDuplicates,len(Population[0]))) *(ub-lb)+lb, axis=0)
return newPopulation
def calculateCost(objf, population, popSize, lb, ub):
"""
It calculates the fitness value of each individual in the population
Parameters
----------
objf : function
The objective function selected
population : list
The list of individuals
popSize: int
Number of chrmosomes in a population
lb: int
lower bound limit
ub: int
Upper bound limit
Returns
-------
list
scores: fitness values of all individuals in the population
"""
scores = numpy.full(popSize, numpy.inf)
#Loop through individuals in population
for i in range(0,popSize):
# Return back the search agents that go beyond the boundaries of the search space
population[i,:]=numpy.clip(population[i,:], lb, ub)
# Calculate objective function for each search agent
scores[i] = objf(population[i,:])
return scores
def sortPopulation(population, scores):
"""
This is used to sort the population according to the fitness values of the individuals
Parameters
----------
population : list
The list of individuals
scores : list
The list of fitness values for each individual
Returns
-------
list
population: The new sorted list of individuals
list
scores: The new sorted list of fitness values of the individuals
"""
sortedIndices = scores.argsort()
population = population[sortedIndices]
scores = scores[sortedIndices]
return population, scores
def GA(objf,lb,ub,dim,popSize,iters):
"""
This is the main method which implements GA
Parameters
----------
objf : function
The objective function selected
lb: int
lower bound limit
ub: int
Upper bound limit
dim: int
The dimension of the indivisual
popSize: int
Number of chrmosomes in a population
iters: int
Number of iterations / generations of GA
Returns
-------
obj
s: The solution obtained from running the algorithm
"""
cp = 1 #crossover Probability
mp = 0.01 #Mutation Probability
keep = 2; # elitism parameter: how many of the best individuals to keep from one generation to the next
s=solution()
bestIndividual=numpy.zeros(dim)
scores=numpy.random.uniform(0.0, 1.0, popSize)
bestScore=float("inf")
ga=numpy.random.uniform(0,1,(popSize,dim)) *(ub-lb)+lb
convergence_curve=numpy.zeros(iters)
print("GA is optimizing \""+objf.__name__+"\"")
timerStart=time.time()
s.startTime=time.strftime("%Y-%m-%d-%H-%M-%S")
for l in range(iters):
#crossover
ga = crossoverPopulaton(ga, scores, popSize, cp, keep)
#mutation
mutatePopulaton(ga, popSize, mp, keep, lb, ub)
ga = clearDups(ga, lb, ub)
scores = calculateCost(objf, ga, popSize, lb, ub)
bestScore = min(scores)
#Sort from best to worst
ga, scores = sortPopulation(ga, scores)
convergence_curve[l]=bestScore
if (l%1==0):
print(['At iteration '+ str(l+1)+ ' the best fitness is '+ str(bestScore)]);
timerEnd=time.time()
s.bestIndividual = bestIndividual
s.endTime=time.strftime("%Y-%m-%d-%H-%M-%S")
s.executionTime=timerEnd-timerStart
s.convergence=convergence_curve
s.optimizer="GA"
s.objfname=objf.__name__
return s