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

Individual.__getitem__()   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 2
dl 0
loc 10
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
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 = rand.RandomState(kwargs.pop('seed', None))
102
		self.setParameters(**kwargs)
103
104
	def setParameters(self, NP=50, InitPopFunc=defaultNumPyInit, itype=None, **kwargs):
105
		r"""Set the parameters/arguments of the algorithm.
106
107
		Args:
108
			NP (Optional[int]): Number of individuals in population :math:`\in [1, \infty]`.
109
			InitPopFunc (Optional[Callable[[int, Task, mtrand.RandomState, Dict[str, Any]], Tuple[numpy.ndarray, numpy.ndarray[float]]]]): Type of individuals used by algorithm.
110
			itype (Optional[Any]): Individual type used in population, default is Numpy array.
111
			**kwargs (Dict[str, Any]): Additional arguments.
112
113
		See Also:
114
			* :func:`NiaPy.algorithms.defaultNumPyInit`
115
			* :func:`NiaPy.algorithms.defaultIndividualInit`
116
		"""
117
		self.NP, self.InitPopFunc, self.itype = NP, InitPopFunc, itype
118
119
	def rand(self, D=1):
120
		r"""Get random distribution of shape D in range from 0 to 1.
121
122
		Args:
123
			D (numpy.ndarray[int]): Shape of returned random distribution.
124
125
		Returns:
126
			Union[numpy.ndarray[float], float]: Random number or numbers :math:`\in [0, 1]`.
127
		"""
128
		if isinstance(D, (ndarray, list)): return self.Rand.rand(*D)
129
		elif D > 1: return self.Rand.rand(D)
130
		else: return self.Rand.rand()
131
132
	def uniform(self, Lower, Upper, D=None):
133
		r"""Get uniform random distribution of shape D in range from "Lower" to "Upper".
134
135
		Args:
136
			Lower (Iterable[float]): Lower bound.
137
			Upper (Iterable[float]): Upper bound.
138
			D (Union[int, Iterable[int]]): Shape of returned uniform random distribution.
139
140
		Returns:
141
			Union[numpy.ndarray[float], float]: Array of numbers :math:`\in [\mathit{Lower}, \mathit{Upper}]`.
142
		"""
143
		return self.Rand.uniform(Lower, Upper, D) if D is not None else self.Rand.uniform(Lower, Upper)
144
145
	def normal(self, loc, scale, D=None):
146
		r"""Get normal random distribution of shape D with mean "loc" and standard deviation "scale".
147
148
		Args:
149
			loc (float): Mean of the normal random distribution.
150
			scale (float): Standard deviation of the normal random distribution.
151
			D (Union[int, Iterable[int]]): Shape of returned normal random distribution.
152
153
		Returns:
154
			Union[numpy.ndarray[float], float]: Array of numbers.
155
		"""
156
		return self.Rand.normal(loc, scale, D) if D is not None else self.Rand.normal(loc, scale)
157
158
	def randn(self, D=None):
159
		r"""Get standard normal distribution of shape D.
160
161
		Args:
162
			D (Union[int, Iterable[int]]): Shape of returned standard normal distribution.
163
164
		Returns:
165
			Union[numpy.ndarray[float], float]: Random generated numbers or one random generated number :math:`\in [0, 1]`.
166
		"""
167
		if D is None: return self.Rand.randn()
168
		elif isinstance(D, int): return self.Rand.randn(D)
169
		return self.Rand.randn(*D)
170
171
	def randint(self, Nmax, D=1, Nmin=0, skip=None):
172
		r"""Get discrete uniform (integer) random distribution of D shape in range from "Nmin" to "Nmax".
173
174
		Args:
175
			Nmin (int): Lower integer bound.
176
			Nmax (int): One above upper integer bound.
177
			D (Union[int, Iterable[int]]): shape of returned discrete uniform random distribution.
178
			skip (Union[int, Iterable[int], numpy.ndarray[int]]): numbers to skip.
179
180
		Returns:
181
			Union[int, numpy.ndarrayj[int]]: Random generated integer number.
182
		"""
183
		r = None
184
		if isinstance(D, (list, tuple, ndarray)): r = self.Rand.randint(Nmin, Nmax, D)
185
		elif D > 1: r = self.Rand.randint(Nmin, Nmax, D)
186
		else: r = self.Rand.randint(Nmin, Nmax)
187
		return r if skip is None or r not in skip else self.randint(Nmax, D, Nmin, skip)
188
189
	def getBest(self, X, X_f, xb=None, xb_f=inf):
190
		r"""Get the best individual for population.
191
192
		Args:
193
			X (numpy.ndarray): Current population.
194
			X_f (numpy.ndarray[float]): Current populations fitness/function values of aligned individuals.
195
			xb (numpy.ndarray): Best individual.
196
			xb_f (float): Fitness value of best individual.
197
198
		Returns:
199
			Tuple[numpy.ndarray, float]:
200
				1. Coordinates of best solution.
201
				2. beset fitness/function value.
202
		"""
203
		ib = argmin(X_f)
204
		if isinstance(X_f, (float, int)) and xb_f >= X_f: return X, X_f
205
		elif isinstance(X_f, (ndarray, list)) and xb_f >= X_f[ib]: return X[ib], X_f[ib]
206
		else: return xb.copy(), xb_f
207
208
	def initPopulation(self, task):
209
		r"""Initialize starting population of optimization algorithm.
210
211
		Args:
212
			task (Task): Optimization task.
213
214
		Returns:
215
			Tuple[numpy.ndarray, numpy.ndarray[float], Dict[str, Any]]:
216
				1. New population.
217
				2. New population fitness values.
218
				3. Additional arguments.
219
220
		See Also:
221
			* :func:`NiaPy.algorithms.Algorithm.setParameters`
222
		"""
223
		pop, fpop = self.InitPopFunc(task=task, NP=self.NP, rnd=self.Rand, itype=self.itype)
224
		return pop, fpop, {}
225
226
	def runIteration(self, task, pop, fpop, xb, fxb, **dparams):
227
		r"""Core functionality of algorithm.
228
229
		This function is called on every algorithm iteration.
230
231
		Args:
232
			task (Task): Optimization task.
233
			pop (numpy.ndarray): Current population coordinates.
234
			fpop (numpy.ndarray[float]): Current population fitness value.
235
			xb (numpy.ndarray): Current generation best individuals coordinates.
236
			xb_f (float): current generation best individuals fitness value.
237
			**dparams (Dict[str, Any]): Additional arguments for algorithms.
238
239
		Returns:
240
			Tuple[numpy.ndarray, numpy.ndarray[float], Dict[str, Any]]:
241
				1. New populations coordinates.
242
				2. New populations fitness values.
243
				3. Additional arguments of the algorithm.
244
245
		See Also:
246
			* :func:`NiaPy.algorithms.Algorithm.runYield`
247
		"""
248
		return pop, fpop, {}
249
250
	def runYield(self, task):
251
		r"""Run the algorithm for a single iteration and return the best solution.
252
253
		Args:
254
			task (Task): Task with bounds and objective function for optimization.
255
256
		Returns:
257
			Generator[Tuple[numpy.ndarray, float], None, None]: Generator getting new/old optimal global values.
258
259
		Yield:
260
			Tuple[numpy.ndarray, float]:
261
				1. New population best individuals coordinates.
262
				2. Fitness value of the best solution.
263
264
		See Also:
265
			* :func:`NiaPy.algorithms.Algorithm.initPopulation`
266
			* :func:`NiaPy.algorithms.Algorithm.runIteration`
267
		"""
268
		pop, fpop, dparams = self.initPopulation(task)
269
		xb, fxb = self.getBest(pop, fpop)
270
		yield xb, fxb
271
		while True:
272
			pop, fpop, dparams = self.runIteration(task, pop, fpop, xb, fxb, **dparams)
273
			xb, fxb = self.getBest(pop, fpop, xb, fxb)
274
			yield xb, fxb
275
276
	def runTask(self, task):
277
		r"""Start the optimization.
278
279
		Args:
280
			task (Task): Task with bounds and objective function for optimization.
281
282
		Returns:
283
			Tuple[numpy.ndarray, float]:
284
				1. Best individuals components found in optimization process.
285
				2. Best fitness value found in optimization process.
286
287
		See Also:
288
			* :func:`NiaPy.algorithms.Algorithm.runYield`
289
		"""
290
		algo, xb, fxb = self.runYield(task), None, inf
291
		while not task.stopCond():
292
			xb, fxb = next(algo)
293
			task.nextIter()
294
		return xb, fxb
295
296
	def run(self, task):
297
		r"""Start the optimization.
298
299
		Args:
300
			task (Task): Optimization task.
301
302
		Returns:
303
			Tuple[numpy.ndarray, float]:
304
				1. Best individuals components found in optimization process.
305
				2. Best fitness value found in optimization process.
306
307
		See Also:
308
			* :func:`NiaPy.algorithms.Algorithm.runTask`
309
		"""
310
		try:
311
			# task.start()
312
			r = self.runTask(task)
313
			return r[0], r[1] * task.optType.value
314
		except (FesException, GenException, TimeException, RefException): return task.x, task.x_f * task.optType.value
315
316
class Individual:
317
	r"""Class that represents one solution in population of solutions.
318
319
	Date:
320
		2018
321
322
	Author:
323
		Klemen Berkovič
324
325
	License:
326
		MIT
327
328
	Attributes:
329
		x (numpy.ndarray): Coordinates of individual.
330
		f (float): Function/fitness value of individual.
331
	"""
332
	x = None
333
	f = inf
334
335
	def __init__(self, x=None, task=None, e=True, rnd=rand, **kwargs):
336
		r"""Initialize new individual.
337
338
		Parameters:
339
			task (Optional[Task]): Optimization task.
340
			rand (Optional[mtrand.RandomState]): Random generator.
341
			x (Optional[numpy.ndarray]): Individuals components.
342
			e (Optional[bool]): True to evaluate the individual on initialization. Default value is True.
343
			**kwargs (Dict[str, Any]): Additional arguments.
344
		"""
345
		self.f = task.optType.value * inf if task is not None else inf
346
		if x is not None: self.x = x if isinstance(x, ndarray) else asarray(x)
347
		else: self.generateSolution(task, rnd)
348
		if e and task is not None: self.evaluate(task, rnd)
349
350
	def generateSolution(self, task, rnd=rand):
351
		r"""Generate new solution.
352
353
		Generate new solution for this individual and set it to ``self.x``.
354
		This method uses ``rnd`` for getting random numbers.
355
		For generating random components ``rnd`` and ``task`` is used.
356
357
		Args:
358
			task (Task): Optimization task.
359
			rnd (Optional[mtrand.RandomState]): Random numbers generator object.
360
		"""
361
		if task is not None: self.x = task.Lower + task.bRange * rnd.rand(task.D)
362
363
	def evaluate(self, task, rnd=rand):
364
		r"""Evaluate the solution.
365
366
		Evaluate solution ``this.x`` with the help of task.
367
		Task is used for reparing the solution and then evaluating it.
368
369
		Args:
370
			task (Task): Objective function object.
371
			rnd (Optional[mtrand.RandomState]): Random generator.
372
373
		See Also:
374
			* :func:`NiaPy.util.Task.repair`
375
		"""
376
		self.x = task.repair(self.x, rnd=rnd)
377
		self.f = task.eval(self.x)
378
379
	def copy(self):
380
		r"""Return a copy of self.
381
382
		Method returns copy of ``this`` object so it is safe for editing.
383
384
		Returns:
385
			Individual: Copy of self.
386
		"""
387
		return Individual(x=self.x.copy(), f=self.f, e=False)
388
389
	def __eq__(self, other):
390
		r"""Compare the individuals for equalities.
391
392
		Args:
393
			other (Union[Any, numpy.ndarray]): Object that we want to compare this object to.
394
395
		Returns:
396
			bool: `True` if equal or `False` if no equal.
397
		"""
398
		if isinstance(other, ndarray):
399
			for e in other:
400
				if self == e: return True
401
			return False
402
		return array_equal(self.x, other.x) and self.f == other.f
403
404
	def __str__(self):
405
		r"""Print the individual with the solution and objective value.
406
407
		Returns:
408
			str: String representation of self.
409
		"""
410
		return '%s -> %s' % (self.x, self.f)
411
412
	def __getitem__(self, i):
413
		r"""Get the value of i-th component of the solution.
414
415
		Args:
416
			i (int): Position of the solution component.
417
418
		Returns:
419
			Any: Value of ith component.
420
		"""
421
		return self.x[i]
422
423
	def __setitem__(self, i, v):
424
		r"""Set the value of i-th component of the solution to v value.
425
426
		Args:
427
			i (int): Position of the solution component.
428
			v (Any): Value to set to i-th component.
429
		"""
430
		self.x[i] = v
431
432
	def __len__(self):
433
		r"""Get the length of the solution or the number of components.
434
435
		Returns:
436
			int: Number of components.
437
		"""
438
		return len(self.x)
439
440
# vim: tabstop=3 noexpandtab shiftwidth=3 softtabstop=3
441