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

EvolutionStrategy1p1.initPopulation()   A

Complexity

Conditions 1

Size

Total Lines 15
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 2
dl 0
loc 15
rs 10
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, unused-argument, singleton-comparison, no-else-return, line-too-long, arguments-differ, no-self-use, superfluous-parens, redefined-builtin, bad-continuation, unused-variable, assignment-from-no-return
3
import logging
4
from math import ceil
5
6
from numpy import argmin, argsort, log, sum, fmax, sqrt, full, exp, eye, diag, apply_along_axis, round, any, asarray, dot, random as rand, tile, inf, where, append
7
from numpy.linalg import norm, cholesky as chol, eig, solve, lstsq
8
9
from NiaPy.algorithms.algorithm import Algorithm, Individual, defaultIndividualInit
10
from NiaPy.util.utility import objects2array
11
12
logging.basicConfig()
13
logger = logging.getLogger('NiaPy.algorithms.basic')
14
logger.setLevel('INFO')
15
16
__all__ = ['EvolutionStrategy1p1', 'EvolutionStrategyMp1', 'EvolutionStrategyMpL', 'EvolutionStrategyML', 'CovarianceMatrixAdaptionEvolutionStrategy']
17
18
class IndividualES(Individual):
19
	r"""Individual for Evolution Strategies.
20
21
	See Also:
22
		* :class:`NiaPy.algorithms.Individual`
23
	"""
24
	def __init__(self, **kwargs):
25
		r"""Initialize individual.
26
27
		Args:
28
			kwargs (Dict[str, Any]): Additional arguments.
29
30
		See Also:
31
			* :func:`NiaPy.algorithms.Individual.__init__`
32
		"""
33
		Individual.__init__(self, **kwargs)
34
		task, x, rho = kwargs.get('task', None), kwargs.get('x', None), kwargs.get('rho', 1)
35
		if rho != None: self.rho = rho
36
		elif task != None or x != None: self.rho = 1.0
37
38
class EvolutionStrategy1p1(Algorithm):
39
	r"""Implementation of (1 + 1) evolution strategy algorithm. Uses just one individual.
40
41
	Algorithm:
42
		(1 + 1) Evolution Strategy Algorithm
43
44
	Date:
45
		2018
46
47
	Authors:
48
		Klemen Berkovič
49
50
	License:
51
		MIT
52
53
	Reference URL:
54
55
	Reference paper:
56
57
	Attributes:
58
		Name (List[str]): List of strings representing algorithm names.
59
		mu (int): Number of parents.
60
		k (int): Number of iterations before checking and fixing rho.
61
		c_a (float): Search range amplification factor.
62
		c_r (float): Search range reduction factor.
63
64
	See Also:
65
		* :class:`NiaPy.algorithms.Algorithm`
66
	"""
67
	Name = ['EvolutionStrategy1p1', 'EvolutionStrategy(1+1)', 'ES(1+1)']
68
69
	@staticmethod
70
	def typeParameters():
71
		r"""Get dictionary with functions for checking values of parameters.
72
73
		Returns:
74
			Dict[str, Callable]:
75
				* mu (Callable[[int], bool]): TODO.
76
				* k (Callable[[int], bool]): TODO.
77
				* c_a (Callable[[Union[float, int]], bool]): TODO.
78
				* c_r (Callable[[Union[float, int]], bool]): TODO.
79
				* epsilon (Callable[[float], bool]): TODO.
80
		"""
81
		return {
82
			'mu': lambda x: isinstance(x, int) and x > 0,
83
			'k': lambda x: isinstance(x, int) and x > 0,
84
			'c_a': lambda x: isinstance(x, (float, int)) and x > 1,
85
			'c_r': lambda x: isinstance(x, (float, int)) and 0 < x < 1,
86
			'epsilon': lambda x: isinstance(x, float) and 0 < x < 1
87
		}
88
89
	def setParameters(self, mu=1, k=10, c_a=1.1, c_r=0.5, epsilon=1e-20, **ukwargs):
90
		r"""Set the arguments of an algorithm.
91
92
		Arguments:
93
			mu (Optional[int]): Number of parents
94
			k (Optional[int]): Number of iterations before checking and fixing rho
95
			c_a (Optional[float]): Search range amplification factor
96
			c_r (Optional[float]): Search range reduction factor
97
98
		See Also:
99
			* :func:`NiaPy.algorithms.Algorithm.setParameters`
100
		"""
101
		Algorithm.setParameters(self, NP=mu, itype=IndividualES, **ukwargs)
102
		self.mu, self.k, self.c_a, self.c_r, self.epsilon = mu, k, c_a, c_r, epsilon
103
		if ukwargs: logger.info('Unused arguments: %s' % (ukwargs))
104
105
	def mutate(self, x, rho):
106
		r"""Mutate individual.
107
108
		Args:
109
			x (Individual): Current individual.
110
			rho (float): Current standard deviation.
111
112
		Returns:
113
			Individual: Mutated individual.
114
		"""
115
		return x + self.normal(0, rho, len(x))
116
117
	def updateRho(self, rho, k):
118
		r"""Update standard deviation.
119
120
		Args:
121
			rho (float): Current standard deviation.
122
			k (int): Number of succesfull mutations.
123
124
		Returns:
125
			float: New standard deviation.
126
		"""
127
		phi = k / self.k
128
		if phi < 0.2: return self.c_r * rho if rho > self.epsilon else 1
129
		elif phi > 0.2: return self.c_a * rho if rho > self.epsilon else 1
130
		else: return rho
131
132
	def initPopulation(self, task):
133
		r"""Initialize starting individual.
134
135
		Args:
136
			task (Task): Optimization task.
137
138
		Returns:
139
			Tuple[Individual, float, Dict[str, Any]]:
140
				1, Initialized individual.
141
				2, Initialized individual fitness/function value.
142
				3. Additional arguments:
143
					* ki (int): Number of successful rho update.
144
		"""
145
		c, ki = IndividualES(task=task, rnd=self.Rand), 0
146
		return c, c.f, {'ki': ki}
147
148
	def runIteration(self, task, c, fpop, xb, fxb, ki, **dparams):
149
		r"""Core function of EvolutionStrategy(1+1) algorithm.
150
151
		Args:
152
			task (Task): Optimization task.
153
			pop (Individual): Current position.
154
			fpop (float): Current position function/fitness value.
155
			xb (Individual): Global best position.
156
			fxb (float): Global best function/fitness value.
157
			ki (int): Number of successful updates before rho update.
158
			**dparams (Dict[str, Any]): Additional arguments.
159
160
		Returns:
161
			Tuple[Individual, float, Dict[str, Any]]:
162
				1, Initialized individual.
163
				2, Initialized individual fitness/function value.
164
				3. Additional arguments:
165
					* ki (int): Number of successful rho update.
166
		"""
167
		if task.Iters % self.k == 0: c.rho, ki = self.updateRho(c.rho, ki), 0
168
		cn = [task.repair(self.mutate(c.x, c.rho), self.Rand) for _i in range(self.mu)]
169
		cn_f = [task.eval(cn[i]) for i in range(len(cn))]
170
		ib = argmin(cn_f)
171
		if cn_f[ib] < c.f: c.x, c.f, ki = cn[ib], cn_f[ib], ki + 1
172
		return c, c.f, {'ki': ki}
173
174
class EvolutionStrategyMp1(EvolutionStrategy1p1):
175
	r"""Implementation of (mu + 1) evolution strategy algorithm. Algorithm creates mu mutants but into new generation goes only one individual.
176
177
	Algorithm:
178
		(:math:`\mu + 1`) Evolution Strategy Algorithm
179
180
	Date:
181
		2018
182
183
	Authors:
184
		Klemen Berkovič
185
186
	License:
187
		MIT
188
189
	Reference URL:
190
191
	Reference paper:
192
193
	Attributes:
194
		Name (List[str]): List of strings representing algorithm names.
195
196
	See Also:
197
		* :class:`NiaPy.algorithms.basic.EvolutionStrategy1p1`
198
	"""
199
	Name = ['EvolutionStrategyMp1', 'EvolutionStrategy(mu+1)', 'ES(m+1)']
200
201
	def setParameters(self, **kwargs):
202
		r"""Set core parameters of EvolutionStrategy(mu+1) algorithm.
203
204
		Args:
205
			**kwargs (Dict[str, Any]):
206
207
      See Also:
208
      	* :func:`NiaPy.algorithms.basic.EvolutionStrategy1p1.setParameters`
209
		"""
210
		mu = kwargs.pop('mu', 40)
211
		EvolutionStrategy1p1.setParameters(self, mu=mu, **kwargs)
212
213
class EvolutionStrategyMpL(EvolutionStrategy1p1):
214
	r"""Implementation of (mu + lambda) evolution strategy algorithm. Mulation creates lambda individual. Lambda individual compete with mu individuals for survival, so only mu individual go to new generation.
215
216
	Algorithm:
217
		(:math:`\mu + \lambda`) Evolution Strategy Algorithm
218
219
	Date:
220
		2018
221
222
	Authors:
223
		Klemen Berkovič
224
225
	License:
226
		MIT
227
228
	Reference URL:
229
230
	Reference paper:
231
232
	Attributes:
233
		Name (List[str]): List of strings representing algorithm names
234
		lam (int): TODO
235
236
	See Also:
237
		* :class:`NiaPy.algorithms.basic.EvolutionStrategy1p1`
238
	"""
239
	Name = ['EvolutionStrategyMpL', 'EvolutionStrategy(mu+lambda)', 'ES(m+l)']
240
241
	@staticmethod
242
	def typeParameters():
243
		r"""TODO.
244
245
		Returns:
246
			Dict[str, Any]:
247
				* lam (Callable[[int], bool]): TODO.
248
249
		See Also:
250
			* :func:`NiaPy.algorithms.basic.EvolutionStrategy1p1`
251
		"""
252
		d = EvolutionStrategy1p1.typeParameters()
253
		d['lam'] = lambda x: isinstance(x, int) and x > 0
254
		return d
255
256
	def setParameters(self, lam=45, **ukwargs):
257
		r"""Set the arguments of an algorithm.
258
259
		Arguments:
260
			lam (int): Number of new individual generated by mutation.
261
262
		See Also:
263
			* :func:`NiaPy.algorithms.basic.es.EvolutionStrategy1p1.setParameters`
264
		"""
265
		EvolutionStrategy1p1.setParameters(self, InitPopFunc=defaultIndividualInit, **ukwargs)
266
		self.lam = lam
267
		if ukwargs: logger.info('Unused arguments: %s' % (ukwargs))
268
269
	def updateRho(self, pop, k):
270
		r"""Update standard deviation for population.
271
272
		Args:
273
			pop (numpy.ndarray[Individual]): Current population.
274
			k (int): Number of successful mutations.
275
		"""
276
		phi = k / self.k
277
		if phi < 0.2:
278
			for i in pop: i.rho = self.c_r * i.rho
279
		elif phi > 0.2:
280
			for i in pop: i.rho = self.c_a * i.rho
281
282
	def changeCount(self, c, cn):
283
		r"""Update number of successful mutations for population.
284
285
		Args:
286
			c (numpy.ndarray[Individual]): Current population.
287
			cn (numpy.ndarray[Individual]): New population.
288
289
		Returns:
290
			int: Number of successful mutations.
291
		"""
292
		k = 0
293
		for e in cn:
294
			if e not in c: k += 1
295
		return k
296
297
	def mutateRand(self, pop, task):
298
		r"""Mutate random individual form population.
299
300
		Args:
301
			pop (numpy.ndarray[Individual]): Current population.
302
			task (Task): Optimization task.
303
304
		Returns:
305
			numpy.ndarray: Random individual from population that was mutated.
306
		"""
307
		i = self.randint(self.mu)
308
		return task.repair(self.mutate(pop[i].x, pop[i].rho), rnd=self.Rand)
309
310
	def initPopulation(self, task):
311
		r"""Initialize starting population.
312
313
		Args:
314
			task (Task): Optimization task.
315
316
		Returns:
317
			Tuple[numpy.ndarray[Individual], numpy.ndarray[float], Dict[str, Any]]:
318
				1. Initialized populaiton.
319
				2. Initialized populations function/fitness values.
320
				3. Additional arguments:
321
					* ki (int): Number of successful mutations.
322
323
		See Also:
324
			* :func:`NiaPy.algorithms.algorithm.Algorithm.initPopulation`
325
		"""
326
		c, fc, d = Algorithm.initPopulation(self, task)
327
		d.update({'ki': 0})
328
		return c, fc, d
329
330
	def runIteration(self, task, c, fpop, xb, fxb, ki, **dparams):
331
		r"""Core function of EvolutionStrategyMpL algorithm.
332
333
		Args:
334
			task (Task): Optimization task.
335
			c (numpy.ndarray[Individual]): Current population.
336
			fpop (numpy.ndarray[float]): Current populations fitness/function values.
337
			xb (Individual): Global best individual.
338
			fxb (float): Global best individuals fitness/function value.
339
			ki (int): Number of successful mutations.
340
			**dparams (Dict[str, Any]): Additional arguments.
341
342
		Returns:
343
			Tuple[numpy.ndarray[Individual], numpy.ndarray[float], Dict[str, Any]]:
344
				1. New population.
345
				2. New populations function/fitness values.
346
				3. Additional arguments:
347
					* ki (int): Number of successful mutations.
348
		"""
349
		if task.Iters % self.k == 0: _, ki = self.updateRho(c, ki), 0
350
		cn = objects2array([IndividualES(x=self.mutateRand(c, task), task=task, rnd=self.Rand) for _ in range(self.lam)])
351
		cn = append(cn, c)
352
		cn = objects2array([cn[i] for i in argsort([i.f for i in cn])[:self.mu]])
353
		ki += self.changeCount(c, cn)
354
		return cn, [x.f for x in cn], {'ki': ki}
355
356
class EvolutionStrategyML(EvolutionStrategyMpL):
357
	r"""Implementation of (mu, lambda) evolution strategy algorithm. Algorithm is good for dynamic environments. Mu individual create lambda chields. Only best mu chields go to new generation. Mu parents are discarded.
358
359
	Algorithm:
360
		(:math:`\mu + \lambda`) Evolution Strategy Algorithm
361
362
	Date:
363
		2018
364
365
	Authors:
366
		Klemen Berkovič
367
368
	License:
369
		MIT
370
371
	Reference URL:
372
373
	Reference paper:
374
375
	Attributes:
376
		Name (List[str]): List of strings representing algorithm names
377
378
	See Also:
379
		* :class:`NiaPy.algorithm.basic.es.EvolutionStrategyMpL`
380
	"""
381
	Name = ['EvolutionStrategyML', 'EvolutionStrategy(mu,lambda)', 'ES(m,l)']
382
383
	def newPop(self, pop):
384
		r"""Return new population.
385
386
		Args:
387
			pop (numpy.ndarray): Current population.
388
389
		Returns:
390
			numpy.ndarray: New population.
391
		"""
392
		pop_s = argsort([i.f for i in pop])
393
		if self.mu < self.lam: return objects2array([pop[i] for i in pop_s[:self.mu]])
394
		npop = list()
395
		for i in range(int(ceil(float(self.mu) / self.lam))): npop.extend(pop[:self.lam if (self.mu - i * self.lam) >= self.lam else self.mu - i * self.lam])
396
		return objects2array(npop)
397
398
	def initPopulation(self, task):
399
		r"""Initialize starting population.
400
401
		Args:
402
			task (Task): Optimization task.
403
404
		Returns:
405
			Tuple[numpy.ndarray[Individual], numpy.ndarray[float], Dict[str, Any]]:
406
				1. Initialized population.
407
				2. Initialized populations fitness/function values.
408
				2. Additional arguments.
409
410
		See Also:
411
			* :func:`NiaPy.algorithm.basic.es.EvolutionStrategyMpL.initPopulation`
412
		"""
413
		c, fc, _ = EvolutionStrategyMpL.initPopulation(self, task)
414
		return c, fc, {}
415
416
	def runIteration(self, task, c, fpop, xb, fxb, **dparams):
417
		r"""Core function of EvolutionStrategyML algorithm.
418
419
		Args:
420
			task (Task): Optimization task.
421
			c (numpy.ndarray[Individual]): Current population.
422
			fpop (numpy.ndarray[float]): Current population fitness/function values.
423
			xb (Individual): Global best individual.
424
			fxb (float): Global best individuals fitness/function value.
425
			**dparams Dict[str, Any]: Additional arguments.
426
427
		Returns:
428
			Tuple[numpy.ndarray[Individual], numpy.ndarray[float], Dict[str, Any]]:
429
				1. New population.
430
				2. New populations fitness/function values.
431
				3. Additional arguments.
432
		"""
433
		cn = objects2array([IndividualES(x=self.mutateRand(c, task), task=task, rand=self.Rand) for _ in range(self.lam)])
434
		c = self.newPop(cn)
435
		return c, asarray([x.f for x in c]), {}
436
437
def CovarianceMaatrixAdaptionEvolutionStrategyF(task, epsilon=1e-20, rnd=rand):
438
	lam, alpha_mu, hs, sigma0 = (4 + round(3 * log(task.D))) * 10, 2, 0, 0.3 * task.bcRange()
439
	mu = int(round(lam / 2))
440
	w = log(mu + 0.5) - log(range(1, mu + 1))
441
	w = w / sum(w)
442
	mueff = 1 / sum(w ** 2)
443
	cs = (mueff + 2) / (task.D + mueff + 5)
444
	ds = 1 + cs + 2 * max(sqrt((mueff - 1) / (task.D + 1)) - 1, 0)
445
	ENN = sqrt(task.D) * (1 - 1 / (4 * task.D) + 1 / (21 * task.D ** 2))
446
	cc, c1 = (4 + mueff / task.D) / (4 + task.D + 2 * mueff / task.D), 2 / ((task.D + 1.3) ** 2 + mueff)
447
	cmu, hth = min(1 - c1, alpha_mu * (mueff - 2 + 1 / mueff) / ((task.D + 2) ** 2 + alpha_mu * mueff / 2)), (1.4 + 2 / (task.D + 1)) * ENN
448
	ps, pc, C, sigma, M = full(task.D, 0.0), full(task.D, 0.0), eye(task.D), sigma0, full(task.D, 0.0)
449
	x = rnd.uniform(task.bcLower(), task.bcUpper())
450
	x_f = task.eval(x)
451
	while not task.stopCondI():
452
		pop_step = asarray([rnd.multivariate_normal(full(task.D, 0.0), C) for _ in range(int(lam))])
453
		pop = asarray([task.repair(x + sigma * ps, rnd) for ps in pop_step])
454
		pop_f = apply_along_axis(task.eval, 1, pop)
455
		isort = argsort(pop_f)
456
		pop, pop_f, pop_step = pop[isort[:mu]], pop_f[isort[:mu]], pop_step[isort[:mu]]
457
		if pop_f[0] < x_f: x, x_f = pop[0], pop_f[0]
458
		M = sum(w * pop_step.T, axis=1)
459
		ps = solve(chol(C).conj() + epsilon, ((1 - cs) * ps + sqrt(cs * (2 - cs) * mueff) * M + epsilon).T)[0].T
460
		sigma *= exp(cs / ds * (norm(ps) / ENN - 1)) ** 0.3
461
		ifix = where(sigma == inf)
462
		if any(ifix): sigma[ifix] = sigma0
463
		if norm(ps) / sqrt(1 - (1 - cs) ** (2 * (task.Iters + 1))) < hth: hs = 1
464
		else: hs = 0
465
		delta = (1 - hs) * cc * (2 - cc)
466
		pc = (1 - cc) * pc + hs * sqrt(cc * (2 - cc) * mueff) * M
467
		C = (1 - c1 - cmu) * C + c1 * (tile(pc, [len(pc), 1]) * tile(pc.reshape([len(pc), 1]), [1, len(pc)]) + delta * C)
468
		for i in range(mu): C += cmu * w[i] * tile(pop_step[i], [len(pop_step[i]), 1]) * tile(pop_step[i].reshape([len(pop_step[i]), 1]), [1, len(pop_step[i])])
469
		E, V = eig(C)
470
		if any(E < epsilon):
471
			E = fmax(E, 0)
472
			C = lstsq(V.T, dot(V, diag(E)).T, rcond=None)[0].T
473
	return x, x_f
474
475
class CovarianceMatrixAdaptionEvolutionStrategy(Algorithm):
476
	r"""Implementation of (mu, lambda) evolution strategy algorithm. Algorithm is good for dynamic environments. Mu individual create lambda chields. Only best mu chields go to new generation. Mu parents are discarded.
477
478
	Algorithm:
479
		(:math:`\mu + \lambda`) Evolution Strategy Algorithm
480
481
	Date:
482
		2018
483
484
	Authors:
485
		Klemen Berkovič
486
487
	License:
488
		MIT
489
490
	Reference URL:
491
		https://arxiv.org/abs/1604.00772
492
493
	Reference paper:
494
		Hansen, Nikolaus. "The CMA evolution strategy: A tutorial." arXiv preprint arXiv:1604.00772 (2016).
495
496
	Attributes:
497
		Name (List[str]): List of names representing algorithm names
498
		epsilon (float): TODO
499
	"""
500
	Name = ['CovarianceMatrixAdaptionEvolutionStrategy', 'CMA-ES', 'CMAES']
501
	epsilon = 1e-20
502
503
	@staticmethod
504
	def typeParameters(): return {
505
			'epsilon': lambda x: isinstance(x, (float, int)) and 0 < x < 1
506
	}
507
508
	def setParameters(self, epsilon=1e-20, **ukwargs):
509
		r"""Set core parameters of CovarianceMatrixAdaptionEvolutionStrategy algorithm.
510
511
		Args:
512
			epsilon (float): Small number.
513
			**ukwargs (Dict[str, Any]): Additional arguments.
514
		"""
515
		self.epsilon = epsilon
516
		if ukwargs: logger.info('Unused arguments: %s' % (ukwargs))
517
518
	def runTask(self, task):
519
		r"""TODO.
520
521
		Args:
522
			task (Task): Optimization task.
523
524
		Returns:
525
			TODO.
526
		"""
527
		return CovarianceMaatrixAdaptionEvolutionStrategyF(task, self.epsilon, rnd=self.Rand)
528
529
# vim: tabstop=3 noexpandtab shiftwidth=3 softtabstop=3
530