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
|