File: gene.py

package info (click to toggle)
python-scipy 0.6.0-12
  • links: PTS, VCS
  • area: main
  • in suites: lenny
  • size: 32,016 kB
  • ctags: 46,675
  • sloc: cpp: 124,854; ansic: 110,614; python: 108,664; fortran: 76,260; objc: 424; makefile: 384; sh: 10
file content (406 lines) | stat: -rw-r--r-- 16,307 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
"""
Genes are the most basic building block in this genetic algorithm library.
A gene represents a particular trait of an individual solution.  Mutliple genes are
combined together to form a genome.  The entire genome represents a solution
to the problem being solved.

This module contains the base gene class from which all genes are derived.
It also has two gene classes, list_gene and float_gene, that are commonly used.
If you need to create your own gene class, derive it from the class gene.

history:
12/31/98 documentation added ej
"""

from numpy import array, log10

from tree import tree_node
from ga_util import GAError, nop, shallow_clone
from prng import prng



class gene(object):
    """
    Genes are the most basic building block in this genetic algorithm library.
    A gene represents a particular trait of an individual solution.  The gene class
    contains the current value of a gene object.  It also knows all the possible
    values that the gene can legally have (the allele set).  The gene itself does not
    know how to initialize itself to a value or mutate to another value.  Instead, it is
    closely coupled with initializer and mutator classes that perform these duties.
    By changing the initialization and mutation classes, the behavior of a gene can be
    altered without actually having to create a new gene class.

    This class is never actually instantiated.  Instead it is a base class from which
    specific gene classes are derived.  The duties of the derived class are
    pretty limited because this class defines almost all the necessary methods.  The
    derived class must define the allele set for the gene.  It usually will specify
    an __init__() method, and possibly some other methods specific to the derived
    gene.  It is also necessary to specifiy the initializer and mutator classes
    to operate on the new gene class

    There are several attributes for the class gene:

    mutation_rate -- Every gene has a possibility of mutating each generation.
                     This is a value between 0 and 1 that specifies, in percent,
                     how often the gene mutates.  mutation_rate can either be
                     set for the entire gene class or on a gene by gene basis.
                     Use the set_mutation() function to change the rate
    mr_bounds -- Sometimes it is useful for the mutation rate of the gene to
                 adapt over the course of evolution.  This 2 element tuple specifies
                 the upper and lower bounds that the mutation rate can have.
                 note:  I haven't really messed with this much and think there
                        is probably a better approach
    mutator -- a mutator object (instantiation of a mutator class) that is used
               to mutate the gene
    initializer -- an intializer object used to initialize the gene

    Take a look at float_gene and list_gene to see examples of derived gene classes.
    """
    mr_bounds = (0,.1)
    mutation_rate = .03
    mutator = None
    initializer = None
    is_gene = 1

    def clone(self):
        """ Makes a shallow copy of the object.
        
        Override if you need more specialized behavior.
        """
        return shallow_clone(self)

    def replicate(self, count):
        """ Returns a list with count copies of this object in it.
        """
        return map(lambda x: x.clone(),[self]*count)

    def initialize(self):
        """ Calls the initializer objects evaluate() function to initialize the
        gene.
        """
        self._value = self.initializer.evaluate(self)
        return self.value()

    def set_mutation(self, mrate):
        """
        Set the mutation rate of the gene.

            Arguments:

              mrate -- can be one of the following:

                * a number between 0 and 1 - sets the mutation rate of the gene to a specific value.
                * "gene" - use the mutation rate set in the class definition for this gene.
                * "adapt" - the mutation rate for the gene is chosen randomly from the range mr_bounds
        """
        if(mrate=='gene'):
            try: del self.mutation_rate #remove local mrates and use gene classes mrate
            except AttributeError: pass
        elif(mrate=='adapt'):
            self.mutation_rate = prng.uniform(self.mr_bounds[0], self.mr_bounds[1])
        else:
            self.__class__.mutation_rate = mrate

    def mutate(self):
        """
            Returns 1 if gene mutated, 0 otherwise.

            Calls the **mutator.evaluate()** function to mutate the gene
            mutation_rate of the time. Otherwise, it does nothing.
        """
        #inlined 'flip_coin' for speed
        if prng.random() < self.mutation_rate:
            self._value = self.mutator.evaluate(self)
            return 1
        return 0
    def value(self):
        """Return the current value of the gene. """
        try:
            return self._value
        except AttributeError:
            raise GAError, 'gene not initialized'
    def set_value(self,x):
        """ Set the value of a gene. NO CHECKING!!!
            Don't assign an incompatible value.
        """
        self._value = x
    def __repr__(self):
        try: return `self.value()`
        except GAError: return 'gene not initialized'
    def __add__(self, other):
        try: return self.value() + other.value()
        except AttributeError: return self.value() + other
    __radd__ = __add__
    def __mul__(self, other):
        try: return self.value() * other.value()
        except AttributeError: return self.value() * other
    __rmul__ = __mul__
    def __sub__(self, other):
        try: return self.value() - other.value()
        except AttributeError: return self.value() - other
    def __rsub__(self, other):
        try: return other.value() - self.value()
        except AttributeError: return other - self.value()
    def __div__(self, other):
        try: return self.value() / other.value()
        except: return self.value() / other
    def __rdiv__(self, other):
        try: return other.value() / self.value()
        except AttributeError: return other / self.value()
    def __float__(self): return float(self.value())
    def __complex__(self): return float(self.value())
    def __neg__(self): return -self.value()
    def __cmp__(self, other):
        try:
            if self.__class__ == other.__class__ and self.__dict__ == other.__dict__: return 0
        except AttributeError: pass
        v1 = self.value()
        try: v2 = other.value()
        except AttributeError: v2 = other
        return cmp(v1,v2)

class list_gene_uniform_mutator(object):
    """
    This class randomly chooses a new gene value from the allele set
    in a list_gene.  It is also useful as an initializer for list_gene.
    """
    def evaluate(self,gene):
        """ return a randomly chosen value from the genes allele set """
        return prng.choice(gene.allele_set)

class list_gene_gaussian_mutator(object):
    """
    This class chooses a new gene value from the allele set
    in a list_gene.  The new value is chosen from a gaussian
    distributed distance away from the current values location in the
    allele set list.  The mutated value is never equal to the current
    gene value.  The dev_width is the standard deviation of the gaussian
    distribution as a percentage of the length of the list.

    As an example, suppose a list_gene has the allele_set [0,1,2,3,4,5,6,7,8,9].
    There are 10 entries in this list.  If the dev_width is .1 (the default),
    then there is a 65% chance the new value will either be 1 position away from
    the current value.  If the current value is 4, then the new value will be
    3 or 5 66% of the time, 2 or 6 29% of the time, and so on based on a gaussian
    distribution.

    If the newly chosen index falls outside of the range of the list (for example
    -1), then a new value is chosen until the value falls inside the lists range.
    The index is NOT truncated to the bottom or top index in the range.
    """
    def __init__(self,dev_width = .1):
        """Arguments:
            dev_width -- a value between 0 and 1 that specifies the standard
            deviation as a percentage of the length of the list_gene's
            allele set.
        """
        self.dev_width = dev_width
    def evaluate(self,gene):
        """ return a new value from the genes allele set """
        size = len(gene.allele_set)
        if size == 1: return gene.allele_set[0]
        w = self.dev_width * size
        old = gene.index()
        new = -1; f = -1
        while not (0 <= new < size):
            f = prng.normal(old,w)
            new = round(f)
            if(old == new and f > new): new = new + 1
            if(old == new and f < new): new = new - 1
        return gene.allele_set[int(new)]
class list_gene_walk_mutator(object):
    """
      This class chooses a new gene value from the allele set
      in a list_gene.  The newly chosen value is +/-1 element
    in the allele_set from the current gene value.
    This is like a random walk across the allele_set
    """
    def evaluate(self,gene):
        old = gene.index()
        move = prng.choice((-1,1))
        return gene.allele_set[old + move]

class list_gene(gene):
    """
    The value of a list_gene is chosen from a list of
    possible values - the allele_set.
    For example, the gene could be used to represent a
    mathematical oeprator.  Here the allele_set might be
    ['+','-','*','/'].  The list could just as easily be
    a list of numbers (ie. standard capacitor values),
    strings, or anything else.

    The default mutator is a gaussian mutator and the
    default initializer randomly chooses a value from the
    allele_set.
    """
    gaussian_mutator = list_gene_gaussian_mutator
    uniform_mutator = list_gene_uniform_mutator
    walk_mutator = list_gene_walk_mutator
    mutator = gaussian_mutator()
    initializer = uniform_mutator()
    def __init__(self, allele_set): self.allele_set = allele_set
    def index(self,*val):
        """set or retreive a specific value from the allele_set"""
        if len(val): self._value = self.allele_set[val[0]]
        return self.allele_set.index(self.value())

class list2_gene(list_gene):
    """
    this is something like we'll do to add part variance to capacitor
    and resistor values during evaluation
    """
    func = nop
    def value(self): 
        return self.func(self._value)
    def __repr__(self):
        return repr(self._value) #???

class float_gene_uniform_mutator(object):
    """ randomly choose a value within the float_gene's bounds"""
    def evaluate(self,gene):
        bounds=gene.bounds
        new = prng.uniform(bounds[0], bounds[1]-bounds[0])
        return new

class float_gene_gaussian_mutator(object):
    """
    chooses a new value for a float_gene with gaussian
    shaped distribution around the current value.

    dev_width -- a value between 0 and 1.  It is the standard
    deviation for the gaussian distribution as a percentage
    of the float_gene's range.  For example:  If the genes bounds
    are (0,10) and dev_width is .1, then the standard deviation
    is 1.
    """

    def __init__(self,dev_width = .1):
        self.dev_width = dev_width
    def evaluate(self,gene):
        dev = (gene.bounds[1]-gene.bounds[0]) * self.dev_width
        new = gene.bounds[1]
#       while not (gene.bounds[0] <= new < gene.bounds[1]):
#           new = prng.normal(gene.value(),dev)
#       new = prng.normal(gene.value(),dev)
        #get the _value explicitly so mutator will work for log_float also
        new = prng.normal(gene._value,dev)
        if new > gene.bounds[1]: new = gene.bounds[1]
        if new < gene.bounds[0]: new = gene.bounds[0]
        return new

class float_gene(gene):
    """
    A float_gene is a gene that takes on a floating point value
    between some upper and lower bounds.

    The default mutator is a gaussian mutator and the
    default initializer randomly chooses a value from within
    the upper and lower bounds.

    bounds -- A 2 element tuple that specifies the lower and upper
    bounds for the gene.
    """
    gaussian_mutator = float_gene_gaussian_mutator
    uniform_mutator = float_gene_uniform_mutator
    mutator = gaussian_mutator()
    initializer = uniform_mutator()
    def __init__(self,bounds):
        if len(bounds) !=2: raise GAError, 'float_gene: init expects a 2 element tuple of the fomr (min,max)'
        self.bounds = bounds
    def set_value(self,x):
        """ Set the value of a gene.
            Convertthe value to a float first!
        """
        self._value = float(x)

class log_float_gene(float_gene):
    def __init__(self,bounds):
        if len(bounds) !=2: raise GAError, 'float_gene: init expects a 2 element tuple of the fomr (min,max)'
        self.bounds = log10(array(bounds))
    def value(self):
        """Return the current value of the gene. """
        try: return 10.**(self._value)
        except AttributeError: raise GAError, 'gene not initialized'

class frozen(object):
    """frozen is a gene that always maintains the same value.
    """
    def __init__(self,val): self._value = val
    def initialize(self): pass
    def set_mutation(self,mrate): pass
    def mutate(self): pass
    def value(self) : return self._value
    def clone(self): return shallow_clone(self)
    def __float__(self): return float(self._value)
    def __repr__(self): return `self._value`
    def __add__(self, other):
        try: return self._value + other.value()
        except AttributeError: return self._value + other
    __radd__ = __add__
    def __mul__(self, other):
        try: return self._value * other.value()
        except AttributeError: return self._value * other
    __rmul__ = __mul__
    def __sub__(self, other):
        try: return self._value - other.value()
        except AttributeError: return self._value - other
    def __rsub__(self, other):
        try: return other.value() - self._value
        except AttributeError: return other - self._value
    def __div__(self, other):
        try: return self._value / other.value()
        except: return self._value / other
    def __rdiv__(self, other):
        try: return other.value() / self._value
        except AttributeError: return other / self._value
    def __neg__(self): return -self._value
    def __cmp__(self, other):
        try:
            if self.__class__ == other.__class__ and self.__dict__ == other.__dict__: return 0
        except AttributeError: pass
        v1 = self.value()
        try: v2 = other.value()
        except AttributeError: v2 = other
        return cmp(v1,v2)

class tree_gene(tree_node):
    mr_bounds = (0,.1)
    mutation_rate = .03
    model_properties = {}
    def __init__(self,child_count,node_type='',derive_type='', parent=None):
        tree_node.__init__(self,child_count,node_type,derive_type, parent)
    def initialize(self,propagate = 1):
        if propagate:
            for child in self._children: child.initialize()
    def defaultize(self):
        for child in self._children: child.defaultize()
    def set_mutation(self,mrate):
        if(mrate=='gene'):
            try: del self.mutation_rate #remove local mrates and use gene classes mrate
            except AttributeError: pass
        elif(mrate=='adapt'):
            self.mutation_rate = prng.uniform(self.mr_bounds[0],self.mr_bounds[1])
        else:
            self.__class__.mutation_rate = mrate
        for child in self._children: child.set_mutation(mrate)

    def mutate(self,propagate = 1):
        mutated = 0
        #if flip_coin(self.mutation_rate): pass # handle tree mutation
        if propagate:
            for child in self._children:
                #careful with short circuit "or"
                mutated = child.mutate() or mutated
        return mutated

    def value(self):
        """Return the current value of the gene. """
        try: return self._value
        except AttributeError: raise GAError, 'gene not initialized'
    def set_value(self,x):
        """ Set the value of a gene. NO CHECKING!!!
            Don't assign an incompatible value.
        """
        self._value = x