Completed
Push — master ( cc7745...279fb5 )
by Grega
19s queued 17s
created

()   A

Complexity

Conditions 4

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
nop 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
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 typeParameters():
71
		# TODO
72
		return {'school_size': lambda x: False, 'SI_final': lambda x: False}
73
74
	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):
75
		r"""Set core arguments of FishSchoolSearch algorithm.
76
77
		Arguments:
78
			NP (Optional[int]): Number of fishes in school.
79
			SI_init (Optional[int]): Length of initial individual step.
80
			SI_final (Optional[int]): Length of final individual step.
81
			SV_init (Optional[int]): Length of initial volatile step.
82
			SV_final (Optional[int]): Length of final volatile step.
83
			min_w (Optional[float]): Minimum weight of a fish.
84
			w_scale (Optional[float]): Maximum weight of a fish.
85
86
		See Also:
87
			* :func:`NiaPy.algorithms.Algorithm.setParameters`
88
		"""
89
		Algorithm.setParameters(self, NP=NP, **ukwargs)
90
		self.step_individual_init = SI_init
91
		self.step_individual_final = SI_final
92
		self.step_volitive_init = SV_init
93
		self.step_volitive_final = SV_final
94
		self.min_w = min_w
95
		self.w_scale = w_scale
96
97
	def getParameters(self):
98
		r"""Get algorithm parameters.
99
100
		Returns:
101
			Dict[str, Any]: TODO.
102
103
		See Also:
104
			* :func:`NiaPy.algorithms.Algorithm.setParameters`
105
		"""
106
		d = Algorithm.getParameters(self)
107
		d.update({
108
			'SI_init': self.step_individual_init,
109
			'SI_final': self.step_individual_final,
110
			'SV_init': self.step_volitive_init,
111
			'SV_final': self.step_volitive_final,
112
			'min_w': self.min_w,
113
			'w_scale': self.w_scale
114
		})
115
		return d
116
117
	def generate_uniform_coordinates(self, task):
118
		r"""Return Numpy array with uniform distribution.
119
120
		Args:
121
			task (Task): Optimization task.
122
123
		Returns:
124
			numpy.ndarray: Array with uniform distribution.
125
		"""
126
		return asarray([self.uniform(task.Lower, task.Upper, task.D) for _ in range(self.NP)])
127
128
	def gen_weight(self):
129
		r"""Get initial weight for fish.
130
131
		Returns:
132
			float: Weight for fish.
133
		"""
134
		return self.w_scale / 2.0
135
136
	def init_fish(self, pos, task):
137
		r"""Create a new fish to a given position.
138
139
		Args:
140
			pos:
141
			task (Task):
142
143
		Returns:
144
145
		"""
146
		return Fish(x=pos, weight=self.gen_weight(), task=task, e=True)
147
148
	def init_school(self, task):
149
		"""Initialize fish school with uniform distribution."""
150
		curr_step_individual = self.step_individual_init * (task.Upper - task.Lower)
151
		curr_step_volitive = self.step_volitive_init * (task.Upper - task.Lower)
152
		curr_weight_school = 0.0
153
		prev_weight_school = 0.0
154
		school = []
155
		positions = self.generate_uniform_coordinates(task)
156
		for idx in range(self.NP):
157
			fish = self.init_fish(positions[idx], task)
158
			school.append(fish)
159
			curr_weight_school += fish.weight
160
		prev_weight_school = curr_weight_school
161
		return curr_step_individual, curr_step_volitive, curr_weight_school, prev_weight_school, objects2array(school)
162
163
	def max_delta_cost(self, school):
164
		r"""Find maximum delta cost - return 0 if none of the fishes moved.
165
166
		Args:
167
			school (numpy.ndarray):
168
169
		Returns:
170
171
		"""
172
		max_ = 0
173
		for fish in school:
174
			if max_ < fish.delta_cost: max_ = fish.delta_cost
175
		return max_
176
177
	def total_school_weight(self, school, prev_weight_school, curr_weight_school):
178
		r"""Calculate and update current weight of fish school.
179
180
		Args:
181
			school (numpy.ndarray):
182
			prev_weight_school (numpy.ndarray):
183
			curr_weight_school (numpy.ndarray):
184
185
		Returns:
186
			Tuple[numpy.ndarray, numpy.ndarray]: TODO.
187
		"""
188
		prev_weight_school = curr_weight_school
189
		curr_weight_school = sum(fish.weight for fish in school)
190
		return prev_weight_school, curr_weight_school
191
192
	def calculate_barycenter(self, school, task):
193
		r"""Calculate barycenter of fish school.
194
195
		Args:
196
			school (numpy.ndarray): Current school fish.
197
			task (Task): Optimization task.
198
199
		Returns:
200
			numpy.ndarray: TODO.
201
		"""
202
		barycenter = zeros((task.D,), dtype=float)
203
		density = 0.0
204
		for fish in school:
205
			density += fish.weight
206
			for dim in range(task.D): barycenter[dim] += (fish.x[dim] * fish.weight)
207
		for dim in range(task.D): barycenter[dim] = barycenter[dim] / density
208
		return barycenter
209
210
	def update_steps(self, task):
211
		r"""Update step length for individual and volatile steps.
212
213
		Args:
214
			task (Task): Optimization task
215
216
		Returns:
217
			Tuple[numpy.ndarray, numpy.ndarray]: TODO.
218
		"""
219
		curr_step_individual = full(task.D, self.step_individual_init - task.Iters * float(self.step_individual_init - self.step_individual_final) / task.nGEN)
220
		curr_step_volitive = full(task.D, self.step_volitive_init - task.Iters * float(self.step_volitive_init - self.step_volitive_final) / task.nGEN)
221
		return curr_step_individual, curr_step_volitive
222
223
	def feeding(self, school):
224
		r"""Feed all fishes.
225
226
		Args:
227
			school (numpy.ndarray): Current school fish population.
228
229
		Returns:
230
			numpy.ndarray: New school fish population.
231
		"""
232
		for fish in school:
233
			if self.max_delta_cost(school): fish.weight = fish.weight + (fish.delta_cost / self.max_delta_cost(school))
234
			if fish.weight > self.w_scale: fish.weight = self.w_scale
235
			elif fish.weight < self.min_w: fish.weight = self.min_w
236
		return school
237
238
	def individual_movement(self, school, curr_step_individual, xb, fxb, task):
239
		r"""Perform individual movement for each fish.
240
241
		Args:
242
			school (numpy.ndarray): School fish population.
243
			curr_step_individual (numpy.ndarray): TODO
244
			xb (numpy.ndarray): Global best solution.
245
			fxb (float): Global best solutions fitness/objecive value.
246
			task (Task): Optimization task.
247
248
		Returns:
249
			Tuple[numpy.ndarray, numpy.ndarray, float]:
250
				1. New school of fishes.
251
		"""
252
		for fish in school:
253
			new_pos = task.repair(fish.x + (curr_step_individual * self.uniform(-1, 1, task.D)), rnd=self.Rand)
254
			cost = task.eval(new_pos)
255
			if cost < fish.f:
256
				xb, fxb = self.getBest(new_pos, cost, xb, fxb)
257
				fish.delta_cost = abs(cost - fish.f)
258
				fish.f = cost
259
				delta_pos = zeros((task.D,), dtype=float)
260
				for idx in range(task.D): delta_pos[idx] = new_pos[idx] - fish.x[idx]
261
				fish.delta_pos = delta_pos
262
				fish.x = new_pos
263
			else:
264
				fish.delta_pos = zeros((task.D,), dtype=float)
265
				fish.delta_cost = 0
266
		return school, xb, fxb
267
268
	def collective_instinctive_movement(self, school, task):
269
		r"""Perform collective instinctive movement.
270
271
		Args:
272
			school (numpy.ndarray): Current population.
273
			task (Task): Optmization task.
274
275
		Returns:
276
			numpy.ndarray: New populaiton
277
		"""
278
		cost_eval_enhanced = zeros((task.D,), dtype=float)
279
		density = sum([f.delta_cost for f in school])
280
		for fish in school: cost_eval_enhanced += (fish.delta_pos * fish.delta_cost)
281
		if density != 0: cost_eval_enhanced = cost_eval_enhanced / density
282
		for fish in school: fish.x = task.repair(fish.x + cost_eval_enhanced, rnd=self.Rand)
283
		return school
284
285
	def collective_volitive_movement(self, school, curr_step_volitive, prev_weight_school, curr_weight_school, xb, fxb, task):
286
		r"""Perform collective volitive movement.
287
288
		Args:
289
			school (numpy.ndarray):
290
			curr_step_volitive :
291
			prev_weight_school:
292
			curr_weight_school:
293
			xb (numpy.ndarray): Global best solution.
294
			fxb (float): Global best solutions fitness/objective value.
295
			task (Task): Optimization task.
296
297
		Returns:
298
			Tuple[numpy.ndarray, numpy.ndarray, float]: New population.
299
		"""
300
		prev_weight_school, curr_weight_school = self.total_school_weight(school=school, prev_weight_school=prev_weight_school, curr_weight_school=curr_weight_school)
301
		barycenter = self.calculate_barycenter(school, task)
302
		for fish in school:
303
			if curr_weight_school > prev_weight_school: fish.x -= (fish.x - barycenter) * curr_step_volitive * self.uniform(0, 1, task.D)
304
			else: fish.x += (fish.x - barycenter) * curr_step_volitive * self.uniform(0, 1, task.D)
305
			fish.evaluate(task, rnd=self.Rand)
306
			xb, fxb = self.getBest(fish.x, fish.f, xb, fxb)
307
		return school, xb, fxb
308
309
	def initPopulation(self, task):
310
		r"""Initialize the school.
311
312
		Args:
313
			task (Task): Optimization task.
314
315
		Returns:
316
			Tuple[numpy.ndarray, numpy.ndarray, dict]: TODO.
317
		"""
318
		curr_step_individual, curr_step_volitive, curr_weight_school, prev_weight_school, school = self.init_school(task)
319
		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}
320
321
	def runIteration(self, task, school, fschool, xb, fxb, curr_step_individual, curr_step_volitive, curr_weight_school, prev_weight_school, **dparams):
322
		r"""Core function of algorithm.
323
324
		Args:
325
			task (Task):
326
			school (numpy.ndarray):
327
			fschool (numpy.ndarray):
328
			best_fish (numpy.ndarray):
329
			fxb (float):
330
			curr_step_individual:
331
			curr_step_volitive:
332
			curr_weight_school:
333
			prev_weight_school:
334
			**dparams:
335
336
		Returns:
337
			Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, float, dict]: TODO.
338
		"""
339
		school, xb, fxb = self.individual_movement(school, curr_step_individual, xb, fxb, task)
340
		school = self.feeding(school)
341
		school = self.collective_instinctive_movement(school, task)
342
		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)
343
		curr_step_individual, curr_step_volitive = self.update_steps(task)
344
		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}
345
346
# vim: tabstop=3 noexpandtab shiftwidth=3 softtabstop=3
347