Passed
Pull Request — master (#233)
by Grega
01:17
created

NiaPy.algorithms.algorithm.Algorithm.normal()   A

Complexity

Conditions 2

Size

Total Lines 12
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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