~ubuntu-branches/ubuntu/utopic/deap/utopic-proposed

« back to all changes in this revision

Viewing changes to deap/benchmarks/tools.py

  • Committer: Package Import Robot
  • Author(s): Daniel Stender, Miriam Ruiz, Daniel Stender, Jakub Wilk
  • Date: 2014-07-06 00:03:41 UTC
  • mfrom: (1.1.2)
  • Revision ID: package-import@ubuntu.com-20140706000341-s7gij1ki3d8xz6t9
Tags: 1.0.1-1
[ Miriam Ruiz ]
* New Upstream Release. Closes: #675200
* Upgraded Standards-Version from 3.9.2 to 3.9.5
* Switched to dh_python2
* Upgraded debian/compat to 9
* Added build-arch and build-indep targets to debian/rules
* Using awk to remove connection from the documentation to google analytics
* Using jquery.js and underscore.js from libjs-jquery and libjs-underscore
* Added patches/doc.patch

[ Daniel Stender ]
* deb/control:
  + Added myself to Uploaders.
  + Added version to b-p on python-all.
  + Updated Homepage.
  + Wrapped and sorted.
* deb/copyright:
  + Changed copyright file towards DEP-5.
  + Updated Source URI (project source moved to Github).
  + Added email addresses for copyright holders.
  + Dropped license for eap/toolbox.py (not included anymore).
  + Extended copyrights to 2014.
  + Added myself to copyrights for deb/*.
  + Specified license location.
* Added watch file [initial version by Jackson Doak].

[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Module containing tools that are useful when benchmarking algorithms
 
2
"""
 
3
from math import hypot, sqrt
 
4
from functools import wraps
 
5
from itertools import repeat
 
6
try:
 
7
    import numpy
 
8
except ImportError:
 
9
    numpy = False
 
10
 
 
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.
 
19
    
 
20
    This decorator adds a :func:`translate` method to the decorated function.
 
21
    """
 
22
    def __init__(self, vector):
 
23
        self.vector = vector
 
24
    
 
25
    def __call__(self, func):
 
26
        # wraps is used to combine stacked decorators that would add functions
 
27
        @wraps(func)
 
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)], 
 
32
                *args, **kargs)
 
33
        wrapper.translate = self.translate
 
34
        return wrapper
 
35
    
 
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. ::
 
40
            
 
41
            @translate([0.25, 0.5, ..., 0.1])
 
42
            def evaluate(individual):
 
43
                return sum(individual),
 
44
            
 
45
            # This will cancel the translation
 
46
            evaluate.translate([0.0, 0.0, ..., 0.0])
 
47
        """
 
48
        self.vector = vector
 
49
 
 
50
class rotate(object):
 
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.
 
59
    
 
60
    This decorator adds a :func:`rotate` method to the decorated function.
 
61
    
 
62
    .. note::
 
63
    
 
64
       A random orthogonal matrix Q can be created via QR decomposition. ::
 
65
           
 
66
           A = numpy.random.random((n,n))
 
67
           Q, _ = numpy.linalg.qr(A)
 
68
    """
 
69
    def __init__(self, matrix):
 
70
        if not numpy:
 
71
            raise RuntimeError("Numpy is required for using the rotation "
 
72
                "decorator")
 
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)
 
76
 
 
77
    def __call__(self, func):
 
78
        # wraps is used to combine stacked decorators that would add functions
 
79
        @wraps(func)
 
80
        def wrapper(individual, *args, **kargs):
 
81
            return func(numpy.dot(self.matrix, individual), *args, **kargs)
 
82
        wrapper.rotate = self.rotate
 
83
        return wrapper
 
84
 
 
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. ::
 
89
            
 
90
            # Create a random orthogonal matrix
 
91
            A = numpy.random.random((n,n))
 
92
            Q, _ = numpy.linalg.qr(A)
 
93
            
 
94
            @rotate(Q)
 
95
            def evaluate(individual):
 
96
                return sum(individual),
 
97
            
 
98
            # This will reset rotation to identity
 
99
            evaluate.rotate(numpy.identity(n))
 
100
        """
 
101
        self.matrix = numpy.linalg.inv(matrix)
 
102
 
 
103
class noise(object):
 
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.
 
113
 
 
114
    This decorator adds a :func:`noise` method to the decorated
 
115
    function.
 
116
    """
 
117
    def __init__(self, noise):
 
118
        try:
 
119
            self.rand_funcs = tuple(noise)
 
120
        except TypeError:
 
121
            self.rand_funcs = repeat(noise)
 
122
 
 
123
    def __call__(self, func):
 
124
        # wraps is used to combine stacked decorators that would add functions
 
125
        @wraps(func)
 
126
        def wrapper(individual, *args, **kargs):
 
127
            result = func(individual, *args, **kargs)
 
128
            noisy = list()
 
129
            for r, f in zip(result, self.rand_funcs):
 
130
                if f is None:
 
131
                    noisy.append(r)
 
132
                else:
 
133
                    noisy.append(r + f())
 
134
            return tuple(noisy)
 
135
        wrapper.noise = self.noise
 
136
        return wrapper
 
137
    
 
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. ::
 
142
        
 
143
            prand = functools.partial(random.gauss, mu=0.0, sigma=1.0)
 
144
        
 
145
            @noise(prand)
 
146
            def evaluate(individual):
 
147
                return sum(individual),
 
148
        
 
149
            # This will remove noise from the evaluation function
 
150
            evaluate.noise(None)
 
151
        """
 
152
        try:
 
153
            self.rand_funcs = tuple(noise)
 
154
        except TypeError:
 
155
            self.rand_funcs = repeat(noise)
 
156
            
 
157
class scale(object):
 
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.
 
165
    
 
166
    This decorator adds a :func:`scale` method to the decorated function.
 
167
    """
 
168
    def __init__(self, factor):
 
169
        # Factor is inverted since it is aplied to the individual and not the
 
170
        # objective function
 
171
        self.factor = tuple(1.0/f for f in factor)
 
172
 
 
173
    def __call__(self, func):
 
174
        # wraps is used to combine stacked decorators that would add functions
 
175
        @wraps(func)
 
176
        def wrapper(individual, *args, **kargs):
 
177
            return func([v * f for v, f in zip(individual, self.factor)], 
 
178
                *args, **kargs)
 
179
        wrapper.scale = self.scale
 
180
        return wrapper
 
181
 
 
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. ::
 
186
            
 
187
            @scale([0.25, 2.0, ..., 0.1])
 
188
            def evaluate(individual):
 
189
                return sum(individual),
 
190
            
 
191
            # This will cancel the scaling
 
192
            evaluate.scale([1.0, 1.0, ..., 1.0])
 
193
        """
 
194
        # Factor is inverted since it is aplied to the individual and not the
 
195
        # objective function
 
196
        self.factor = tuple(1.0/f for f in factor)
 
197
 
 
198
class bound(object):
 
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.
 
206
 
 
207
    The *type* determines how the attributes are brought back into the valid
 
208
    range
 
209
    
 
210
    This decorator adds a :func:`bound` method to the decorated function.
 
211
    """
 
212
    def _clip(self, individual):
 
213
        return individual
 
214
 
 
215
    def _wrap(self, individual):
 
216
        return individual
 
217
 
 
218
    def _mirror(self, individual):
 
219
        return individual
 
220
 
 
221
    def __call__(self, func):
 
222
        @wraps(func)
 
223
        def wrapper(*args, **kargs):
 
224
            individuals = func(*args, **kargs)
 
225
            return self.bound(individuals)
 
226
        wrapper.bound = self.bound
 
227
        return wrapper
 
228
 
 
229
    def __init__(self, bounds, type):
 
230
        try:
 
231
            self.bounds = tuple(bounds)
 
232
        except TypeError:
 
233
            self.bounds = itertools.repeat(bounds)
 
234
 
 
235
        if type == "mirror":
 
236
            self.bound = self._mirror
 
237
        elif type == "wrap":
 
238
            self.bound = self._wrap
 
239
        elif type == "clip":
 
240
            self.bound = self._clip
 
241
        
 
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.
 
247
    """
 
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:])]
 
255
 
 
256
    if len(first_front) == 1:
 
257
        return df + dl
 
258
 
 
259
    dm = sum(dt)/len(dt)
 
260
    di = sum(abs(d_i - dm) for d_i in dt)
 
261
    delta = (df + dl + di)/(df + dl + len(dt) * dm )
 
262
    return delta
 
263
 
 
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.
 
269
    """
 
270
    distances = []
 
271
    
 
272
    for ind in first_front:
 
273
        distances.append(float("inf"))
 
274
        for opt_ind in optimal_front:
 
275
            dist = 0.
 
276
            for i in xrange(len(opt_ind)):
 
277
                dist += (ind.fitness.values[i] - opt_ind[i])**2
 
278
            if dist < distances[-1]:
 
279
                distances[-1] = dist
 
280
        distances[-1] = sqrt(distances[-1])
 
281
        
 
282
    return sum(distances) / len(distances)
 
 
b'\\ No newline at end of file'