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

NiaPy.algorithms.algorithm   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 472
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 56
eloc 122
dl 0
loc 472
rs 5.5199
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A Individual.copy() 0 9 1
A Individual.evaluate() 0 15 1
A Algorithm.randint() 0 17 4
A Algorithm.bad_run() 0 7 1
A Individual.__str__() 0 7 1
A Algorithm.uniform() 0 12 2
A Algorithm.runTask() 0 19 2
A Algorithm.normal() 0 12 2
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.runYield() 0 24 2
A Algorithm.getParameters() 0 12 1
A Algorithm.run() 0 21 3
A Algorithm.typeParameters() 0 9 2
A Individual.__eq__() 0 14 4
A Algorithm.randn() 0 12 3
A Algorithm.setParameters() 0 14 1
A Algorithm.rand() 0 12 3
A Algorithm.algorithmInfo() 0 8 1
A Individual.__len__() 0 7 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

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
		except Exception as e: self.exception = e
339
		return None, None
340
341
	def bad_run(self):
342
		r"""Check if some exeptions where thrown when the algorithm was running.
343
344
		Returns:
345
			bool: True if some error where detected at runtime of the algorithm, otherwise False
346
		"""
347
		return self.exception is not None
348
349
class Individual:
350
	r"""Class that represents one solution in population of solutions.
351
352
	Date:
353
		2018
354
355
	Author:
356
		Klemen Berkovič
357
358
	License:
359
		MIT
360
361
	Attributes:
362
		x (numpy.ndarray): Coordinates of individual.
363
		f (float): Function/fitness value of individual.
364
	"""
365
	x = None
366
	f = inf
367
368
	def __init__(self, x=None, task=None, e=True, rnd=rand, **kwargs):
369
		r"""Initialize new individual.
370
371
		Parameters:
372
			task (Optional[Task]): Optimization task.
373
			rand (Optional[mtrand.RandomState]): Random generator.
374
			x (Optional[numpy.ndarray]): Individuals components.
375
			e (Optional[bool]): True to evaluate the individual on initialization. Default value is True.
376
			**kwargs (Dict[str, Any]): Additional arguments.
377
		"""
378
		self.f = task.optType.value * inf if task is not None else inf
379
		if x is not None: self.x = x if isinstance(x, ndarray) else asarray(x)
380
		else: self.generateSolution(task, rnd)
381
		if e and task is not None: self.evaluate(task, rnd)
382
383
	def generateSolution(self, task, rnd=rand):
384
		r"""Generate new solution.
385
386
		Generate new solution for this individual and set it to ``self.x``.
387
		This method uses ``rnd`` for getting random numbers.
388
		For generating random components ``rnd`` and ``task`` is used.
389
390
		Args:
391
			task (Task): Optimization task.
392
			rnd (Optional[mtrand.RandomState]): Random numbers generator object.
393
		"""
394
		if task is not None: self.x = task.Lower + task.bRange * rnd.rand(task.D)
395
396
	def evaluate(self, task, rnd=rand):
397
		r"""Evaluate the solution.
398
399
		Evaluate solution ``this.x`` with the help of task.
400
		Task is used for reparing the solution and then evaluating it.
401
402
		Args:
403
			task (Task): Objective function object.
404
			rnd (Optional[mtrand.RandomState]): Random generator.
405
406
		See Also:
407
			* :func:`NiaPy.util.Task.repair`
408
		"""
409
		self.x = task.repair(self.x, rnd=rnd)
410
		self.f = task.eval(self.x)
411
412
	def copy(self):
413
		r"""Return a copy of self.
414
415
		Method returns copy of ``this`` object so it is safe for editing.
416
417
		Returns:
418
			Individual: Copy of self.
419
		"""
420
		return Individual(x=self.x.copy(), f=self.f, e=False)
421
422
	def __eq__(self, other):
423
		r"""Compare the individuals for equalities.
424
425
		Args:
426
			other (Union[Any, numpy.ndarray]): Object that we want to compare this object to.
427
428
		Returns:
429
			bool: `True` if equal or `False` if no equal.
430
		"""
431
		if isinstance(other, ndarray):
432
			for e in other:
433
				if self == e: return True
434
			return False
435
		return array_equal(self.x, other.x) and self.f == other.f
436
437
	def __str__(self):
438
		r"""Print the individual with the solution and objective value.
439
440
		Returns:
441
			str: String representation of self.
442
		"""
443
		return '%s -> %s' % (self.x, self.f)
444
445
	def __getitem__(self, i):
446
		r"""Get the value of i-th component of the solution.
447
448
		Args:
449
			i (int): Position of the solution component.
450
451
		Returns:
452
			Any: Value of ith component.
453
		"""
454
		return self.x[i]
455
456
	def __setitem__(self, i, v):
457
		r"""Set the value of i-th component of the solution to v value.
458
459
		Args:
460
			i (int): Position of the solution component.
461
			v (Any): Value to set to i-th component.
462
		"""
463
		self.x[i] = v
464
465
	def __len__(self):
466
		r"""Get the length of the solution or the number of components.
467
468
		Returns:
469
			int: Number of components.
470
		"""
471
		return len(self.x)
472
473
# vim: tabstop=3 noexpandtab shiftwidth=3 softtabstop=3
474