1
|
|
|
# encoding=utf8 |
2
|
|
|
from numpy import nan, asarray, zeros, float, full |
3
|
|
|
|
4
|
|
|
from NiaPy.util.utility import objects2array |
5
|
|
|
from NiaPy.algorithms.algorithm import Algorithm, Individual |
6
|
|
|
|
7
|
|
|
class Fish(Individual): |
8
|
|
|
r"""Fish individual class. |
9
|
|
|
|
10
|
|
|
Attributes: |
11
|
|
|
weight (float): Weight of fish. |
12
|
|
|
delta_pos (float): TODO. |
13
|
|
|
delta_cost (float): TODO. |
14
|
|
|
has_improved (bool): If the fish has improved. |
15
|
|
|
|
16
|
|
|
See Also: |
17
|
|
|
* :class:`NiaPy.algorithms.algorithm.Individual` |
18
|
|
|
""" |
19
|
|
|
def __init__(self, weight, **kwargs): |
20
|
|
|
r"""Initialize fish individual. |
21
|
|
|
|
22
|
|
|
Args: |
23
|
|
|
weight (float): Weight of fish. |
24
|
|
|
**kwargs (Dict[str, Any]): Additional arguments. |
25
|
|
|
|
26
|
|
|
See Also: |
27
|
|
|
* :func:`NiaPy.algorithms.algorithm.Individual` |
28
|
|
|
""" |
29
|
|
|
Individual.__init__(self, **kwargs) |
30
|
|
|
self.weight = weight |
31
|
|
|
self.delta_pos = nan |
32
|
|
|
self.delta_cost = nan |
33
|
|
|
self.has_improved = False |
34
|
|
|
|
35
|
|
|
class FishSchoolSearch(Algorithm): |
36
|
|
|
r"""Implementation of Fish School Search algorithm. |
37
|
|
|
|
38
|
|
|
Algorithm: |
39
|
|
|
Fish School Search algorithm |
40
|
|
|
|
41
|
|
|
Date: |
42
|
|
|
2019 |
43
|
|
|
|
44
|
|
|
Authors: |
45
|
|
|
Clodomir Santana Jr, Elliackin Figueredo, Mariana Maceds, Pedro Santos. |
46
|
|
|
Ported to NiaPy with small changes by Kristian Järvenpää (2018). |
47
|
|
|
Ported to the NiaPy 2.0 by Klemen Berkovič (2019). |
48
|
|
|
|
49
|
|
|
License: |
50
|
|
|
MIT |
51
|
|
|
|
52
|
|
|
Reference paper: |
53
|
|
|
Bastos Filho, Lima Neto, Lins, D. O. Nascimento and P. Lima, “A novel search algorithm based on fish school behavior,” in 2008 IEEE International Conference on Systems, Man and Cybernetics, Oct 2008, pp. 2646–2651. |
54
|
|
|
|
55
|
|
|
Attributes: |
56
|
|
|
Name (List[str]): List of strings representing algorithm name. |
57
|
|
|
SI_init (int): Length of initial individual step. |
58
|
|
|
SI_final (int): Length of final individual step. |
59
|
|
|
SV_init (int): Length of initial volatile step. |
60
|
|
|
SV_final (int): Length of final volatile step. |
61
|
|
|
min_w (float): Minimum weight of a fish. |
62
|
|
|
w_scale (float): Maximum weight of a fish. |
63
|
|
|
|
64
|
|
|
See Also: |
65
|
|
|
* :class:`NiaPy.algorithms.algorithm.Algorithm` |
66
|
|
|
""" |
67
|
|
|
Name = ['FSS', 'FishSchoolSearch'] |
68
|
|
|
|
69
|
|
|
@staticmethod |
70
|
|
|
def algorithmInfo(): |
71
|
|
|
r"""Get default information of algorithm. |
72
|
|
|
|
73
|
|
|
Returns: |
74
|
|
|
str: Basic information. |
75
|
|
|
|
76
|
|
|
See Also: |
77
|
|
|
* :func:`NiaPy.algorithms.Algorithm.algorithmInfo` |
78
|
|
|
""" |
79
|
|
|
return r"""Bastos Filho, Lima Neto, Lins, D. O. Nascimento and P. Lima, “A novel search algorithm based on fish school behavior,” in 2008 IEEE International Conference on Systems, Man and Cybernetics, Oct 2008, pp. 2646–2651.""" |
80
|
|
|
|
81
|
|
|
@staticmethod |
82
|
|
|
def typeParameters(): |
83
|
|
|
# TODO |
84
|
|
|
return {'school_size': lambda x: False, 'SI_final': lambda x: False} |
85
|
|
|
|
86
|
|
|
def setParameters(self, NP=25, SI_init=3, SI_final=10, SV_init=3, SV_final=13, min_w=0.3, w_scale=0.7, **ukwargs): |
87
|
|
|
r"""Set core arguments of FishSchoolSearch algorithm. |
88
|
|
|
|
89
|
|
|
Arguments: |
90
|
|
|
NP (Optional[int]): Number of fishes in school. |
91
|
|
|
SI_init (Optional[int]): Length of initial individual step. |
92
|
|
|
SI_final (Optional[int]): Length of final individual step. |
93
|
|
|
SV_init (Optional[int]): Length of initial volatile step. |
94
|
|
|
SV_final (Optional[int]): Length of final volatile step. |
95
|
|
|
min_w (Optional[float]): Minimum weight of a fish. |
96
|
|
|
w_scale (Optional[float]): Maximum weight of a fish. |
97
|
|
|
|
98
|
|
|
See Also: |
99
|
|
|
* :func:`NiaPy.algorithms.Algorithm.setParameters` |
100
|
|
|
""" |
101
|
|
|
Algorithm.setParameters(self, NP=NP, **ukwargs) |
102
|
|
|
self.step_individual_init = SI_init |
103
|
|
|
self.step_individual_final = SI_final |
104
|
|
|
self.step_volitive_init = SV_init |
105
|
|
|
self.step_volitive_final = SV_final |
106
|
|
|
self.min_w = min_w |
107
|
|
|
self.w_scale = w_scale |
108
|
|
|
|
109
|
|
|
def getParameters(self): |
110
|
|
|
r"""Get algorithm parameters. |
111
|
|
|
|
112
|
|
|
Returns: |
113
|
|
|
Dict[str, Any]: TODO. |
114
|
|
|
|
115
|
|
|
See Also: |
116
|
|
|
* :func:`NiaPy.algorithms.Algorithm.setParameters` |
117
|
|
|
""" |
118
|
|
|
d = Algorithm.getParameters(self) |
119
|
|
|
d.update({ |
120
|
|
|
'SI_init': self.step_individual_init, |
121
|
|
|
'SI_final': self.step_individual_final, |
122
|
|
|
'SV_init': self.step_volitive_init, |
123
|
|
|
'SV_final': self.step_volitive_final, |
124
|
|
|
'min_w': self.min_w, |
125
|
|
|
'w_scale': self.w_scale |
126
|
|
|
}) |
127
|
|
|
return d |
128
|
|
|
|
129
|
|
|
def generate_uniform_coordinates(self, task): |
130
|
|
|
r"""Return Numpy array with uniform distribution. |
131
|
|
|
|
132
|
|
|
Args: |
133
|
|
|
task (Task): Optimization task. |
134
|
|
|
|
135
|
|
|
Returns: |
136
|
|
|
numpy.ndarray: Array with uniform distribution. |
137
|
|
|
""" |
138
|
|
|
return asarray([self.uniform(task.Lower, task.Upper, task.D) for _ in range(self.NP)]) |
139
|
|
|
|
140
|
|
|
def gen_weight(self): |
141
|
|
|
r"""Get initial weight for fish. |
142
|
|
|
|
143
|
|
|
Returns: |
144
|
|
|
float: Weight for fish. |
145
|
|
|
""" |
146
|
|
|
return self.w_scale / 2.0 |
147
|
|
|
|
148
|
|
|
def init_fish(self, pos, task): |
149
|
|
|
r"""Create a new fish to a given position. |
150
|
|
|
|
151
|
|
|
Args: |
152
|
|
|
pos: |
153
|
|
|
task (Task): |
154
|
|
|
|
155
|
|
|
Returns: |
156
|
|
|
|
157
|
|
|
""" |
158
|
|
|
return Fish(x=pos, weight=self.gen_weight(), task=task, e=True) |
159
|
|
|
|
160
|
|
|
def init_school(self, task): |
161
|
|
|
"""Initialize fish school with uniform distribution.""" |
162
|
|
|
curr_step_individual = self.step_individual_init * (task.Upper - task.Lower) |
163
|
|
|
curr_step_volitive = self.step_volitive_init * (task.Upper - task.Lower) |
164
|
|
|
curr_weight_school = 0.0 |
165
|
|
|
prev_weight_school = 0.0 |
166
|
|
|
school = [] |
167
|
|
|
positions = self.generate_uniform_coordinates(task) |
168
|
|
|
for idx in range(self.NP): |
169
|
|
|
fish = self.init_fish(positions[idx], task) |
170
|
|
|
school.append(fish) |
171
|
|
|
curr_weight_school += fish.weight |
172
|
|
|
prev_weight_school = curr_weight_school |
173
|
|
|
return curr_step_individual, curr_step_volitive, curr_weight_school, prev_weight_school, objects2array(school) |
174
|
|
|
|
175
|
|
|
def max_delta_cost(self, school): |
176
|
|
|
r"""Find maximum delta cost - return 0 if none of the fishes moved. |
177
|
|
|
|
178
|
|
|
Args: |
179
|
|
|
school (numpy.ndarray): |
180
|
|
|
|
181
|
|
|
Returns: |
182
|
|
|
|
183
|
|
|
""" |
184
|
|
|
max_ = 0 |
185
|
|
|
for fish in school: |
186
|
|
|
if max_ < fish.delta_cost: max_ = fish.delta_cost |
187
|
|
|
return max_ |
188
|
|
|
|
189
|
|
|
def total_school_weight(self, school, prev_weight_school, curr_weight_school): |
190
|
|
|
r"""Calculate and update current weight of fish school. |
191
|
|
|
|
192
|
|
|
Args: |
193
|
|
|
school (numpy.ndarray): |
194
|
|
|
prev_weight_school (numpy.ndarray): |
195
|
|
|
curr_weight_school (numpy.ndarray): |
196
|
|
|
|
197
|
|
|
Returns: |
198
|
|
|
Tuple[numpy.ndarray, numpy.ndarray]: TODO. |
199
|
|
|
""" |
200
|
|
|
prev_weight_school = curr_weight_school |
201
|
|
|
curr_weight_school = sum(fish.weight for fish in school) |
202
|
|
|
return prev_weight_school, curr_weight_school |
203
|
|
|
|
204
|
|
|
def calculate_barycenter(self, school, task): |
205
|
|
|
r"""Calculate barycenter of fish school. |
206
|
|
|
|
207
|
|
|
Args: |
208
|
|
|
school (numpy.ndarray): Current school fish. |
209
|
|
|
task (Task): Optimization task. |
210
|
|
|
|
211
|
|
|
Returns: |
212
|
|
|
numpy.ndarray: TODO. |
213
|
|
|
""" |
214
|
|
|
barycenter = zeros((task.D,), dtype=float) |
215
|
|
|
density = 0.0 |
216
|
|
|
for fish in school: |
217
|
|
|
density += fish.weight |
218
|
|
|
for dim in range(task.D): barycenter[dim] += (fish.x[dim] * fish.weight) |
219
|
|
|
for dim in range(task.D): barycenter[dim] = barycenter[dim] / density |
220
|
|
|
return barycenter |
221
|
|
|
|
222
|
|
|
def update_steps(self, task): |
223
|
|
|
r"""Update step length for individual and volatile steps. |
224
|
|
|
|
225
|
|
|
Args: |
226
|
|
|
task (Task): Optimization task |
227
|
|
|
|
228
|
|
|
Returns: |
229
|
|
|
Tuple[numpy.ndarray, numpy.ndarray]: TODO. |
230
|
|
|
""" |
231
|
|
|
curr_step_individual = full(task.D, self.step_individual_init - task.Iters * float(self.step_individual_init - self.step_individual_final) / task.nGEN) |
232
|
|
|
curr_step_volitive = full(task.D, self.step_volitive_init - task.Iters * float(self.step_volitive_init - self.step_volitive_final) / task.nGEN) |
233
|
|
|
return curr_step_individual, curr_step_volitive |
234
|
|
|
|
235
|
|
|
def feeding(self, school): |
236
|
|
|
r"""Feed all fishes. |
237
|
|
|
|
238
|
|
|
Args: |
239
|
|
|
school (numpy.ndarray): Current school fish population. |
240
|
|
|
|
241
|
|
|
Returns: |
242
|
|
|
numpy.ndarray: New school fish population. |
243
|
|
|
""" |
244
|
|
|
for fish in school: |
245
|
|
|
if self.max_delta_cost(school): fish.weight = fish.weight + (fish.delta_cost / self.max_delta_cost(school)) |
246
|
|
|
if fish.weight > self.w_scale: fish.weight = self.w_scale |
247
|
|
|
elif fish.weight < self.min_w: fish.weight = self.min_w |
248
|
|
|
return school |
249
|
|
|
|
250
|
|
|
def individual_movement(self, school, curr_step_individual, xb, fxb, task): |
251
|
|
|
r"""Perform individual movement for each fish. |
252
|
|
|
|
253
|
|
|
Args: |
254
|
|
|
school (numpy.ndarray): School fish population. |
255
|
|
|
curr_step_individual (numpy.ndarray): TODO |
256
|
|
|
xb (numpy.ndarray): Global best solution. |
257
|
|
|
fxb (float): Global best solutions fitness/objecive value. |
258
|
|
|
task (Task): Optimization task. |
259
|
|
|
|
260
|
|
|
Returns: |
261
|
|
|
Tuple[numpy.ndarray, numpy.ndarray, float]: |
262
|
|
|
1. New school of fishes. |
263
|
|
|
""" |
264
|
|
|
for fish in school: |
265
|
|
|
new_pos = task.repair(fish.x + (curr_step_individual * self.uniform(-1, 1, task.D)), rnd=self.Rand) |
266
|
|
|
cost = task.eval(new_pos) |
267
|
|
|
if cost < fish.f: |
268
|
|
|
xb, fxb = self.getBest(new_pos, cost, xb, fxb) |
269
|
|
|
fish.delta_cost = abs(cost - fish.f) |
270
|
|
|
fish.f = cost |
271
|
|
|
delta_pos = zeros((task.D,), dtype=float) |
272
|
|
|
for idx in range(task.D): delta_pos[idx] = new_pos[idx] - fish.x[idx] |
273
|
|
|
fish.delta_pos = delta_pos |
274
|
|
|
fish.x = new_pos |
275
|
|
|
else: |
276
|
|
|
fish.delta_pos = zeros((task.D,), dtype=float) |
277
|
|
|
fish.delta_cost = 0 |
278
|
|
|
return school, xb, fxb |
279
|
|
|
|
280
|
|
|
def collective_instinctive_movement(self, school, task): |
281
|
|
|
r"""Perform collective instinctive movement. |
282
|
|
|
|
283
|
|
|
Args: |
284
|
|
|
school (numpy.ndarray): Current population. |
285
|
|
|
task (Task): Optmization task. |
286
|
|
|
|
287
|
|
|
Returns: |
288
|
|
|
numpy.ndarray: New populaiton |
289
|
|
|
""" |
290
|
|
|
cost_eval_enhanced = zeros((task.D,), dtype=float) |
291
|
|
|
density = sum([f.delta_cost for f in school]) |
292
|
|
|
for fish in school: cost_eval_enhanced += (fish.delta_pos * fish.delta_cost) |
293
|
|
|
if density != 0: cost_eval_enhanced = cost_eval_enhanced / density |
294
|
|
|
for fish in school: fish.x = task.repair(fish.x + cost_eval_enhanced, rnd=self.Rand) |
295
|
|
|
return school |
296
|
|
|
|
297
|
|
|
def collective_volitive_movement(self, school, curr_step_volitive, prev_weight_school, curr_weight_school, xb, fxb, task): |
298
|
|
|
r"""Perform collective volitive movement. |
299
|
|
|
|
300
|
|
|
Args: |
301
|
|
|
school (numpy.ndarray): |
302
|
|
|
curr_step_volitive : |
303
|
|
|
prev_weight_school: |
304
|
|
|
curr_weight_school: |
305
|
|
|
xb (numpy.ndarray): Global best solution. |
306
|
|
|
fxb (float): Global best solutions fitness/objective value. |
307
|
|
|
task (Task): Optimization task. |
308
|
|
|
|
309
|
|
|
Returns: |
310
|
|
|
Tuple[numpy.ndarray, numpy.ndarray, float]: New population. |
311
|
|
|
""" |
312
|
|
|
prev_weight_school, curr_weight_school = self.total_school_weight(school=school, prev_weight_school=prev_weight_school, curr_weight_school=curr_weight_school) |
313
|
|
|
barycenter = self.calculate_barycenter(school, task) |
314
|
|
|
for fish in school: |
315
|
|
|
if curr_weight_school > prev_weight_school: fish.x -= (fish.x - barycenter) * curr_step_volitive * self.uniform(0, 1, task.D) |
316
|
|
|
else: fish.x += (fish.x - barycenter) * curr_step_volitive * self.uniform(0, 1, task.D) |
317
|
|
|
fish.evaluate(task, rnd=self.Rand) |
318
|
|
|
xb, fxb = self.getBest(fish.x, fish.f, xb, fxb) |
319
|
|
|
return school, xb, fxb |
320
|
|
|
|
321
|
|
|
def initPopulation(self, task): |
322
|
|
|
r"""Initialize the school. |
323
|
|
|
|
324
|
|
|
Args: |
325
|
|
|
task (Task): Optimization task. |
326
|
|
|
|
327
|
|
|
Returns: |
328
|
|
|
Tuple[numpy.ndarray, numpy.ndarray, dict]: TODO. |
329
|
|
|
""" |
330
|
|
|
curr_step_individual, curr_step_volitive, curr_weight_school, prev_weight_school, school = self.init_school(task) |
331
|
|
|
return school, asarray([f.f for f in school]), {'curr_step_individual': curr_step_individual, 'curr_step_volitive': curr_step_volitive, 'curr_weight_school': curr_weight_school, 'prev_weight_school': prev_weight_school} |
332
|
|
|
|
333
|
|
|
def runIteration(self, task, school, fschool, xb, fxb, curr_step_individual, curr_step_volitive, curr_weight_school, prev_weight_school, **dparams): |
334
|
|
|
r"""Core function of algorithm. |
335
|
|
|
|
336
|
|
|
Args: |
337
|
|
|
task (Task): |
338
|
|
|
school (numpy.ndarray): |
339
|
|
|
fschool (numpy.ndarray): |
340
|
|
|
best_fish (numpy.ndarray): |
341
|
|
|
fxb (float): |
342
|
|
|
curr_step_individual: |
343
|
|
|
curr_step_volitive: |
344
|
|
|
curr_weight_school: |
345
|
|
|
prev_weight_school: |
346
|
|
|
**dparams: |
347
|
|
|
|
348
|
|
|
Returns: |
349
|
|
|
Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, float, dict]: TODO. |
350
|
|
|
""" |
351
|
|
|
school, xb, fxb = self.individual_movement(school, curr_step_individual, xb, fxb, task) |
352
|
|
|
school = self.feeding(school) |
353
|
|
|
school = self.collective_instinctive_movement(school, task) |
354
|
|
|
school, xb, fxb = self.collective_volitive_movement(school=school, curr_step_volitive=curr_step_volitive, prev_weight_school=prev_weight_school, curr_weight_school=curr_weight_school, xb=xb, fxb=fxb, task=task) |
355
|
|
|
curr_step_individual, curr_step_volitive = self.update_steps(task) |
356
|
|
|
return school, asarray([f.f for f in school]), xb, fxb, {'curr_step_individual': curr_step_individual, 'curr_step_volitive': curr_step_volitive, 'curr_weight_school': curr_weight_school, 'prev_weight_school': prev_weight_school} |
357
|
|
|
|
358
|
|
|
# vim: tabstop=3 noexpandtab shiftwidth=3 softtabstop=3 |
359
|
|
|
|