1
"""Module containing tools that are useful when benchmarking algorithms
3
from math import hypot, sqrt
4
from functools import wraps
5
from itertools import repeat
11
class translate(object):
12
"""Decorator for evaluation functions, it translates the objective
13
function by *vector* which should be the same length as the individual
14
size. When called the decorated function should take as first argument the
15
individual to be evaluated. The inverse translation vector is actually
16
applied to the individual and the resulting list is given to the
17
evaluation function. Thus, the evaluation function shall not be expecting
18
an individual as it will receive a plain list.
20
This decorator adds a :func:`translate` method to the decorated function.
22
def __init__(self, vector):
25
def __call__(self, func):
26
# wraps is used to combine stacked decorators that would add functions
28
def wrapper(individual, *args, **kargs):
29
# A subtraction is applied since the translation is applied to the
30
# individual and not the function
31
return func([v - t for v, t in zip(individual, self.vector)],
33
wrapper.translate = self.translate
36
def translate(self, vector):
37
"""Set the current translation to *vector*. After decorating the
38
evaluation function, this function will be available directly from
39
the function object. ::
41
@translate([0.25, 0.5, ..., 0.1])
42
def evaluate(individual):
43
return sum(individual),
45
# This will cancel the translation
46
evaluate.translate([0.0, 0.0, ..., 0.0])
51
"""Decorator for evaluation functions, it rotates the objective function
52
by *matrix* which should be a valid orthogonal NxN rotation matrix, with N
53
the length of an individual. When called the decorated function should
54
take as first argument the individual to be evaluated. The inverse
55
rotation matrix is actually applied to the individual and the resulting
56
list is given to the evaluation function. Thus, the evaluation function
57
shall not be expecting an individual as it will receive a plain list
58
(numpy.array). The multiplication is done using numpy.
60
This decorator adds a :func:`rotate` method to the decorated function.
64
A random orthogonal matrix Q can be created via QR decomposition. ::
66
A = numpy.random.random((n,n))
67
Q, _ = numpy.linalg.qr(A)
69
def __init__(self, matrix):
71
raise RuntimeError("Numpy is required for using the rotation "
73
# The inverse is taken since the rotation is applied to the individual
74
# and not the function which is the inverse
75
self.matrix = numpy.linalg.inv(matrix)
77
def __call__(self, func):
78
# wraps is used to combine stacked decorators that would add functions
80
def wrapper(individual, *args, **kargs):
81
return func(numpy.dot(self.matrix, individual), *args, **kargs)
82
wrapper.rotate = self.rotate
85
def rotate(self, matrix):
86
"""Set the current rotation to *matrix*. After decorating the
87
evaluation function, this function will be available directly from
88
the function object. ::
90
# Create a random orthogonal matrix
91
A = numpy.random.random((n,n))
92
Q, _ = numpy.linalg.qr(A)
95
def evaluate(individual):
96
return sum(individual),
98
# This will reset rotation to identity
99
evaluate.rotate(numpy.identity(n))
101
self.matrix = numpy.linalg.inv(matrix)
104
"""Decorator for evaluation functions, it evaluates the objective function
105
and adds noise by calling the function(s) provided in the *noise*
106
argument. The noise functions are called without any argument, consider
107
using the :class:`~deap.base.Toolbox` or Python's
108
:func:`functools.partial` to provide any required argument. If a single
109
function is provided it is applied to all objectives of the evaluation
110
function. If a list of noise functions is provided, it must be of length
111
equal to the number of objectives. The noise argument also accept
112
:obj:`None`, which will leave the objective without noise.
114
This decorator adds a :func:`noise` method to the decorated
117
def __init__(self, noise):
119
self.rand_funcs = tuple(noise)
121
self.rand_funcs = repeat(noise)
123
def __call__(self, func):
124
# wraps is used to combine stacked decorators that would add functions
126
def wrapper(individual, *args, **kargs):
127
result = func(individual, *args, **kargs)
129
for r, f in zip(result, self.rand_funcs):
133
noisy.append(r + f())
135
wrapper.noise = self.noise
138
def noise(self, noise):
139
"""Set the current noise to *noise*. After decorating the
140
evaluation function, this function will be available directly from
141
the function object. ::
143
prand = functools.partial(random.gauss, mu=0.0, sigma=1.0)
146
def evaluate(individual):
147
return sum(individual),
149
# This will remove noise from the evaluation function
153
self.rand_funcs = tuple(noise)
155
self.rand_funcs = repeat(noise)
158
"""Decorator for evaluation functions, it scales the objective function by
159
*factor* which should be the same length as the individual size. When
160
called the decorated function should take as first argument the individual
161
to be evaluated. The inverse factor vector is actually applied to the
162
individual and the resulting list is given to the evaluation function.
163
Thus, the evaluation function shall not be expecting an individual as it
164
will receive a plain list.
166
This decorator adds a :func:`scale` method to the decorated function.
168
def __init__(self, factor):
169
# Factor is inverted since it is aplied to the individual and not the
171
self.factor = tuple(1.0/f for f in factor)
173
def __call__(self, func):
174
# wraps is used to combine stacked decorators that would add functions
176
def wrapper(individual, *args, **kargs):
177
return func([v * f for v, f in zip(individual, self.factor)],
179
wrapper.scale = self.scale
182
def scale(self, factor):
183
"""Set the current scale to *factor*. After decorating the
184
evaluation function, this function will be available directly from
185
the function object. ::
187
@scale([0.25, 2.0, ..., 0.1])
188
def evaluate(individual):
189
return sum(individual),
191
# This will cancel the scaling
192
evaluate.scale([1.0, 1.0, ..., 1.0])
194
# Factor is inverted since it is aplied to the individual and not the
196
self.factor = tuple(1.0/f for f in factor)
199
"""Decorator for crossover and mutation functions, it changes the
200
individuals after the modification is done to bring it back in the allowed
201
*bounds*. The *bounds* are functions taking individual and returning
202
wheter of not the variable is allowed. You can provide one or multiple such
203
functions. In the former case, the function is used on all dimensions and
204
in the latter case, the number of functions must be greater or equal to
205
the number of dimension of the individuals.
207
The *type* determines how the attributes are brought back into the valid
210
This decorator adds a :func:`bound` method to the decorated function.
212
def _clip(self, individual):
215
def _wrap(self, individual):
218
def _mirror(self, individual):
221
def __call__(self, func):
223
def wrapper(*args, **kargs):
224
individuals = func(*args, **kargs)
225
return self.bound(individuals)
226
wrapper.bound = self.bound
229
def __init__(self, bounds, type):
231
self.bounds = tuple(bounds)
233
self.bounds = itertools.repeat(bounds)
236
self.bound = self._mirror
238
self.bound = self._wrap
240
self.bound = self._clip
242
def diversity(first_front, first, last):
243
"""Given a Pareto front `first_front` and the two extreme points of the
244
optimal Pareto front, this function returns a metric of the diversity
245
of the front as explained in the original NSGA-II article by K. Deb.
246
The smaller the value is, the better the front is.
248
df = hypot(first_front[0].fitness.values[0] - first[0],
249
first_front[0].fitness.values[1] - first[1])
250
dl = hypot(first_front[-1].fitness.values[0] - last[0],
251
first_front[-1].fitness.values[1] - last[1])
252
dt = [hypot(first.fitness.values[0] - second.fitness.values[0],
253
first.fitness.values[1] - second.fitness.values[1])
254
for first, second in zip(first_front[:-1], first_front[1:])]
256
if len(first_front) == 1:
260
di = sum(abs(d_i - dm) for d_i in dt)
261
delta = (df + dl + di)/(df + dl + len(dt) * dm )
264
def convergence(first_front, optimal_front):
265
"""Given a Pareto front `first_front` and the optimal Pareto front,
266
this function returns a metric of convergence
267
of the front as explained in the original NSGA-II article by K. Deb.
268
The smaller the value is, the closer the front is to the optimal one.
272
for ind in first_front:
273
distances.append(float("inf"))
274
for opt_ind in optimal_front:
276
for i in xrange(len(opt_ind)):
277
dist += (ind.fitness.values[i] - opt_ind[i])**2
278
if dist < distances[-1]:
280
distances[-1] = sqrt(distances[-1])
282
return sum(distances) / len(distances)
b'\\ No newline at end of file'