Skip to content

Commit 72ca12d

Browse files
committed
Add test functions
1 parent eb79598 commit 72ca12d

3 files changed

Lines changed: 151 additions & 28 deletions

File tree

heuristic_algorithm/genetic_algorithm.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def __init__(self, func, boundary,
4242
generation=50,
4343
cross_rate=0.8,
4444
mutate_rate=0.1,
45-
selection='roulette_wheel',
45+
selection='tournament',
46+
tournament_size=3,
4647
crossover='one_point'):
4748

4849
self.population_size = population_size
@@ -52,6 +53,7 @@ def __init__(self, func, boundary,
5253
self.boundary = boundary
5354
self.population = []
5455
self.selection_method = selection
56+
self.tournament_size = tournament_size
5557
self.crossover_method = crossover
5658
self.fitness_func = func
5759

@@ -78,24 +80,23 @@ def selection(self, population, k):
7880
"""
7981
Select k genes based on certain rules. Accepted rules:
8082
- roulette-wheel
81-
- tournament (TO DO)
82-
- stochastic (To DO)
83+
- tournament
84+
- stochastic
8385
- truncation (To DO)
8486
:param population: The population to select from.
8587
:param k: Number of genes to select.
8688
:return: list of genes.
8789
"""
8890
ret = []
8991

92+
# Calculate accumulated normalized fitness.
93+
fitness = [x.get_fitness() for x in population]
94+
reverse_fitness = [1 / fit for fit in fitness]
95+
normalized_fitness = [f / sum(reverse_fitness) for f in reverse_fitness]
96+
for i in range(1, len(fitness)):
97+
normalized_fitness[i] += normalized_fitness[i - 1]
98+
9099
if self.selection_method == "roulette_wheel":
91-
# Calculate accumulated normalized fitness.
92-
fitness = [x.get_fitness() for x in population]
93-
# print(fitness)
94-
reverse_fitness = [1/fit for fit in fitness]
95-
normalized_fitness = [f/sum(reverse_fitness) for f in reverse_fitness]
96-
for i in range(1, len(fitness)):
97-
normalized_fitness[i] += normalized_fitness[i-1]
98-
#print(normalized_fitness)
99100
# Selection based on fitness.
100101
for j in range(k):
101102
r = random.random()
@@ -104,11 +105,29 @@ def selection(self, population, k):
104105
ret.append(population[index])
105106
break
106107

108+
if self.selection_method == "stochastic":
109+
for j in range(k):
110+
candidates = []
111+
for _ in range(2):
112+
r = random.random()
113+
for index in range(len(fitness)):
114+
if normalized_fitness[index] >= r:
115+
candidates.append(population[index])
116+
break
117+
candidates.sort(key=lambda x: x.get_fitness(), reverse=False)
118+
ret.append(candidates[0])
119+
120+
if self.selection_method == "tournament":
121+
for j in range(k):
122+
candidates = random.choices(self.population, k=self.tournament_size)
123+
candidates.sort(key=lambda x: x.get_fitness(), reverse=False)
124+
ret.append(candidates[0])
125+
107126
return ret
108127

109128
def crossover(self, gene1, gene2):
110129
"""
111-
Crossover between two genes and return the offsprings.
130+
Crossover between two genes and return the offsprings if they are better.
112131
Methods accepted:
113132
- one_point
114133
- two-points (To DO)
@@ -129,6 +148,9 @@ def crossover(self, gene1, gene2):
129148
offspring1 = Gene(data=offspring1_data)
130149
offspring2 = Gene(data=offspring2_data)
131150

151+
offspring1.set_fitness(self.evaluate(offspring1.get_data()))
152+
offspring2.set_fitness(self.evaluate(offspring2.get_data()))
153+
132154
return offspring1, offspring2
133155

134156
def mutation(self, gene):
@@ -145,7 +167,7 @@ def mutation(self, gene):
145167
lower, upper = self.boundary[pos]
146168
new_val = random.uniform(lower, upper)
147169
data[pos] = new_val
148-
return Gene(data=data)
170+
return Gene(data=data, fitness=self.evaluate(data))
149171

150172
def population_info(self, population):
151173
"""
@@ -207,10 +229,6 @@ def fit(self):
207229
# Replace old population with new.
208230
self.population = next_gen
209231

210-
# Update fitness for all genes.
211-
for gene in self.population:
212-
gene.set_fitness(self.evaluate(gene.get_data()))
213-
214232
cur_generation += 1
215233
temp = self.population_info(self.population)
216234

test.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
from heuristic_algorithm.genetic_algorithm import GeneticAlgorithm
2-
from test_function.single_objective import Ackley
2+
from test_function import single_objective
33
import math
44

5-
ackley = Ackley(dim=5)
6-
boundary = ackley.boundary()
5+
func = single_objective.CrossIT()
76

8-
GA = GeneticAlgorithm(ackley.function, boundary, generation=5000)
9-
GA.fit()
7+
boundary = func.boundary()
8+
9+
GA = GeneticAlgorithm(func.function, boundary,
10+
population_size=1000,
11+
generation=1000,
12+
selection="tournament",
13+
tournament_size=9)
14+
GA.fit()
15+
16+
print(func)

test_function/single_objective.py

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#
22
# Author: Steven Zhao
33
# Some test functions for single objective optimization, including:
4-
# - Rastrigin function
5-
# - Ackley function
6-
# - Sphere function
7-
# - Rosenbrock
4+
# - Rastrigin (DONE)
5+
# - Ackley (DONE)
6+
# - Bukin (DONE)
7+
# - Sphere (DONE)
8+
# - Rosenbrock (DONE)
9+
# - Cross-in-tray (DONE)
810
#
911
#
1012
##########################################################################
@@ -36,7 +38,7 @@ def solution(self):
3638

3739
class Ackley(Function):
3840
"""
39-
Implementation based on: https://www.sfu.ca/~ssurjano/ackley.html
41+
Implementation reference: https://www.sfu.ca/~ssurjano/ackley.html
4042
"""
4143
def __init__(self, dim, a=20, b=0.2, c=2*np.pi):
4244
self.a = a
@@ -57,5 +59,101 @@ def boundary(self):
5759
return [(-32.768, 32.768)] * self.dim
5860

5961
def solution(self):
60-
return [0] * self.dim
62+
return (0,) * self.dim
6163

64+
def __str__(self):
65+
return "Ackley:\tDim = " + str(self.dim) + "\tGlobal Minimum: " + str(self.solution())
66+
67+
68+
class Bukin(Function):
69+
"""
70+
Implementation reference: https://www.sfu.ca/~ssurjano/bukin6.html
71+
"""
72+
def function(self, params):
73+
return 100 * np.sqrt(abs(params[1] - 0.01 * params[0]**2)) + 0.01 * abs(params[0] + 10)
74+
75+
def boundary(self):
76+
return [(-15, -5), (-3, 3)]
77+
78+
def solution(self):
79+
return -10, 1
80+
81+
def __str__(self):
82+
return "Bukin:\tDim = 2\tGlobal Minimum: " + str(self.solution())
83+
84+
85+
class Rastrigin(Function):
86+
"""
87+
Implementation reference: https://www.sfu.ca/~ssurjano/rastr.html
88+
"""
89+
def __init__(self, dim):
90+
self.dim = dim
91+
92+
def function(self, params):
93+
return 10 * self.dim + sum(map(lambda x: x**2 - 10 * np.cos(2*np.pi*x), params))
94+
95+
def boundary(self):
96+
return [(-5.12, 5.12)] * self.dim
97+
98+
def solution(self):
99+
return (0,) * self.dim
100+
101+
def __str__(self):
102+
return "Rastrigin:\tDim = " + str(self.dim) + "\tGlobal Minimum: " + str(self.solution())
103+
104+
105+
class Sphere(Function):
106+
"""
107+
Implementation reference: https://www.sfu.ca/~ssurjano/spheref.html
108+
"""
109+
def __init__(self, dim):
110+
self.dim = dim
111+
112+
def function(self, params):
113+
return sum(map(lambda x: x ** 2, params))
114+
115+
def boundary(self):
116+
return [(-10, 10)] * self.dim
117+
118+
def solution(self):
119+
return (0,) * self.dim
120+
121+
def __str__(self):
122+
return "Sphere:\tDim = " + str(self.dim) + "\tGlobal Minimum: " + str(self.solution())
123+
124+
125+
class Rosenbrock(Function):
126+
"""
127+
Implementation reference: https://www.sfu.ca/~ssurjano/rosen.html
128+
"""
129+
def __init__(self, dim):
130+
self.dim = dim
131+
132+
def function(self, params):
133+
return sum(100*(params[i+1] - params[i]**2)**2 + (params[i] - 1)**2 for i in range(self.dim - 1))
134+
135+
def boundary(self):
136+
return [(-5, 10)] * self.dim
137+
138+
def solution(self):
139+
return (1,) * self.dim
140+
141+
def __str__(self):
142+
return "Rosenbrock:\tDim = " + str(self.dim) + "\tGlobal Minimum: " + str(self.solution())
143+
144+
145+
class CrossIT(Function):
146+
"""
147+
Implementation reference: https://www.sfu.ca/~ssurjano/crossit.html
148+
"""
149+
def function(self, params):
150+
return -0.0001 * (abs(np.sin(params[0]) * np.sin(params[1]) * np.exp(abs(100 - (params[0]**2 + params[1]**2)**0.5/np.pi))) + 1)**0.1 + 2.06262
151+
152+
def boundary(self):
153+
return [(-10, 10), (-10, 10)]
154+
155+
def solution(self):
156+
return 1.3491, -1.3491
157+
158+
def __str__(self):
159+
return "CrossIT:\tDim = 2\tGlobal Minimum: " + str(self.solution())

0 commit comments

Comments
 (0)