/usr/share/pyshared/genetic/organism.py is in python-genetic 0.1.1b-11.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 | # Genetic
# Copyright (C) 2001 Jean-Baptiste LAMY
#
# This program is free software. See README or LICENSE for the license terms.
"""genetic.organism -- Organism and Chromosom class.
FUNC is the function to minimize. The only thing this module can do is minimizing a function, but hey, that can do a LOT of thing !!!
You should provide FUNC by calling setfunc(FUNC); see the demos for example.
FUNC can take as many args as you need, and those args's names must correspond to genes's names. FUNC must return a float value, or None is no result is available.
An Organism is described by his 'genotype' and his 'phenotype'.
A 'genotype' is considered to be a list of pairs of chromosoms : [(chromosom_0_A, chromosom_0_B), (chromosom_1_A, chromosom_1_B), ...]
Some chromosoms can be 'None', for incomplete pairs.
The genotype is inherited from the parents (A child take one chromosom of each pair of each parent).
A 'phenotype' is a 2-values tuple; the first value is the result of FUNC for the organism, the second is the sequence of the args for FUNC.
The phenotype is computed from the genotype, according to the PHENOTYPE function. This function take the genotype, and must return the genotype, or None is the organism cannot live (e.g. he has lost some gene...).
You may want to provide the PHENOTYPE function by calling setphenotypefunc(PHENOTYPE); see its docstring for more info. Default is recessive choice.
"""
#from __future__ import nested_scopes
import random, types, copy, operator
RECESSIVE_PHENOTYPE = 1
DOMINANT_PHENOTYPE = 2
PERCHROMOSOM_DOMINANCY_PHENOTYPE = 3
class Characteristic:
"""A characteristic of an organism : e.g. eyes color, ...
The characteritic's phenotype is computed from the genotype.
This class should be inherited/extended to create different, and :
A characteristic id defined by :
- The "func" function that computes its value. "func" takes genes values
and return the phenotype. The args of "func" should have the same names
that genes.
- The "phenotype" function that choose between the multiple values for a
genes the one used by "func" : there's typically 2 values for the same gene,
but only one can be used by "func" !
Common phenotype functions are provided in this class, use the following
constant in the constructor : RECESSIVE_PHENOTYPE (the default),
DOMINANT_PHENOTYPE and PERCHROMOSOME_DOMINANCY_PHENOTYPE.
"""
def __init__(self, name, func, phenotype = RECESSIVE_PHENOTYPE, funcargs = None):
"""Characteristic(name, func, phenotype = RECESSIVE_PHENOTYPE, funcargs = None) -> Characteristic -- Create a new characteristic.
funcargs is the list of the name of the genes that correspond to "func" argument.
If not provided, funcargs is guessed from func's arguments names.
"""
self.name = name
self.func = func
if funcargs is None:
if not type(func) is types.FunctionType: func = func.im_func
self.funcargs = func.func_code.co_varnames[: func.func_code.co_argcount]
else: self.funcargs = funcargs
if phenotype is RECESSIVE_PHENOTYPE: self.phenotype = self.recessive_phenotype
elif phenotype is DOMINANT_PHENOTYPE : self.phenotype = self.dominant_phenotype
elif phenotype is PERCHROMOSOM_DOMINANCY_PHENOTYPE:
self.phenotype = self.perchromosom_dominancy_phenotype
else: self.phenotype = phenotype
def perchromosom_dominancy_phenotype(self, genotype):
"""perchromosom_dominancy_phenotype(genotype) -> phenotype -- Gets the dominant phenotype from the given list, this func use per-chromosom dominancy (__dominancy__ gene).
Notice that this phenotype computation method is cheaper in time that dominant or recessive method."""
# 1 - Collects all the values for each gene, in a dict : {"gene1" : (dominancy, value), ...}
attrs = {}
for pair in genotype:
for chromosom in pair:
if chromosom is None: continue
for gene, value in chromosom.__dict__.items():
attr = attrs.get(gene, None)
if (not attr is None) and (attr[0] >= chromosom.__dominancy__):
# The previous found version of this gene is on a chromosom more dominant that the current one... skip !
continue
attrs[gene] = (chromosom.__dominancy__, value)
# 2 - Gets all the attrs needed by func in a list : [(dominancy1, value1), ...]
attrs_for_func = map(attrs.get, self.funcargs)
# 3 - Gets all the args in a list : [value1, value2, ...]
try: args = [attr[1] for attr in attrs_for_func]
except TypeError:
# A needed gene is not present ! (= attr is None)
return (None, None)
# 4 - Computes the phenotype
return (self.func(*args), args)
def recessive_phenotype(self, genotype):
"""recessive_phenotype(genotype) -> phenotype -- Gets the better phenotype from the given list. This roughly corresponds to a recessive disease."""
# Compute all the phenotype, and choose the one with the minimal value -- the best one
def chooser(phenotypes):
phenotypes = filter(lambda phenotype: not phenotype[0] is None, phenotypes)
if phenotypes: return min(phenotypes)
return (None, None)
return self._compute_all_phenotypes(genotype, chooser)
phenotype = recessive_phenotype
def dominant_phenotype(self, genotype):
"""dominant_phenotype(genotype) -> phenotype -- Gets the worst phenotype from the given list. This roughly corresponds to a dominant disease."""
# Compute all the phenotype, and choose the one with the maximal value -- the worst one
def chooser(phenotypes):
for phenotype in phenotypes:
if phenotype[0] is None: return phenotype
return max(phenotypes)
return self._compute_all_phenotypes(genotype, chooser)
def _compute_all_phenotypes(self, genotype, chooser):
"""_compute_all_phenotypes(genotype, chooser) -> phenotype -- An utility func that compute ALL the possible phenotype, and let chooser choose the right one.
genotype is a list of chromosoms pair, and chooser a function that take a list of phenotypes as argument, and should return the right one."""
# 1 - Collects all the values for each gene, in a dict : {"gene1":[value1, value2, ...], ...}
attrs = {}
for pair in genotype:
for chromosom in pair:
if chromosom is None: continue
for gene, value in chromosom.__dict__.items():
attr = attrs.get(gene, None)
if attr is None:
attr = []
attrs[gene] = attr
attr.append(value)
# Computes the phenotype from the genotype :
# 2 - Gets all the args in a list : [[x1, x2], [y1, y2], ...]
attrs_for_func = map(attrs.get, self.funcargs)
# 3 - Gets all the combinations of these args : [[x1, y1], [x1, y2], ..., [x2, y1], [x2, y2], ...]
try: allcombinations = combinations(*attrs_for_func)
except EmptyListError:
# All the needed genes are not present... this organism cannot live !
return (None, None)
# 4 - Computes all the possible phenotypes : [(result, [x1, y1]), ...]
phenotypes = [(self.func(*combination), combination) for combination in allcombinations]
# 5 - Choose the right one.
return chooser(phenotypes)
class Organism:
"""The Organism class. An organism has :
- a genotype = a list of (possibly incomplete) pair of chromosoms,
- a phenotype, computed from the genotype by the phenotype function of the
environment. A phenotype is a tupple whose first element is the phenotype
value and whose second element is the list of arguments (= values of genes)
that has given this value.
You must override this class, and change the class attribute "characteristics".
This attribute should be set to the list of characteristics yours organisms
have.
"""
characteristics = []
def __init__(self, genotype):
"""Organism(genotype) -> Organism -- Creates a new Organism with the given genotype.
genotype can be either a list of pairs of chromosoms, or a list of chromosoms
for homozygote organisms (= both chromosoms of each pair are the same)."""
# 1 - Save data
if len(genotype) > 0 and isinstance(genotype[0], Chromosom):
# genotype is not a list of pairs of chromosoms but a list of chromosom
# => this organism is homozygote
genotype = map(None, genotype, genotype)
self.genotype = genotype
# 2 - Remove useless pair of chromosoms from the genotype -- this may not be really fair... ??
for pair in self.genotype[:]:
if (pair[0] is None or pair[0].useless()) and (pair[1] is None or pair[1].useless()): self.genotype.remove(pair)
# 3 - Compute the phenotypes
self._compute_phenotypes()
def _compute_phenotypes(self):
# Compute the phenotypes for each characteristic
self.canlive = 1
for characteristic in self.characteristics:
value, args = characteristic.phenotype(self.genotype)
setattr(self, characteristic.name, value)
setattr(self, characteristic.name + "_args", args) # The args that give this result -- usefull ! It's often what we want to know.
# If a phenotype returns None, there is no available phenotype, so the organism cannot live.
if value is None: self.canlive = 0
def __repr__(self):
if self.canlive:
charac = ["%s%s : %s%s\n" % (characteristic.name, characteristic.funcargs, getattr(self, characteristic.name), getattr(self, characteristic.name + "_args")) for characteristic in self.characteristics]
repr = reduce(operator.add, charac)
# repr = reduce(operator.add,
# map(lambda characteristic: "%s%s : %s%s\n" % (characteristic.name, characteristic.funcargs, getattr(self, characteristic.name), getattr(self, characteristic.name + "_args")),
# self.characteristics
# )
# )
else:
repr = "phenotype : DEAD (non-viable)\n"
i = 0
for pair in self.genotype:
if pair[0] is None: repr = repr + "genotype, chromosome %s A : None\n" % i
else: repr = repr + "genotype, chromosome %s A :\n%s" % (i, `pair[0]`)
if pair[1] is None: repr = repr + "genotype, chromosome %s B : None\n" % i
else: repr = repr + "genotype, chromosome %s B :\n%s" % (i, `pair[1]`)
i = i + 1
return repr
def __eq__(self, other): return self.genotype == other.genotype
def givetochild(self):
gift = []
for pair in self.genotype:
chromosom = self.givechromosomtochild(pair)
if isinstance(chromosom, Chromosom): gift.append(chromosom)
else: gift.extend(chromosom)
return gift
def givechromosomtochild(self, pair):
if pair[0] is None: return pair[1] or []
if pair[1] is None: return pair[0]
if random.random() < (pair[0].__crossover__ + pair[1].__crossover__) / 2.0:
# Crossing over !
chromosom = pair[0].crossover(pair[1])
else:
# Choose a random chromosom
if random.random() < 0.5: chromosom = pair[0]
else: chromosom = pair[1]
# Checks for mutation
return chromosom.checkmutate()
class Mutable:
"""Mixin class for mutable object. This allows to use object as gene's value,
instead of float.
"""
def checkmutate(self):
"""Mutable.checkmutate() -> new value -- Called to check mutation in this mutable object.
The returned value should be the mutable object itself if it hasn't been
changed.
"""
pass
NotMutableAttributeError = "NotMutableAttributeError"
class Chromosom(Mutable):
DEFAULT_GENES = {
"__dominancy__" : 1.0 ,
"__mutation__" : 0.5 ,
"__mutampl__" : 50.0 ,
"__mutsign__" : 0.0 ,
"__crossover__" : 0.2 ,
"__break__" : 0.01,
"__deletion__" : 0.01,
"__loss__" : 0.01,
}
def __init__(self, **data):
self.__dict__.update(self.DEFAULT_GENES)
self.__dict__.update(data)
def useless(self):
# A useless chromosom is a chromosom that has ONLY "magic" genes (=__(something)__)
for gene in self.__dict__.keys():
if not gene.startswith("__"): return 0
return 1
def crossover(self, other):
dict = {}
genes1 = self .__dict__.items()
genes2 = other.__dict__.items()
crossat = int(random.random() * len(genes1))
i = 0
while i < crossat:
dict[genes1[i][0]] = genes1[i][1]
i = i + 1
while i < len(genes2):
dict[genes2[i][0]] = genes2[i][1]
i = i + 1
return Chromosom(**dict)
def checkmutate(self):
# Checks if the chromosom is lost
if random.random() < self.__loss__: return []
mutated = None
newdict = self.checkmutate_object(self.__dict__)
if not newdict is self.__dict__:
mutated = self.__class__(**newdict)
# Checks if a gene is deleted on the chromosom
if random.random() < self.__deletion__:
if mutated is None: mutated = self.__class__(**self.__dict__)
deletablegenes = filter(lambda gene: not gene.startswith("__"), mutated.__dict__.keys())
if len(deletablegenes) > 0:
delattr(mutated, random.choice(deletablegenes))
mutated = mutated or self
# Checks if the chromosom break in 2 parts
if random.random() < self.__break__:
genes = mutated.__dict__.items()
breakat = int(random.random() * (len(genes) + 1))
genes1, genes2 = {}, {}
for i in range(breakat): genes1[genes[i][0]] = genes[i][1]
for i in range(breakat, len(genes)): genes2[genes[i][0]] = genes[i][1]
return self.__class__(**genes1), self.__class__(**genes2)
return mutated
def checkmutate_object(self, object, name = ""):
objtype = type(object)
if objtype is types.FloatType:
if random.random() > self.__mutation__: return object
if name.startswith("__"):
# The gene is a "magic" gene. For magic gene, the amplitude of mutation is the gene's value itself.
return object * random.random() * 2.0
else:
# Else, use the default mutation sign and amplitude (= the value of the "__mutsign__" and "__mutampl__" gene)
mutsign = self.__mutsign__
if mutsign == 0.0 or (mutsign > 0.5 and mutsign < 2.0):
# No sign for the mutation
return object + (random.random() - 0.5) * 2.0 * self.__mutampl__
else:
# The mutation has a sign : if sign < 0.5, mutation can only decrease the gene's value. Else increase it.
if mutsign < 0.5: return object - random.random() * self.__mutampl__
else: return object + random.random() * self.__mutampl__
elif isinstance(object, Mutable): return object.checkmutate()
elif objtype is types.DictType:
mutated = None
for gene, value in object.items():
newvalue = self.checkmutate_object(value, gene)
if not newvalue is value:
if mutated is None: mutated = object.copy()
mutated[gene] = newvalue
return mutated or object
elif objtype is types.ListType:
#mutated = object[:]
#for i in range(len(mutated)): mutated[i] = self.checkmutate_object(mutated[i])
#return mutated
return map(self.checkmutate_object, object)
else:
# Non mutable => do not mutate !
return object
def __repr__(self):
repr = ""
genes = self.__dict__.items()
genes.sort()
for gene, value in genes:
if value != 0.0 or not gene.startswith("__"):
repr = repr + " %s\t : %s\n" % (gene, value)
return repr.expandtabs(32)
def multiply(organismA, organismB):
return organismA.__class__(map(None, organismA.givetochild(), organismB.givetochild()))
# Tools functions :
EmptyListError = "EmptyListError"
def combinations(*lists):
"""combinations([x1, x2,...], [y1, y2, ...], ...) -> [[x1, y1], [x1, y2], ..., [x2, y1], [x2, y2], ...] -- Get all the possible combinations, from the given lists of args."""
if not lists[0]: raise EmptyListError
if len(lists) == 1: return zip(lists[0])
r = []
subcombinations = combinations(*lists[1:])
for first in lists[0]:
r.extend([[first] + list(combination) for combination in subcombinations])
return r
def mean(*floats):
return reduce(operator.add, floats) / len(floats)
|