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

KrillHerd.getNeighbours()   A

Complexity

Conditions 5

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 6
nop 4
dl 0
loc 16
rs 9.3333
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, redefined-builtin, singleton-comparison, no-self-use, bad-continuation
3
import logging
4
from scipy.spatial.distance import euclidean as ed
5
from numpy import apply_along_axis, argmin, argmax, sum, full, inf, asarray, mean, where, sqrt
6
from NiaPy.util import fullArray
7
from NiaPy.algorithms.algorithm import Algorithm
8
9
logging.basicConfig()
10
logger = logging.getLogger('NiaPy.algorithms.basic')
11
logger.setLevel('INFO')
12
13
__all__ = ['KrillHerdV1', 'KrillHerdV2', 'KrillHerdV3', 'KrillHerdV4', 'KrillHerdV11']
14
15
class KrillHerd(Algorithm):
16
	r"""Implementation of krill herd algorithm.
17
18
	Algorithm:
19
		Krill Herd Algorithm
20
21
	Date:
22
		2018
23
24
	Authors:
25
		Klemen Berkovič
26
27
	License:
28
		MIT
29
30
	Reference URL:
31
		http://www.sciencedirect.com/science/article/pii/S1007570412002171
32
33
	Reference paper:
34
		Amir Hossein Gandomi, Amir Hossein Alavi, Krill herd: A new bio-inspired optimization algorithm, Communications in Nonlinear Science and Numerical Simulation, Volume 17, Issue 12, 2012, Pages 4831-4845, ISSN 1007-5704, https://doi.org/10.1016/j.cnsns.2012.05.010.
35
36
	Attributes:
37
		Name (List[str]): List of strings representing algorithm names..
38
		NP (int): Number of krill herds in population.
39
		N_max (float): Maximum induced speed.
40
		V_f (float): Foraging speed.
41
		D_max (float): Maximum diffusion speed.
42
		C_t (float): Constant :math:`\in [0, 2]`
43
		W_n (Union[int, float, numpy.ndarray]): Interta weights of the motion induced from neighbors :math:`\in [0, 1]`.
44
		W_f (Union[int, float, numpy.ndarray]): Interta weights of the motion induced from foraging :math`\in [0, 1]`.
45
		d_s (float): Maximum euclidean distance for neighbors.
46
		nn (int): Maximum neighbors for neighbors effect.
47
		Cr (float): Crossover probability.
48
		Mu (float): Mutation probability.
49
		epsilon (float): Small numbers for division.
50
51
	See Also:
52
		* :class:`NiaPy.algorithms.algorithm.Algorithm`
53
	"""
54
	Name = ['KrillHerd', 'KH']
55
56
	@staticmethod
57
	def typeParameters():
58
		r"""Get dictionary with functions for checking values of parameters.
59
60
		Returns:
61
			Dict[str, Callable]:
62
				* N_max (Callable[[Union[int, float]], bool]): TODO
63
				* V_f (Callable[[Union[int, float]], bool]): TODO
64
				* D_max (Callable[[Union[int, float]], bool]): TODO
65
				* C_t (Callable[[Union[int, float]], bool]): TODO
66
				* W_n (Callable[[Union[int, float]], bool]): TODO
67
				* W_f (Callable[[Union[int, float]], bool]): TODO
68
				* d_s (Callable[[Union[int, float]], boool]): TODO
69
				* nn (Callable[[int], bool]): TODO
70
				* Cr (Callable[[float], bool]): TODO
71
				* Mu (Callable[[float], bool]): TODO
72
				* epsilon (Callable[[float], bool]): TODO
73
74
		See Also:
75
			* :func:`NiaPy.algorithms.algorithm.Algorithm`
76
		"""
77
		d = Algorithm.typeParameters()
78
		d.update({
79
			'N_max': lambda x: isinstance(x, (int, float)) and x > 0,
80
			'V_f': lambda x: isinstance(x, (int, float)) and x > 0,
81
			'D_max': lambda x: isinstance(x, (int, float)) and x > 0,
82
			'C_t': lambda x: isinstance(x, (int, float)) and x > 0,
83
			'W_n': lambda x: isinstance(x, (int, float)) and x > 0,
84
			'W_f': lambda x: isinstance(x, (int, float)) and x > 0,
85
			'd_s': lambda x: isinstance(x, (int, float)) and x > 0,
86
			'nn': lambda x: isinstance(x, int) and x > 0,
87
			'Cr': lambda x: isinstance(x, float) and 0 <= x <= 1,
88
			'Mu': lambda x: isinstance(x, float) and 0 <= x <= 1,
89
			'epsilon': lambda x: isinstance(x, float) and 0 < x < 1
90
		})
91
		return d
92
93
	def setParameters(self, NP=50, N_max=0.01, V_f=0.02, D_max=0.002, C_t=0.93, W_n=0.42, W_f=0.38, d_s=2.63, nn=5, Cr=0.2, Mu=0.05, epsilon=1e-31, **ukwargs):
94
		r"""Set the arguments of an algorithm.
95
96
		Arguments:
97
			NP (Optional[int]): Number of krill herds in population.
98
			N_max (Optional[float]): Maximum induced speed.
99
			V_f (Optional[float]): Foraging speed.
100
			D_max (Optional[float]): Maximum diffusion speed.
101
			C_t (Optional[float]): Constant $\in [0, 2]$.
102
			W_n (Optional[Union[int, float, numpy.ndarray]]): Intera weights of the motion induced from neighbors :math:`\in [0, 1]`.
103
			W_f (Optional[Union[int, float, numpy.ndarray]]): Intera weights of the motion induced from foraging :math:`\in [0, 1]`.
104
			d_s (Optional[float]): Maximum euclidean distance for neighbors.
105
			nn (Optional[int]): Maximum neighbors for neighbors effect.
106
			Cr (Optional[float]): Crossover probability.
107
			Mu (Optional[float]): Mutation probability.
108
			epsilon (Optional[float]): Small numbers for division.
109
110
		See Also:
111
			* :func:`NiaPy.algorithms.algorithm.Algorithm.setParameters`
112
		"""
113
		Algorithm.setParameters(self, NP=NP, **ukwargs)
114
		self.N_max, self.V_f, self.D_max, self.C_t, self.W_n, self.W_f, self.d_s, self.nn, self._Cr, self._Mu, self.epsilon = N_max, V_f, D_max, C_t, W_n, W_f, d_s, nn, Cr, Mu, epsilon
115
		if ukwargs: logger.info('Unused arguments: %s' % (ukwargs))
116
117
	def initWeights(self, task):
118
		r"""Initialize weights.
119
120
		Args:
121
			task (Task): Optimization task.
122
123
		Returns:
124
			Tuple[numpy.ndarray, numpy.ndarray]:
125
				1. Weights for neighborhood.
126
				2. Weights for foraging.
127
		"""
128
		return fullArray(self.W_n, task.D), fullArray(self.W_f, task.D)
129
130
	def sensRange(self, ki, KH):
131
		r"""Calculate sense range for selected individual.
132
133
		Args:
134
			ki (int): Selected individual.
135
			KH (numpy.ndarray): Krill heard population.
136
137
		Returns:
138
			TODO
139
		"""
140
		return sum([ed(KH[ki], KH[i]) for i in range(self.NP)]) / (self.nn * self.NP)
141
142
	def getNeighbours(self, i, ids, KH):
143
		r"""Get neighbours.
144
145
		Args:
146
			i (int): Individual looking for neighbours.
147
			ids (float): Maximal distance for being a neighbour.
148
			KH (numpy.ndarray): Current population.
149
150
		Returns:
151
			numpy.ndarray: Neighbours of krill heard.
152
		"""
153
		N = list()
154
		for j in range(self.NP):
155
			if j != i and ids > ed(KH[i], KH[j]): N.append(j)
156
		if not N: N.append(self.randint(self.NP))
157
		return asarray(N)
158
159
	def funX(self, x, y): return ((y - x) + self.epsilon) / (ed(y, x) + self.epsilon)
160
161
	def funK(self, x, y, b, w): return ((x - y) + self.epsilon) / ((w - b) + self.epsilon)
162
163
	def induceNeighborsMotion(self, i, n, W, KH, KH_f, ikh_b, ikh_w, task):
164
		Ni = self.getNeighbours(i, self.sensRange(i, KH), KH)
165
		Nx, Nf, f_b, f_w = KH[Ni], KH_f[Ni], KH_f[ikh_b], KH_f[ikh_w]
166
		alpha_l = sum(asarray([self.funK(KH_f[i], j, f_b, f_w) for j in Nf]) * asarray([self.funX(KH[i], j) for j in Nx]).T)
167
		alpha_t = 2 * (1 + self.rand() * task.Iters / task.nGEN)
168
		return self.N_max * (alpha_l + alpha_t) + W * n
169
170
	def induceForagingMotion(self, i, x, x_f, f, W, KH, KH_f, ikh_b, ikh_w, task):
171
		beta_f = 2 * (1 - task.Iters / task.nGEN) * self.funK(KH_f[i], x_f, KH_f[ikh_b], KH_f[ikh_w]) * self.funX(KH[i], x) if KH_f[ikh_b] < KH_f[i] else 0
172
		beta_b = self.funK(KH_f[i], KH_f[ikh_b], KH_f[ikh_b], KH_f[ikh_w]) * self.funX(KH[i], KH[ikh_b])
173
		return self.V_f * (beta_f + beta_b) + W * f
174
175
	def inducePhysicalDiffusion(self, task): return self.D_max * (1 - task.Iters / task.nGEN) * self.uniform(-1, 1, task.D)
176
177
	def deltaT(self, task): return self.C_t * sum(task.bcRange())
178
179
	def crossover(self, x, xo, Cr): return [xo[i] if self.rand() < Cr else x[i] for i in range(len(x))]
180
181
	def mutate(self, x, x_b, Mu):
182
		return [x[i] if self.rand() < Mu else (x_b[i] + self.rand()) for i in range(len(x))]
183
184
	def getFoodLocation(self, KH, KH_f, task):
185
		x_food = task.repair(asarray([sum(KH[:, i] / KH_f) for i in range(task.D)]) / sum(1 / KH_f), rnd=self.Rand)
186
		x_food_f = task.eval(x_food)
187
		return x_food, x_food_f
188
189
	def Mu(self, xf, yf, xf_best, xf_worst): return self._Mu / (self.funK(xf, yf, xf_best, xf_worst) + 1e-31)
190
191
	def Cr(self, xf, yf, xf_best, xf_worst): return self._Cr * self.funK(xf, yf, xf_best, xf_worst)
192
193
	def initPopulation(self, task):
194
		r"""Initialize stating population.
195
196
		Args:
197
			task (Task): Optimization task.
198
199
		Returns:
200
			Tuple[numpy.ndarray, numpy.ndarray[float], Dict[str, Any]]:
201
				1. Initialized population.
202
				2. Initialized populations function/fitness values.
203
				3. Additional arguments:
204
					* W_n (numpy.ndarray): Weights neighborhood.
205
					* W_f (numpy.ndarray): Weights foraging.
206
					* N (numpy.ndarray): TODO
207
					* F (numpy.ndarray): TODO
208
209
		See Also:
210
			* :func:`NiaPy.algorithms.algorithm.Algorithm.initPopulation`
211
		"""
212
		KH, KH_f, d = Algorithm.initPopulation(self, task)
213
		W_n, W_f = self.initWeights(task)
214
		N, F = full(self.NP, .0), full(self.NP, .0)
215
		d.update({'W_n': W_n, 'W_f': W_f, 'N': N, 'F': F})
216
		return KH, KH_f, d
217
218
	def runIteration(self, task, KH, KH_f, xb, fxb, W_n, W_f, N, F, **dparams):
219
		ikh_b, ikh_w = argmin(KH_f), argmax(KH_f)
220
		x_food, x_food_f = self.getFoodLocation(KH, KH_f, task)
221
		N = asarray([self.induceNeighborsMotion(i, N[i], W_n, KH, KH_f, ikh_b, ikh_w, task) for i in range(self.NP)])
222
		F = asarray([self.induceForagingMotion(i, x_food, x_food_f, F[i], W_f, KH, KH_f, ikh_b, ikh_w, task) for i in range(self.NP)])
223
		D = asarray([self.inducePhysicalDiffusion(task) for i in range(self.NP)])
224
		KH_n = KH + (self.deltaT(task) * (N + F + D))
225
		Cr = asarray([self.Cr(KH_f[i], KH_f[ikh_b], KH_f[ikh_b], KH_f[ikh_w]) for i in range(self.NP)])
226
		KH_n = asarray([self.crossover(KH_n[i], KH[i], Cr[i]) for i in range(self.NP)])
227
		Mu = asarray([self.Mu(KH_f[i], KH_f[ikh_b], KH_f[ikh_b], KH_f[ikh_w]) for i in range(self.NP)])
228
		KH_n = asarray([self.mutate(KH_n[i], KH[ikh_b], Mu[i]) for i in range(self.NP)])
229
		KH = apply_along_axis(task.repair, 1, KH_n, rnd=self.Rand)
230
		KH_f = apply_along_axis(task.eval, 1, KH)
231
		return KH, KH_f, {'W_n': W_n, 'W_f': W_f, 'N': N, 'F': F}
232
233
class KrillHerdV4(KrillHerd):
234
	r"""Implementation of krill herd algorithm.
235
236
	Algorithm:
237
		Krill Herd Algorithm
238
239
	Date:
240
		2018
241
242
	Authors:
243
		Klemen Berkovič
244
245
	License:
246
		MIT
247
248
	Reference URL:
249
		http://www.sciencedirect.com/science/article/pii/S1007570412002171
250
251
	Reference paper:
252
		Amir Hossein Gandomi, Amir Hossein Alavi, Krill herd: A new bio-inspired optimization algorithm, Communications in Nonlinear Science and Numerical Simulation, Volume 17, Issue 12, 2012, Pages 4831-4845, ISSN 1007-5704, https://doi.org/10.1016/j.cnsns.2012.05.010.
253
254
	Attributes:
255
		Name (List[str]): List of strings representing algorithm name.
256
	"""
257
	Name = ['KrillHerdV4', 'KHv4']
258
259
	@staticmethod
260
	def typeParameters():
261
		r"""Get dictionary with functions for checking values of parameters.
262
263
		Returns:
264
			Dict[str, Callable]:
265
				* TODO
266
267
		See Also:
268
			* :func:NiaPy.algorithms.basic.kh.KrillHerd.typeParameters`
269
		"""
270
		d = KrillHerd.typeParameters()
271
		del d['Cr']
272
		del d['Mu']
273
		del d['epsilon']
274
		return d
275
276
	def setParameters(self, NP=50, N_max=0.01, V_f=0.02, D_max=0.002, C_t=0.93, W_n=0.42, W_f=0.38, d_s=2.63, **ukwargs):
277
		r"""Set algorithm core parameters.
278
279
		Args:
280
			N_max (Optional[float]): TODO
281
			V_f (Optional[float]): TODO
282
			D_max (Optional[float]): TODO
283
			C_t (Optional[float]): TODO
284
			W_n (Optional]float]): TODO
285
			W_f (Optional[float]): TODO
286
			d_s (Optional[float]): TODO
287
			**ukwargs (Dict[str, Any]): Additional arguments.
288
289
      See Also:
290
      	* :func:NiaPy.algorithms.basic.kh.KrillHerd.KrillHerd.setParameters`
291
		"""
292
		KrillHerd.setParameters(self, NP, N_max, V_f, D_max, C_t, W_n, W_f, d_s, 4, 0.2, 0.05, 1e-31, **ukwargs)
293
294
class KrillHerdV1(KrillHerd):
295
	r"""Implementation of krill herd algorithm.
296
297
	Algorithm:
298
		Krill Herd Algorithm
299
300
	Date:
301
		2018
302
303
	Authors:
304
		Klemen Berkovič
305
306
	License:
307
		MIT
308
309
	Reference URL:
310
		http://www.sciencedirect.com/science/article/pii/S1007570412002171
311
312
	Reference paper:
313
		Amir Hossein Gandomi, Amir Hossein Alavi, Krill herd: A new bio-inspired optimization algorithm, Communications in Nonlinear Science and Numerical Simulation, Volume 17, Issue 12, 2012, Pages 4831-4845, ISSN 1007-5704, https://doi.org/10.1016/j.cnsns.2012.05.010.
314
315
	Attributes:
316
		Name (List[str]): List of strings representing algorithm name.
317
318
	See Also:
319
		* :func:NiaPy.algorithms.basic.kh.KrillHerd.KrillHerd`
320
	"""
321
	Name = ['KrillHerdV1', 'KHv1']
322
323
	@staticmethod
324
	def typeParameters():
325
		r"""Get dictionary with functions for checking values of parameters.
326
327
		Returns:
328
			Dict[str, Callable]:
329
				* TODO
330
331
		See Also:
332
			* :func:NiaPy.algorithms.basic.kh.KrillHerd.typeParameters`
333
		"""
334
		return KrillHerd.typeParameters()
335
336
	def crossover(self, x, xo, Cr):
337
		r"""Preform a crossover operation on individual.
338
339
		Args:
340
			x (numpy.ndarray): Current individual
341
			xo (numpy.ndarray): New individual
342
			Cr (float): Crossover probability
343
344
		Returns:
345
			numpy.ndarray: Crossoved individual
346
		"""
347
		return x
348
349
	def mutate(self, x, x_b, Mu):
350
		r"""Mutate individual.
351
352
		Args:
353
			x (numpy.ndarray): Current individual.
354
			x_b (numpy.ndarray): Global best individual.
355
			Mu (float): TODO
356
357
		Returns:
358
			numpy.ndarray: TODO
359
		"""
360
		return x
361
362
class KrillHerdV2(KrillHerd):
363
	r"""Implementation of krill herd algorithm.
364
365
	Algorithm:
366
		Krill Herd Algorithm
367
368
	Date:
369
		2018
370
371
	Authors:
372
		Klemen Berkovič
373
374
	License:
375
		MIT
376
377
	Reference URL:
378
		http://www.sciencedirect.com/science/article/pii/S1007570412002171
379
380
	Reference paper:
381
		Amir Hossein Gandomi, Amir Hossein Alavi, Krill herd: A new bio-inspired optimization algorithm, Communications in Nonlinear Science and Numerical Simulation, Volume 17, Issue 12, 2012, Pages 4831-4845, ISSN 1007-5704, https://doi.org/10.1016/j.cnsns.2012.05.010.
382
383
	Attributes:
384
		Name (List[str]): List of strings representing algorithm name.
385
	"""
386
	Name = ['KrillHerdV2', 'KHv2']
387
388
	@staticmethod
389
	def typeParameters():
390
		r"""Get dictionary with functions for checking values of parameters.
391
392
		Returns:
393
			Dict[str, Callable]:
394
				* TODO
395
396
		See Also:
397
			* :func:NiaPy.algorithms.basic.kh.KrillHerd.typeParameters`
398
		"""
399
		d = KrillHerd.typeParameters()
400
		del d['Mu']
401
		return d
402
403
	def mutate(self, x, x_b, Mu): return x
404
405
class KrillHerdV3(KrillHerd):
406
	r"""Implementation of krill herd algorithm.
407
408
	Algorithm:
409
		Krill Herd Algorithm
410
411
	Date:
412
		2018
413
414
	Authors:
415
		Klemen Berkovič
416
417
	License:
418
		MIT
419
420
	Reference URL:
421
		http://www.sciencedirect.com/science/article/pii/S1007570412002171
422
423
	Reference paper:
424
		Amir Hossein Gandomi, Amir Hossein Alavi, Krill herd: A new bio-inspired optimization algorithm, Communications in Nonlinear Science and Numerical Simulation, Volume 17, Issue 12, 2012, Pages 4831-4845, ISSN 1007-5704, https://doi.org/10.1016/j.cnsns.2012.05.010.
425
	"""
426
	Name = ['KrillHerdV3', 'KHv3']
427
428
	@staticmethod
429
	def typeParameters():
430
		d = KrillHerd.typeParameters()
431
		del d['Cr']
432
		return d
433
434
	def crossover(self, x, xo, Cr): return x
435
436
class KrillHerdV11(KrillHerd):
437
	r"""Implementation of krill herd algorithm.
438
439
	Algorithm:
440
		Krill Herd Algorithm
441
442
	Date:
443
		2018
444
445
	Authors:
446
		Klemen Berkovič
447
448
	License:
449
		MIT
450
451
	Reference URL:
452
453
	Reference paper:
454
	"""
455
	Name = ['KrillHerdV11', 'KHv11']
456
457
	def ElitistSelection(self, KH, KH_f, KHo, KHo_f):
458
		ipb = where(KHo_f >= KH_f)
459
		KHo[ipb], KHo_f[ipb] = KH[ipb], KH_f[ipb]
460
		return KHo, KHo_f
461
462
	def Neighbors(self, i, KH, KH_f, iw, ib, N, W_n, task):
463
		Rgb, RR, Kw_Kgb = KH[ib] - KH[i], KH - KH[i], KH_f[iw] - KH_f[ib]
464
		R = sqrt(sum(RR * RR))
465
		alpha_b = -2 * (1 + self.rand() * task.Iters / task.nGEN) * (KH_f[ib]) / Kw_Kgb / sqrt(sum(Rgb * Rgb)) * Rgb if KH_f[ib] < KH_f[i] else 0
466
		alpah_n, nn, ds = 0.0, 0, mean(R) / 5
467
		for n in range(self.NP):
468
			if R < ds and n != i:
469
				nn += 1
470
				if nn <= 4 and KH_f[i] != KH[n]: alpah_n -= (KH(n) - KH[i]) / Kw_Kgb / R[n] * RR[n]
471
		return W_n * N * self.N_max * (alpha_b + alpah_n)
472
473
	def Foraging(self, KH, KH_f, KHo, KHo_f, W_f, F, KH_wf, KH_bf, x_food, x_food_f, task):
474
		Rf, Kw_Kgb = x_food - KH, KH_wf - KH_bf
475
		beta_f = -2 * (1 - task.Iters / task.nGEN) * (x_food_f - KH_f) / Kw_Kgb / sqrt(sum(Rf * Rf)) * Rf if x_food_f < KH_f else 0
476
		Rib = KHo - KH
477
		beta_b = -(KHo_f - KH_f) / Kw_Kgb / sqrt(sum(Rib * Rib)) * Rib if KHo_f < KH_f else 0
478
		return W_f * F + self.V_f * (beta_b + beta_f)
479
480
	def Cr(self, KH_f, KHb_f, KHw_f): return 0.8 + 0.2 * (KH_f - KHb_f) / (KHw_f - KHb_f)
481
482
	def initPopulation(self, task):
483
		KH, KH_f, d = Algorithm.initPopulation(self, task)
484
		KHo, KHo_f = full([self.NP, task.D], task.optType.value * inf), full(self.NP, task.optType.value * inf)
485
		N, F, Dt = full(self.NP, .0), full(self.NP, .0), mean(task.bcRange()) / 2
486
		d.update({'KHo': KHo, 'KHo_f': KHo_f, 'N': N, 'F': F, 'Dt': Dt})
487
		return KH, KH_f, d
488
489
	def runIteration(self, task, KH, KH_f, xb, fxb, KHo, KHo_f, N, F, Dt, **dparams):
490
		w = full(task.D, 0.1 + 0.8 * (1 - task.Iters / task.nGEN))
491
		ib, iw = argmin(KH_f), argmax(KH_f)
492
		x_food, x_food_f = self.getFoodLocation(KH, KH_f, task)
493
		N = asarray([self.Neighbors(i, KH, KH_f, iw, ib, N[i], w, task) for i in range(self.NP)])
494
		F = asarray([self.Foraging(KH[i], KH_f[i], KHo[i], KHo_f[i], w, F[i], KH_f[iw], KH_f[ib], x_food, x_food_f, task) for i in range(self.NP)])
495
		Cr = asarray([self.Cr(KH_f[i], KH_f[ib], KH_f[iw]) for i in range(self.NP)])
496
		KH_n = asarray([self.crossover(KH[self.randint(self.NP)], KH[i], Cr[i]) for i in range(self.NP)])
497
		KH_n = KH + Dt * (F + N)
498
		KH = apply_along_axis(task.repair, 1, KH_n, self.Rand)
499
		KH_f = apply_along_axis(task.eval, 1, KH)
500
		KHo, KHo_f = self.ElitistSelection(KH, KH_f, KHo, KHo_f)
501
		return KH, KH_f, {'KHo': KHo, 'KHo_f': KHo_f, 'N': N, 'F': F, 'Dt': Dt}
502
503
# vim: tabstop=3 noexpandtab shiftwidth=3 softtabstop=3
504