Passed
Pull Request — master (#233)
by
unknown
01:19
created

NiaPy.algorithms.basic.fss   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 346
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 122
dl 0
loc 346
rs 9.36
c 0
b 0
f 0
wmc 38

18 Methods

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