Passed
Branch master (13db34)
by Grega
02:16
created

FishSchoolSearch.init_school()   A

Complexity

Conditions 2

Size

Total Lines 14
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 13
nop 2
dl 0
loc 14
rs 9.75
c 0
b 0
f 0
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
3
import copy
4
5
from numpy import nan, asarray, zeros, float, full
6
7
from NiaPy.util.utility import objects2array
8
from NiaPy.algorithms.algorithm import Algorithm, Individual
9
10
class Fish(Individual):
11
	r"""Fish individual class.
12
13
	Attributes:
14
		weight (float): Weight of fish.
15
		delta_pos (float): TODO.
16
		delta_cost (float): TODO.
17
		has_improved (bool): If the fish has improved.
18
19
	See Also:
20
		* :class:`NiaPy.algorithms.algorithm.Individual`
21
	"""
22
	def __init__(self, weight, **kwargs):
23
		r"""Initialize fish individual.
24
25
		Args:
26
			weight (float): Weight of fish.
27
			**kwargs (Dict[str, Any]): Additional arguments.
28
29
		See Also:
30
			* :func:`NiaPy.algorithms.algorithm.Individual`
31
		"""
32
		Individual.__init__(self, **kwargs)
33
		self.weight = weight
34
		self.delta_pos = nan
35
		self.delta_cost = nan
36
		self.has_improved = False
37
38
class FishSchoolSearch(Algorithm):
39
	r"""Implementation of Fish School Search algorithm.
40
41
	Algorithm:
42
		Fish School Search algorithm
43
44
	Date:
45
		2019
46
47
	Authors:
48
		Clodomir Santana Jr, Elliackin Figueredo, Mariana Maceds, Pedro Santos.
49
		Ported to NiaPy with small changes by Kristian Järvenpää (2018).
50
		Ported to the NiaPy 2.0 by Klemen Berkovič (2019).
51
52
	License:
53
		MIT
54
55
	Reference paper:
56
		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.
57
58
	Attributes:
59
		Name (List[str]): List of strings representing algorithm name.
60
		SI_init (int): Length of initial individual step.
61
		SI_final (int): Length of final individual step.
62
		SV_init (int): Length of initial volatile step.
63
		SV_final (int): Length of final volatile step.
64
		min_w (float): Minimum weight of a fish.
65
		w_scale (float): Maximum weight of a fish.
66
67
	See Also:
68
		* :class:`NiaPy.algorithms.algorithm.Algorithm`
69
	"""
70
	Name = ['FSS', 'FishSchoolSearch']
71
72
	@staticmethod
73
	def typeParameters():
74
		# TODO
75
		return {'school_size': lambda x: False, 'SI_final': lambda x: False}
76
77
	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):
78
		r"""Set core arguments of FishSchoolSearch algorithm.
79
80
		Arguments:
81
			NP (Optional[int]): Number of fishes in school.
82
			SI_init (Optional[int]): Length of initial individual step.
83
			SI_final (Optional[int]): Length of final individual step.
84
			SV_init (Optional[int]): Length of initial volatile step.
85
			SV_final (Optional[int]): Length of final volatile step.
86
			min_w (Optional[float]): Minimum weight of a fish.
87
			w_scale (Optional[float]): Maximum weight of a fish.
88
89
		See Also:
90
			* :func:`NiaPy.algorithms.Algorithm.setParameters`
91
		"""
92
		Algorithm.setParameters(self, NP=NP)
93
		self.step_individual_init = SI_init
94
		self.step_individual_final = SI_final
95
		self.step_volitive_init = SV_init
96
		self.step_volitive_final = SV_final
97
		self.min_w = min_w
98
		self.w_scale = w_scale
99
100
	def generate_uniform_coordinates(self, task):
101
		r"""Return Numpy array with uniform distribution.
102
103
		Args:
104
			task (Task): Optimization task.
105
106
		Returns:
107
			numpy.ndarray: Array with uniform distribution.
108
		"""
109
		x = zeros((self.NP, task.D))
110
		for i in range(self.NP): x[i] = self.uniform(task.Lower[i], task.Upper[i], task.D)
111
		return x
112
113
	def gen_weight(self):
114
		r"""Get initial weight for fish.
115
116
		Returns:
117
			float: Weight for fish.
118
		"""
119
		return self.w_scale / 2.0
120
121
	def init_fish(self, pos, task):
122
		"""Create a new fish to a given position."""
123
		return Fish(x=pos, weight=self.gen_weight(), task=task, e=True)
124
125
	def init_school(self, task):
126
		"""Initialize fish school with uniform distribution."""
127
		curr_step_individual = self.step_individual_init * (task.Upper - task.Lower)
128
		curr_step_volitive = self.step_volitive_init * (task.Upper - task.Lower)
129
		curr_weight_school = 0.0
130
		prev_weight_school = 0.0
131
		school = []
132
		positions = self.generate_uniform_coordinates(task)
133
		for idx in range(self.NP):
134
			fish = self.init_fish(positions[idx], task)
135
			school.append(fish)
136
			curr_weight_school += fish.weight
137
		prev_weight_school = curr_weight_school
138
		return curr_step_individual, curr_step_volitive, curr_weight_school, prev_weight_school, objects2array(school)
139
140
	def max_delta_cost(self, school):
141
		"""Find maximum delta cost - return 0 if none of the fishes moved."""
142
		max_ = 0
143
		for fish in school:
144
			if max_ < fish.delta_cost: max_ = fish.delta_cost
145
		return max_
146
147
	def total_school_weight(self, school, prev_weight_school, curr_weight_school):
148
		"""Calculate and update current weight of fish school."""
149
		prev_weight_school = curr_weight_school
150
		curr_weight_school = sum(fish.weight for fish in school)
151
		return prev_weight_school, curr_weight_school
152
153
	def calculate_barycenter(self, school, task):
154
		"""Calculate barycenter of fish school."""
155
		barycenter = zeros((task.D,), dtype=float)
156
		density = 0.0
157
		for fish in school:
158
			density += fish.weight
159
			for dim in range(task.D): barycenter[dim] += (fish.x[dim] * fish.weight)
160
		for dim in range(task.D): barycenter[dim] = barycenter[dim] / density
161
		return barycenter
162
163
	def update_steps(self, task):
164
		"""Update step length for individual and volatile steps."""
165
		curr_step_individual = full(task.D, self.step_individual_init - task.Iters * float(self.step_individual_init - self.step_individual_final) / task.nGEN)
166
		curr_step_volitive = full(task.D, self.step_volitive_init - task.Iters * float(self.step_volitive_init - self.step_volitive_final) / task.nGEN)
167
		return curr_step_individual, curr_step_volitive
168
169
	def update_best_fish(self, school, best_fish):
170
		"""Find and update current best fish."""
171
		if best_fish is None: best_fish = copy.copy(school[0])
172
		for fish in school:
173
			if best_fish.f > fish.f: best_fish = copy.copy(fish)
174
		return best_fish
175
176
	def feeding(self, school):
177
		r"""Feed all fishes.
178
179
		Args:
180
			school (numpy.ndarray[Fish]): Current school fish population.
181
182
		Returns:
183
			numpy.ndarray[Fish]: New school fish population.
184
		"""
185
		for fish in school:
186
			if self.max_delta_cost(school): fish.weight = fish.weight + (fish.delta_cost / self.max_delta_cost(school))
187
			if fish.weight > self.w_scale: fish.weight = self.w_scale
188
			elif fish.weight < self.min_w: fish.weight = self.min_w
189
		return school
190
191
	def individual_movement(self, school, curr_step_individual, task):
192
		r"""Perform individual movement for each fish.
193
194
		Args:
195
			school (numpy.ndarray): School fish population.
196
			curr_step_individual (numpy.ndarray[float]): TODO
197
			task (Task): Optimization task.
198
199
		Returns:
200
			numpy.ndarray[Fish]: New school of fishes.
201
		"""
202
		for fish in school:
203
			new_pos = task.repair(fish.x + (curr_step_individual * self.uniform(-1, 1, task.D)), rnd=self.Rand)
204
			cost = task.eval(new_pos)
205
			if cost < fish.f:
206
				fish.delta_cost = abs(cost - fish.f)
207
				fish.f = cost
208
				delta_pos = zeros((task.D,), dtype=float)
209
				for idx in range(task.D): delta_pos[idx] = new_pos[idx] - fish.x[idx]
210
				fish.delta_pos = delta_pos
211
				fish.x = new_pos
212
			else:
213
				fish.delta_pos = zeros((task.D,), dtype=float)
214
				fish.delta_cost = 0
215
		return school
216
217
	def collective_instinctive_movement(self, school, task):
218
		"""Perform collective instictive movement."""
219
		cost_eval_enhanced = zeros((task.D,), dtype=float)
220
		density = sum([f.delta_cost for f in school])
221
		for fish in school: cost_eval_enhanced += (fish.delta_pos * fish.delta_cost)
222
		if density != 0: cost_eval_enhanced = cost_eval_enhanced / density
223
		for fish in school: fish.x = task.repair(fish.x + cost_eval_enhanced, rnd=self.Rand)
224
		return school
225
226
	def collective_volitive_movement(self, school, curr_step_volitive, prev_weight_school, curr_weight_school, task):
227
		"""Perform collective volitive movement."""
228
		prev_weight_school, curr_weight_school = self.total_school_weight(school=school, prev_weight_school=prev_weight_school, curr_weight_school=curr_weight_school)
229
		barycenter = self.calculate_barycenter(school, task)
230
		for fish in school:
231
			if curr_weight_school > prev_weight_school: fish.x -= (fish.x - barycenter) * curr_step_volitive * self.uniform(0, 1, task.D)
232
			else: fish.x += (fish.x - barycenter) * curr_step_volitive * self.uniform(0, 1, task.D)
233
			fish.evaluate(task, rnd=self.Rand)
234
		return school
235
236
	def initPopulation(self, task):
237
		curr_step_individual, curr_step_volitive, curr_weight_school, prev_weight_school, school = self.init_school(task)
238
		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}
239
240
	def runIteration(self, task, school, fschool, best_fish, fxb, curr_step_individual, curr_step_volitive, curr_weight_school, prev_weight_school, **dparams):
241
		school = self.individual_movement(school, curr_step_individual, task)
242
		school = self.feeding(school)
243
		school = self.collective_instinctive_movement(school, task)
244
		school = self.collective_volitive_movement(school=school, curr_step_volitive=curr_step_volitive, prev_weight_school=prev_weight_school, curr_weight_school=curr_weight_school, task=task)
245
		curr_step_individual, curr_step_volitive = self.update_steps(task)
246
		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}
247
248
# vim: tabstop=3 noexpandtab shiftwidth=3 softtabstop=3
249