~ubuntu-branches/ubuntu/saucy/deap/saucy

« back to all changes in this revision

Viewing changes to eap/gp.py

  • Committer: Package Import Robot
  • Author(s): Miriam Ruiz, Jakub Wilk, Miriam Ruiz
  • Date: 2011-11-17 11:53:15 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20111117115315-k9lkwpqcbsq8n0q7
Tags: 0.7.1-1
[ Jakub Wilk ]
* Add Vcs-* fields.

[ Miriam Ruiz ]
* New Upstream Release
* Upgraded Standards-Version from 3.9.1 to 3.9.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#    This file is part of EAP.
2
 
#
3
 
#    EAP is free software: you can redistribute it and/or modify
4
 
#    it under the terms of the GNU Lesser General Public License as
5
 
#    published by the Free Software Foundation, either version 3 of
6
 
#    the License, or (at your option) any later version.
7
 
#
8
 
#    EAP is distributed in the hope that it will be useful,
9
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
 
#    GNU Lesser General Public License for more details.
12
 
#
13
 
#    You should have received a copy of the GNU Lesser General Public
14
 
#    License along with EAP. If not, see <http://www.gnu.org/licenses/>.
15
 
 
16
 
"""The :mod:`gp` module provides the methods and classes to perform
17
 
Genetic Programming with EAP. It essentially contains the classes to
18
 
build a Genetic Program Tree, and the functions to evaluate it.
19
 
 
20
 
This module support both strongly and loosely typed GP.
21
 
"""
22
 
 
23
 
 
24
 
import copy
25
 
import random
26
 
 
27
 
import base
28
 
 
29
 
from itertools import repeat
30
 
from collections import defaultdict
31
 
 
32
 
# Define the name of type for any types.
33
 
__type__ = None
34
 
 
35
 
## GP Tree utility functions
36
 
 
37
 
def evaluate(expr, pset=None):
38
 
    """Evaluate the expression expr into a string if pset is None
39
 
    or into Python code is pset is not None.
40
 
    """
41
 
    def _stringify(expr):
42
 
        try:
43
 
            func = expr[0]
44
 
            return str(func(*[_stringify(value) for value in expr[1:]]))
45
 
        except TypeError:
46
 
            return str(expr)
47
 
    if not pset is None:
48
 
        return eval(_stringify(expr), pset.func_dict)
49
 
    else:
50
 
        return _stringify(expr)
51
 
 
52
 
def evaluateADF(seq):
53
 
    """Evaluate a list of ADF and return a dict mapping the ADF name with its
54
 
    lambda function.
55
 
    """
56
 
    adfdict = {}
57
 
    for i, expr in enumerate(reversed(seq[1:])):
58
 
        func = lambdify(expr.pset, expr)
59
 
        adfdict.update({expr.pset.__name__ : func})
60
 
        for expr2 in reversed(seq[1:i+1]):
61
 
            expr2.pset.func_dict.update(adfdict)
62
 
            
63
 
    return adfdict
64
 
 
65
 
def lambdify(pset, expr):
66
 
    """Return a lambda function of the expression.
67
 
 
68
 
    Remark:
69
 
    This function is a stripped version of the lambdify
70
 
    function of sympy0.6.6.
71
 
    """
72
 
    expr = evaluate(expr)
73
 
    args = ",".join(a for a in pset.arguments)
74
 
    lstr = "lambda %s: %s" % (args, expr)
75
 
    return eval(lstr, dict(pset.func_dict))
76
 
 
77
 
def lambdifyList(expr):
78
 
    """Return a lambda function created from a list of trees. The first 
79
 
    element of the list is the main tree, and the following elements are
80
 
    automatically defined functions (ADF) that can be called by the first
81
 
    tree.
82
 
    """
83
 
    adfdict = evaluateADF(expr)
84
 
    expr[0].pset.func_dict.update(adfdict)   
85
 
    return lambdify(expr[0].pset, expr[0])
86
 
 
87
 
## Loosely + Strongly Typed GP 
88
 
 
89
 
class Primitive(object):
90
 
    """Class that encapsulates a primitive and when called with arguments it
91
 
    returns the Python code to call the primitive with the arguments.
92
 
        
93
 
        >>> import operator
94
 
        >>> pr = Primitive(operator.mul, (int, int), int)
95
 
        >>> pr("1", "2")
96
 
        'mul(1, 2)'
97
 
    """    
98
 
        
99
 
    def __init__(self, primitive, args, ret = __type__):
100
 
        self.name = primitive.__name__
101
 
        self.arity = len(args)           
102
 
        self.args = args
103
 
        self.ret = ret
104
 
        args = ", ".join(repeat("%s", self.arity))
105
 
        self.seq = "%s(%s)" % (self.name, args)  
106
 
    def __call__(self, *args):
107
 
        return self.seq % args  
108
 
    def __repr__(self):
109
 
        return self.name 
110
 
 
111
 
class Operator(Primitive):
112
 
    """Class that encapsulates an operator and when called with arguments it
113
 
    returns the Python code to call the operator with the arguments. It acts
114
 
    as the Primitive class, but instead of returning a function and its 
115
 
    arguments, it returns an operator and its operands.
116
 
        
117
 
        >>> import operator
118
 
        >>> op = Operator(operator.mul, (int, int), int)
119
 
        >>> op("1", "2")
120
 
        '(1 * 2)'
121
 
        >>> op2 = Operator(operator.neg, (int,), int)
122
 
        >>> op2(1)
123
 
        '-(1)'
124
 
    """
125
 
 
126
 
    symbols = {"add" : "+", "sub" : "-", "mul" : "*", "div" : "/", "neg" : "-",
127
 
               "and_" : "and", "or_" : "or", "not_" : "not", 
128
 
               "lt" : "<", "eq" : "==", "gt" : ">", "geq" : ">=", "leq" : "<="}
129
 
    def __init__(self, operator, args, ret = __type__):
130
 
        Primitive.__init__(self, operator, args, ret)
131
 
        if len(args) == 1:
132
 
            self.seq = "%s(%s)" % (self.symbols[self.name], "%s")
133
 
        elif len(args) == 2:
134
 
            self.seq = "(%s %s %s)" % ("%s", self.symbols[self.name], "%s")
135
 
        else:
136
 
            raise ValueError("Operator arity can be either 1 or 2.")
137
 
 
138
 
class Terminal(object):
139
 
    """Class that encapsulates terminal primitive in expression. Terminals can
140
 
    be symbols, values, or 0-arity functions.
141
 
    """
142
 
    def __init__(self, terminal, ret = __type__):
143
 
        self.ret = ret
144
 
        try:
145
 
            self.value = terminal.__name__
146
 
        except AttributeError:
147
 
            self.value = terminal
148
 
    def __call__(self):
149
 
        return self
150
 
    def __repr__(self):
151
 
        return str(self.value)
152
 
 
153
 
class Ephemeral(Terminal):
154
 
    """Class that encapsulates a terminal which value is set at run-time. 
155
 
    The value of the `Ephemeral` can be regenerated with the method `regen`.
156
 
    """
157
 
    def __init__(self, func, ret = __type__):
158
 
        self.func = func
159
 
        Terminal.__init__(self, self.func(), ret)
160
 
    def regen(self):
161
 
        self.value = self.func()
162
 
        
163
 
class EphemeralGenerator(object):
164
 
    """Class that generates `Ephemeral` to be added to an expression."""
165
 
    def __init__(self, ephemeral, ret = __type__):
166
 
        self.ret = ret
167
 
        self.name = ephemeral.__name__
168
 
        self.func = ephemeral
169
 
    def __call__(self):
170
 
        return Ephemeral(self.func, self.ret)
171
 
    def __repr__(self):
172
 
        return self.name
173
 
 
174
 
class PrimitiveSetTyped(object):
175
 
    """Class that contains the primitives that can be used to solve a
176
 
    Strongly Typed GP problem. The set also defined the researched
177
 
    function return type, and input arguments type and number.
178
 
    """
179
 
    def __init__(self, name, in_types, ret_type, prefix = "ARG"):
180
 
        self.terminals = defaultdict(list)
181
 
        self.primitives = defaultdict(list)
182
 
        self.arguments = []
183
 
        self.func_dict = dict()
184
 
        self.terms_count = 0
185
 
        self.prims_count = 0
186
 
        self.adfs_count = 0
187
 
        
188
 
        self.__name__ = name 
189
 
        self.ret = ret_type
190
 
        self.ins = in_types
191
 
        for i, type_ in enumerate(in_types):
192
 
            self.arguments.append(prefix + ("%s" % i))
193
 
            PrimitiveSetTyped.addTerminal(self, self.arguments[-1], type_)
194
 
            
195
 
    def renameArguments(self, new_args):
196
 
        """Rename function arguments with new arguments name *new_args*.
197
 
        """
198
 
        for i, argument in enumerate(self.arguments):
199
 
            if new_args.has_key(argument):
200
 
                self.arguments[i] = new_args[argument]
201
 
        for terminals in self.terminals.values():
202
 
            for terminal in terminals:
203
 
                if ( isinstance(terminal, Terminal) and 
204
 
                     new_args.has_key(terminal.value) ):
205
 
                    terminal.value = new_args[terminal.value]
206
 
 
207
 
    def addPrimitive(self, primitive, in_types, ret_type):
208
 
        """Add a primitive to the set. 
209
 
 
210
 
        *primitive* is a callable object or a function.
211
 
        *in_types* is a list of argument's types the primitive takes.
212
 
        *ret_type* is the type returned by the primitive.
213
 
        """
214
 
        try:
215
 
            prim = Operator(primitive, in_types, ret_type)
216
 
        except (KeyError, ValueError):
217
 
            prim = Primitive(primitive, in_types, ret_type)
218
 
        self.primitives[ret_type].append(prim)
219
 
        self.func_dict[primitive.__name__] = primitive
220
 
        self.prims_count += 1
221
 
        
222
 
    def addTerminal(self, terminal, ret_type):
223
 
        """Add a terminal to the set. 
224
 
 
225
 
        *terminal* is an object, or a function with no arguments.
226
 
        *ret_type* is the type of the terminal.
227
 
        """
228
 
        if callable(terminal):
229
 
            self.func_dict[terminal.__name__] = terminal
230
 
        prim = Terminal(terminal, ret_type)
231
 
        self.terminals[ret_type].append(prim)
232
 
        self.terms_count += 1
233
 
        
234
 
    def addEphemeralConstant(self, ephemeral, ret_type):
235
 
        """Add an ephemeral constant to the set. An ephemeral constant
236
 
        is a no argument function that returns a random value. The value
237
 
        of the constant is constant for a Tree, but may differ from one
238
 
        Tree to another.
239
 
 
240
 
        *ephemeral* function with no arguments that returns a random value.
241
 
        *ret_type* is the type of the object returned by the function.
242
 
        """
243
 
        prim = EphemeralGenerator(ephemeral, ret_type)
244
 
        self.terminals[ret_type].append(prim)
245
 
        self.terms_count += 1
246
 
        
247
 
    def addADF(self, adfset):
248
 
        """Add an Automatically Defined Function (ADF) to the set.
249
 
 
250
 
        *adfset* is a PrimitiveSetTyped containing the primitives with which
251
 
        the ADF can be built.        
252
 
        """
253
 
        prim = Primitive(adfset, adfset.ins, adfset.ret)
254
 
        self.primitives[adfset.ret].append(prim)
255
 
        self.prims_count += 1
256
 
    
257
 
    @property
258
 
    def terminalRatio(self):
259
 
        """Return the ratio of the number of terminals on the number of all
260
 
        kind of primitives.
261
 
        """
262
 
        return self.terms_count / float(self.terms_count + self.prims_count)
263
 
 
264
 
class PrimitiveSet(PrimitiveSetTyped):
265
 
    """Class same as PrimitiveSetTyped, except there is no 
266
 
    definition of type.
267
 
    """
268
 
    def __init__(self, name, arity, prefix="ARG"):
269
 
        args = [__type__]*arity
270
 
        PrimitiveSetTyped.__init__(self, name, args, __type__, prefix)
271
 
 
272
 
    def addPrimitive(self, primitive, arity):
273
 
        """Add primitive *primitive* with arity *arity* to the set."""
274
 
        assert arity > 0, "arity should be >= 1"
275
 
        args = [__type__] * arity 
276
 
        PrimitiveSetTyped.addPrimitive(self, primitive, args, __type__)
277
 
 
278
 
    def addTerminal(self, terminal):
279
 
        """Add a terminal to the set.""" 
280
 
        PrimitiveSetTyped.addTerminal(self, terminal, __type__)
281
 
 
282
 
    def addEphemeralConstant(self, ephemeral):
283
 
        """Add an ephemeral constant to the set."""
284
 
        PrimitiveSetTyped.addEphemeralConstant(self, ephemeral, __type__)
285
 
 
286
 
class PrimitiveTree(base.Tree):
287
 
    """Tree class faster than base Tree, optimized for Primitives."""
288
 
    pset = None   
289
 
    
290
 
    def _getstate(self):
291
 
        state = []
292
 
        for elem in self:
293
 
            try:
294
 
                state.append(elem._getstate())
295
 
            except AttributeError:
296
 
                state.append(elem)
297
 
        return state
298
 
 
299
 
    def __deepcopy__(self, memo):
300
 
        """Deepcopy a Tree by first converting it back to a list of list.
301
 
        
302
 
        This deepcopy is faster than the default implementation. From
303
 
        quick testing, up to 1.6 times faster, and at least 2 times less
304
 
        function calls.
305
 
        """
306
 
        new = self.__class__(self._getstate())
307
 
        new.__dict__.update(copy.deepcopy(self.__dict__, memo))
308
 
        return new
309
 
 
310
 
# Expression generation functions
311
 
 
312
 
def generateRamped(pset, min_, max_, type_=__type__):
313
 
    """Generate an expression with a PrimitiveSet *pset*.
314
 
    Half the time, the expression is generated with generateGrow,
315
 
    the other half, the expression is generated with generateFull.
316
 
    """
317
 
    method = random.choice((generateGrow, generateFull))
318
 
    return method(pset, min_, max_, type_)
319
 
 
320
 
def generateFull(pset, min_, max_, type_=__type__):
321
 
    """Generate an expression where each leaf has a the same depth 
322
 
    between *min* and *max*.
323
 
    """
324
 
    def condition(max_depth):
325
 
        return max_depth == 0
326
 
    return _generate(pset, min_, max_, condition, type_)
327
 
 
328
 
def generateGrow(pset, min_, max_, type_=__type__):
329
 
    """Generate an expression where each leaf might have a different depth 
330
 
    between *min* and *max*.
331
 
    """
332
 
    def condition(max_depth):
333
 
        return max_depth == 0 or random.random() < pset.terminalRatio
334
 
    return _generate(pset, min_, max_, condition, type_)
335
 
 
336
 
def _generate(pset, min_, max_, condition, type_=__type__):
337
 
    def genExpr(max_depth, type_):
338
 
        if condition(max_depth):
339
 
            term = random.choice(pset.terminals[type_])
340
 
            expr = term()
341
 
        else:
342
 
            prim = random.choice(pset.primitives[type_])
343
 
            expr = [prim]
344
 
            args = (genExpr(max_depth-1, arg) for arg in prim.args)
345
 
            expr.extend(args)
346
 
        return expr
347
 
    max_depth = random.randint(min_, max_)
348
 
    expr = genExpr(max_depth, type_)
349
 
    if not isinstance(expr, list):
350
 
        expr = [expr]
351
 
    return expr
352
 
 
353
 
if __name__ == "__main__":
354
 
    import doctest
355
 
    doctest.testmod()
356