NiaPy.algorithms.algorithm   C
last analyzed

Complexity

Total Complexity 55

Size/Duplication

Total Lines 471
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 121
dl 0
loc 471
rs 6
c 0
b 0
f 0
wmc 55

26 Methods

Rating   Name   Duplication   Size   Complexity  
A Algorithm.randint() 0 17 4
A Algorithm.uniform() 0 12 2
A Algorithm.runTask() 0 19 2
A Algorithm.normal() 0 12 2
A Algorithm.runYield() 0 24 2
A Algorithm.getParameters() 0 12 1
A Algorithm.typeParameters() 0 9 2
A Algorithm.randn() 0 12 3
A Algorithm.setParameters() 0 14 1
A Algorithm.rand() 0 12 3
A Algorithm.algorithmInfo() 0 8 1
B Algorithm.getBest() 0 18 6
A Algorithm.__init__() 0 11 1
A Algorithm.initPopulation() 0 17 1
A Algorithm.runIteration() 0 25 1
A Individual.copy() 0 9 1
A Individual.evaluate() 0 15 1
A Algorithm.bad_run() 0 7 1
A Individual.__str__() 0 7 1
B Individual.__init__() 0 14 6
A Individual.__setitem__() 0 8 1
A Individual.__getitem__() 0 10 1
A Individual.generateSolution() 0 12 2
A Algorithm.run() 0 20 2
A Individual.__eq__() 0 14 4
A Individual.__len__() 0 7 1

2 Functions

Rating   Name   Duplication   Size   Complexity  
A defaultIndividualInit() 0 17 1
A defaultNumPyInit() 0 17 1

How to fix   Complexity   

Complexity

Complex classes like NiaPy.algorithms.algorithm often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# encoding=utf8
2
import logging
3
4
from numpy import random as rand, inf, ndarray, asarray, array_equal, argmin, apply_along_axis
5
6
from NiaPy.util import FesException, GenException, TimeException, RefException
7
from NiaPy.util.utility import objects2array
8
9
logging.basicConfig()
10
logger = logging.getLogger('NiaPy.util.utility')
11
logger.setLevel('INFO')
12
13
__all__ = [
14
	'Algorithm',
15
	'Individual',
16
	'defaultIndividualInit',
17
	'defaultNumPyInit'
18
]
19
20
def defaultNumPyInit(task, NP, rnd=rand, **kwargs):
21
	r"""Initialize starting population that is represented with `numpy.ndarray` with shape `{NP, task.D}`.
22
23
	Args:
24
		task (Task): Optimization task.
25
		NP (int): Number of individuals in population.
26
		rnd (Optional[mtrand.RandomState]): Random number generator.
27
		kwargs (Dict[str, Any]): Additional arguments.
28
29
	Returns:
30
		Tuple[numpy.ndarray, numpy.ndarray[float]]:
31
			1. New population with shape `{NP, task.D}`.
32
			2. New population function/fitness values.
33
	"""
34
	pop = task.Lower + rnd.rand(NP, task.D) * task.bRange
35
	fpop = apply_along_axis(task.eval, 1, pop)
36
	return pop, fpop
37
38
def defaultIndividualInit(task, NP, rnd=rand, itype=None, **kwargs):
39
	r"""Initialize `NP` individuals of type `itype`.
40
41
	Args:
42
		task (Task): Optimization task.
43
		NP (int): Number of individuals in population.
44
		rnd (Optional[mtrand.RandomState]): Random number generator.
45
		itype (Optional[Individual]): Class of individual in population.
46
		kwargs (Dict[str, Any]): Additional arguments.
47
48
	Returns:
49
		Tuple[numpy.ndarray[Individual], numpy.ndarray[float]:
50
			1. Initialized individuals.
51
			2. Initialized individuals function/fitness values.
52
	"""
53
	pop = objects2array([itype(task=task, rnd=rnd, e=True) for _ in range(NP)])
54
	return pop, asarray([x.f for x in pop])
55
56
class Algorithm:
57
	r"""Class for implementing algorithms.
58
59
	Date:
60
		2018
61
62
	Author
63
		Klemen Berkovič
64
65
	License:
66
		MIT
67
68
	Attributes:
69
		Name (List[str]): List of names for algorithm.
70
		Rand (mtrand.RandomState): Random generator.
71
		NP (int): Number of inidividuals in populatin.
72
		InitPopFunc (Callable[[int, Task, mtrand.RandomState, Dict[str, Any]], Tuple[numpy.ndarray, numpy.ndarray[float]]]): Idividual initialization function.
73
		itype (Individual): Type of individuals used in population, default value is None for Numpy arrays.
74
	"""
75
	Name = ['Algorithm', 'AAA']
76
	Rand = rand.RandomState(None)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable rand does not seem to be defined.
Loading history...
77
	NP = 50
78
	InitPopFunc = defaultNumPyInit
79
	itype = None
80
81
	@staticmethod
82
	def typeParameters():
83
		r"""Return functions for checking values of parameters.
84
85
		Return:
86
			Dict[str, Callable]:
87
				* NP (Callable[[int], bool]): Check if number of individuals is :math:`\in [0, \infty]`.
88
		"""
89
		return {'NP': lambda x: isinstance(x, int) and x > 0}
90
91
	def __init__(self, **kwargs):
92
		r"""Initialize algorithm and create name for an algorithm.
93
94
		Args:
95
			seed (int): Starting seed for random generator.
96
97
		See Also:
98
			* :func:`NiaPy.algorithms.Algorithm.setParameters`
99
		"""
100
		self.Rand, self.exception = rand.RandomState(kwargs.pop('seed', None)), None
101
		self.setParameters(**kwargs)
102
103
	@staticmethod
104
	def algorithmInfo():
105
		r"""Get algorithm information.
106
107
		Returns:
108
			str: Bit item.
109
		"""
110
		return '''Basic algorithm. No implementation!!!'''
111
112
	def setParameters(self, NP=50, InitPopFunc=defaultNumPyInit, itype=None, **kwargs):
113
		r"""Set the parameters/arguments of the algorithm.
114
115
		Args:
116
			NP (Optional[int]): Number of individuals in population :math:`\in [1, \infty]`.
117
			InitPopFunc (Optional[Callable[[int, Task, mtrand.RandomState, Dict[str, Any]], Tuple[numpy.ndarray, numpy.ndarray[float]]]]): Type of individuals used by algorithm.
118
			itype (Optional[Any]): Individual type used in population, default is Numpy array.
119
			**kwargs (Dict[str, Any]): Additional arguments.
120
121
		See Also:
122
			* :func:`NiaPy.algorithms.defaultNumPyInit`
123
			* :func:`NiaPy.algorithms.defaultIndividualInit`
124
		"""
125
		self.NP, self.InitPopFunc, self.itype = NP, InitPopFunc, itype
126
127
	def getParameters(self):
128
		r"""Get parameters of the algorithm.
129
130
		Returns:
131
			Dict[str, Any]:
132
			* Parameter name (str): Represents a parameter name
133
			* Value of parameter (Any): Represents the value of the parameter
134
		"""
135
		return {
136
			'NP': self.NP,
137
			'InitPopFunc': self.InitPopFunc,
138
			'itype': self.itype
139
		}
140
141
	def rand(self, D=1):
142
		r"""Get random distribution of shape D in range from 0 to 1.
143
144
		Args:
145
			D (numpy.ndarray[int]): Shape of returned random distribution.
146
147
		Returns:
148
			Union[numpy.ndarray[float], float]: Random number or numbers :math:`\in [0, 1]`.
149
		"""
150
		if isinstance(D, (ndarray, list)): return self.Rand.rand(*D)
151
		elif D > 1: return self.Rand.rand(D)
152
		else: return self.Rand.rand()
153
154
	def uniform(self, Lower, Upper, D=None):
155
		r"""Get uniform random distribution of shape D in range from "Lower" to "Upper".
156
157
		Args:
158
			Lower (Iterable[float]): Lower bound.
159
			Upper (Iterable[float]): Upper bound.
160
			D (Union[int, Iterable[int]]): Shape of returned uniform random distribution.
161
162
		Returns:
163
			Union[numpy.ndarray[float], float]: Array of numbers :math:`\in [\mathit{Lower}, \mathit{Upper}]`.
164
		"""
165
		return self.Rand.uniform(Lower, Upper, D) if D is not None else self.Rand.uniform(Lower, Upper)
166
167
	def normal(self, loc, scale, D=None):
168
		r"""Get normal random distribution of shape D with mean "loc" and standard deviation "scale".
169
170
		Args:
171
			loc (float): Mean of the normal random distribution.
172
			scale (float): Standard deviation of the normal random distribution.
173
			D (Union[int, Iterable[int]]): Shape of returned normal random distribution.
174
175
		Returns:
176
			Union[numpy.ndarray[float], float]: Array of numbers.
177
		"""
178
		return self.Rand.normal(loc, scale, D) if D is not None else self.Rand.normal(loc, scale)
179
180
	def randn(self, D=None):
181
		r"""Get standard normal distribution of shape D.
182
183
		Args:
184
			D (Union[int, Iterable[int]]): Shape of returned standard normal distribution.
185
186
		Returns:
187
			Union[numpy.ndarray[float], float]: Random generated numbers or one random generated number :math:`\in [0, 1]`.
188
		"""
189
		if D is None: return self.Rand.randn()
190
		elif isinstance(D, int): return self.Rand.randn(D)
191
		return self.Rand.randn(*D)
192
193
	def randint(self, Nmax, D=1, Nmin=0, skip=None):
194
		r"""Get discrete uniform (integer) random distribution of D shape in range from "Nmin" to "Nmax".
195
196
		Args:
197
			Nmin (int): Lower integer bound.
198
			Nmax (int): One above upper integer bound.
199
			D (Union[int, Iterable[int]]): shape of returned discrete uniform random distribution.
200
			skip (Union[int, Iterable[int], numpy.ndarray[int]]): numbers to skip.
201
202
		Returns:
203
			Union[int, numpy.ndarrayj[int]]: Random generated integer number.
204
		"""
205
		r = None
206
		if isinstance(D, (list, tuple, ndarray)): r = self.Rand.randint(Nmin, Nmax, D)
207
		elif D > 1: r = self.Rand.randint(Nmin, Nmax, D)
208
		else: r = self.Rand.randint(Nmin, Nmax)
209
		return r if skip is None or r not in skip else self.randint(Nmax, D, Nmin, skip)
210
211
	def getBest(self, X, X_f, xb=None, xb_f=inf):
212
		r"""Get the best individual for population.
213
214
		Args:
215
			X (numpy.ndarray): Current population.
216
			X_f (numpy.ndarray): Current populations fitness/function values of aligned individuals.
217
			xb (numpy.ndarray): Best individual.
218
			xb_f (float): Fitness value of best individual.
219
220
		Returns:
221
			Tuple[numpy.ndarray, float]:
222
				1. Coordinates of best solution.
223
				2. beset fitness/function value.
224
		"""
225
		ib = argmin(X_f)
226
		if isinstance(X_f, (float, int)) and xb_f >= X_f: xb, xb_f = X, X_f
227
		elif isinstance(X_f, (ndarray, list)) and xb_f >= X_f[ib]: xb, xb_f = X[ib], X_f[ib]
228
		return (xb.x.copy() if isinstance(xb, Individual) else xb.copy()), xb_f
229
230
	def initPopulation(self, task):
231
		r"""Initialize starting population of optimization algorithm.
232
233
		Args:
234
			task (Task): Optimization task.
235
236
		Returns:
237
			Tuple[numpy.ndarray, numpy.ndarray, Dict[str, Any]]:
238
				1. New population.
239
				2. New population fitness values.
240
				3. Additional arguments.
241
242
		See Also:
243
			* :func:`NiaPy.algorithms.Algorithm.setParameters`
244
		"""
245
		pop, fpop = self.InitPopFunc(task=task, NP=self.NP, rnd=self.Rand, itype=self.itype)
246
		return pop, fpop, {}
247
248
	def runIteration(self, task, pop, fpop, xb, fxb, **dparams):
249
		r"""Core functionality of algorithm.
250
251
		This function is called on every algorithm iteration.
252
253
		Args:
254
			task (Task): Optimization task.
255
			pop (numpy.ndarray): Current population coordinates.
256
			fpop (numpy.ndarray): Current population fitness value.
257
			xb (numpy.ndarray): Current generation best individuals coordinates.
258
			xb_f (float): current generation best individuals fitness value.
259
			**dparams (Dict[str, Any]): Additional arguments for algorithms.
260
261
		Returns:
262
			Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, float, Dict[str, Any]]:
263
				1. New populations coordinates.
264
				2. New populations fitness values.
265
				3. New global best position/solution
266
				4. New global best fitness/objective value
267
				5. Additional arguments of the algorithm.
268
269
		See Also:
270
			* :func:`NiaPy.algorithms.Algorithm.runYield`
271
		"""
272
		return pop, fpop, xb, fxb, dparams
273
274
	def runYield(self, task):
275
		r"""Run the algorithm for a single iteration and return the best solution.
276
277
		Args:
278
			task (Task): Task with bounds and objective function for optimization.
279
280
		Returns:
281
			Generator[Tuple[numpy.ndarray, float], None, None]: Generator getting new/old optimal global values.
282
283
		Yield:
284
			Tuple[numpy.ndarray, float]:
285
				1. New population best individuals coordinates.
286
				2. Fitness value of the best solution.
287
288
		See Also:
289
			* :func:`NiaPy.algorithms.Algorithm.initPopulation`
290
			* :func:`NiaPy.algorithms.Algorithm.runIteration`
291
		"""
292
		pop, fpop, dparams = self.initPopulation(task)
293
		xb, fxb = self.getBest(pop, fpop)
294
		yield xb, fxb
295
		while True:
296
			pop, fpop, xb, fxb, dparams = self.runIteration(task, pop, fpop, xb, fxb, **dparams)
297
			yield xb, fxb
298
299
	def runTask(self, task):
300
		r"""Start the optimization.
301
302
		Args:
303
			task (Task): Task with bounds and objective function for optimization.
304
305
		Returns:
306
			Tuple[numpy.ndarray, float]:
307
				1. Best individuals components found in optimization process.
308
				2. Best fitness value found in optimization process.
309
310
		See Also:
311
			* :func:`NiaPy.algorithms.Algorithm.runYield`
312
		"""
313
		algo, xb, fxb = self.runYield(task), None, inf
314
		while not task.stopCond():
315
			xb, fxb = next(algo)
316
			task.nextIter()
317
		return xb, fxb
318
319
	def run(self, task):
320
		r"""Start the optimization.
321
322
		Args:
323
			task (Task): Optimization task.
324
325
		Returns:
326
			Tuple[numpy.ndarray, float]:
327
				1. Best individuals components found in optimization process.
328
				2. Best fitness value found in optimization process.
329
330
		See Also:
331
			* :func:`NiaPy.algorithms.Algorithm.runTask`
332
		"""
333
		try:
334
			# task.start()
335
			r = self.runTask(task)
336
			return r[0], r[1] * task.optType.value
337
		except (FesException, GenException, TimeException, RefException): return task.x, task.x_f * task.optType.value
338
		return None, None
339
340
	def bad_run(self):
341
		r"""Check if some exeptions where thrown when the algorithm was running.
342
343
		Returns:
344
			bool: True if some error where detected at runtime of the algorithm, otherwise False
345
		"""
346
		return self.exception is not None
347
348
class Individual:
349
	r"""Class that represents one solution in population of solutions.
350
351
	Date:
352
		2018
353
354
	Author:
355
		Klemen Berkovič
356
357
	License:
358
		MIT
359
360
	Attributes:
361
		x (numpy.ndarray): Coordinates of individual.
362
		f (float): Function/fitness value of individual.
363
	"""
364
	x = None
365
	f = inf
366
367
	def __init__(self, x=None, task=None, e=True, rnd=rand, **kwargs):
368
		r"""Initialize new individual.
369
370
		Parameters:
371
			task (Optional[Task]): Optimization task.
372
			rand (Optional[mtrand.RandomState]): Random generator.
373
			x (Optional[numpy.ndarray]): Individuals components.
374
			e (Optional[bool]): True to evaluate the individual on initialization. Default value is True.
375
			**kwargs (Dict[str, Any]): Additional arguments.
376
		"""
377
		self.f = task.optType.value * inf if task is not None else inf
378
		if x is not None: self.x = x if isinstance(x, ndarray) else asarray(x)
379
		else: self.generateSolution(task, rnd)
380
		if e and task is not None: self.evaluate(task, rnd)
381
382
	def generateSolution(self, task, rnd=rand):
383
		r"""Generate new solution.
384
385
		Generate new solution for this individual and set it to ``self.x``.
386
		This method uses ``rnd`` for getting random numbers.
387
		For generating random components ``rnd`` and ``task`` is used.
388
389
		Args:
390
			task (Task): Optimization task.
391
			rnd (Optional[mtrand.RandomState]): Random numbers generator object.
392
		"""
393
		if task is not None: self.x = task.Lower + task.bRange * rnd.rand(task.D)
394
395
	def evaluate(self, task, rnd=rand):
396
		r"""Evaluate the solution.
397
398
		Evaluate solution ``this.x`` with the help of task.
399
		Task is used for reparing the solution and then evaluating it.
400
401
		Args:
402
			task (Task): Objective function object.
403
			rnd (Optional[mtrand.RandomState]): Random generator.
404
405
		See Also:
406
			* :func:`NiaPy.util.Task.repair`
407
		"""
408
		self.x = task.repair(self.x, rnd=rnd)
409
		self.f = task.eval(self.x)
410
411
	def copy(self):
412
		r"""Return a copy of self.
413
414
		Method returns copy of ``this`` object so it is safe for editing.
415
416
		Returns:
417
			Individual: Copy of self.
418
		"""
419
		return Individual(x=self.x.copy(), f=self.f, e=False)
420
421
	def __eq__(self, other):
422
		r"""Compare the individuals for equalities.
423
424
		Args:
425
			other (Union[Any, numpy.ndarray]): Object that we want to compare this object to.
426
427
		Returns:
428
			bool: `True` if equal or `False` if no equal.
429
		"""
430
		if isinstance(other, ndarray):
431
			for e in other:
432
				if self == e: return True
433
			return False
434
		return array_equal(self.x, other.x) and self.f == other.f
435
436
	def __str__(self):
437
		r"""Print the individual with the solution and objective value.
438
439
		Returns:
440
			str: String representation of self.
441
		"""
442
		return '%s -> %s' % (self.x, self.f)
443
444
	def __getitem__(self, i):
445
		r"""Get the value of i-th component of the solution.
446
447
		Args:
448
			i (int): Position of the solution component.
449
450
		Returns:
451
			Any: Value of ith component.
452
		"""
453
		return self.x[i]
454
455
	def __setitem__(self, i, v):
456
		r"""Set the value of i-th component of the solution to v value.
457
458
		Args:
459
			i (int): Position of the solution component.
460
			v (Any): Value to set to i-th component.
461
		"""
462
		self.x[i] = v
463
464
	def __len__(self):
465
		r"""Get the length of the solution or the number of components.
466
467
		Returns:
468
			int: Number of components.
469
		"""
470
		return len(self.x)
471
472
# vim: tabstop=3 noexpandtab shiftwidth=3 softtabstop=3
473