gene.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. """
  2. Implements a collection of gene classes
  3. Genes support the following python operators:
  4. - + - calculates the phenotype resulting from the
  5. combination of a pair of genes
  6. These genes work via classical Mendelian genetics
  7. """
  8. import sys, new
  9. from random import random, randint, uniform, choice
  10. from math import sqrt
  11. from xmlio import PGXmlMixin
  12. class BaseGene(PGXmlMixin):
  13. """
  14. Base class from which all the gene classes are derived.
  15. You cannot use this class directly, because there are
  16. some methods that must be overridden.
  17. """
  18. # each gene should have an object in
  19. # which its genotype should be stored
  20. value = None
  21. # probability of a mutation occurring
  22. mutProb = 0.01
  23. # List of acceptable fields for the factory
  24. fields = ["value", "mutProb"]
  25. def __init__(self, value=None):
  26. # if value is not provided, it will be
  27. # randomly generated
  28. if value is not None:
  29. self.value = value
  30. elif self.__class__.value is not None:
  31. self.value = self.__class__.value
  32. else:
  33. self.value = self.randomValue()
  34. def copy(self):
  35. """
  36. returns clone of this gene
  37. """
  38. cls = self.__class__()
  39. cls.value = self.value
  40. return cls
  41. def __add__(self, other):
  42. """
  43. Combines two genes in a gene pair, to produce an effect
  44. This is used to determine the gene's phenotype
  45. This default method computes the arithmetic mean
  46. of the two genes.
  47. Override as needed
  48. Must be overridden
  49. """
  50. raise Exception("Method __add__ must be overridden")
  51. def __repr__(self):
  52. return "<%s:%s>" % (self.__class__.__name__, self.value)
  53. # def __cmp__(this, other):
  54. # return cmp(this.value, other.value)
  55. #
  56. def maybeMutate(self):
  57. if random() < self.mutProb:
  58. self.mutate()
  59. def mutate(self):
  60. """
  61. Perform a mutation on the gene
  62. You MUST override this in subclasses
  63. """
  64. raise Exception("method 'mutate' not implemented")
  65. def randomValue(self):
  66. """
  67. Generates a plausible random value
  68. for this gene.
  69. Must be overridden
  70. """
  71. raise Exception("Method 'randomValue' not implemented")
  72. def xmlDumpSelf(self, doc, parent):
  73. """
  74. dump out this gene into parent tag
  75. """
  76. genetag = doc.createElement("gene")
  77. parent.appendChild(genetag)
  78. self.xmlDumpClass(genetag)
  79. self.xmlDumpAttribs(genetag)
  80. # now dump out the value into a text tag
  81. ttag = doc.createTextNode(str(self.value))
  82. # and add it to self tag
  83. genetag.appendChild(ttag)
  84. def xmlDumpAttribs(self, tag):
  85. """
  86. sets attributes of tag
  87. """
  88. tag.setAttribute("mutProb", str(self.mutProb))
  89. class ComplexGene(BaseGene):
  90. """
  91. A gene whose value is a complex point number
  92. """
  93. # amount by which to mutate, will change value
  94. # by up to +/- this amount
  95. mutAmtReal = 0.1
  96. mutAmtImag = 0.1
  97. # used for random gene creation
  98. # override in subclasses
  99. randMin = -1.0
  100. randMax = 1.0
  101. # Acceptable fields for factory
  102. fields = ["value", "mutProb", "mutAmtReal", "mutAmtImag",
  103. "randMin", "randMax"]
  104. def __add__(self, other):
  105. """
  106. Combines two genes in a gene pair, to produce an effect
  107. This is used to determine the gene's phenotype
  108. This class computes the arithmetic mean
  109. of the two genes' values, so is akin to incomplete
  110. dominance.
  111. Override if desired
  112. """
  113. return (self.value + other.value) / 2
  114. #return abs(complex(self.value.real, other.value.imag))
  115. def mutate(self):
  116. """
  117. Mutate this gene's value by a random amount
  118. within the range +/- self.mutAmt
  119. perform mutation IN-PLACE, ie don't return mutated copy
  120. """
  121. self.value += complex(
  122. uniform(-self.mutAmtReal, self.mutAmtReal),
  123. uniform(-self.mutAmtImag, self.mutAmtImag)
  124. )
  125. # if the gene has wandered outside the alphabet,
  126. # rein it back in
  127. real = self.value.real
  128. imag = self.value.imag
  129. if real < self.randMin:
  130. real = self.randMin
  131. elif real > self.randMax:
  132. real = self.randMax
  133. if imag < self.randMin:
  134. imag = self.randMin
  135. elif imag > self.randMax:
  136. imag = self.randMax
  137. self.value = complex(real, imag)
  138. def randomValue(self):
  139. """
  140. Generates a plausible random value
  141. for this gene.
  142. Override as needed
  143. """
  144. min = self.randMin
  145. range = self.randMax - min
  146. real = uniform(self.randMin, self.randMax)
  147. imag = uniform(self.randMin, self.randMax)
  148. return complex(real, imag)
  149. class FloatGene(BaseGene):
  150. """
  151. A gene whose value is a floating point number
  152. Class variables to override:
  153. - mutAmt - default 0.1 - amount by which to mutate.
  154. The gene will will move this proportion towards
  155. its permissible extreme values
  156. - randMin - default -1.0 - minimum possible value
  157. for this gene. Mutation will never allow the gene's
  158. value to be less than this
  159. - randMax - default 1.0 - maximum possible value
  160. for this gene. Mutation will never allow the gene's
  161. value to be greater than this
  162. """
  163. # amount by which to mutate, will change value
  164. # by up to +/- this amount
  165. mutAmt = 0.1
  166. # used for random gene creation
  167. # override in subclasses
  168. randMin = -1.0
  169. randMax = 1.0
  170. # Acceptable fields for factory
  171. fields = ["value", "mutProb", "mutAmt", "randMin", "randMax"]
  172. def __add__(self, other):
  173. """
  174. Combines two genes in a gene pair, to produce an effect
  175. This is used to determine the gene's phenotype
  176. This class computes the arithmetic mean
  177. of the two genes' values, so is akin to incomplete
  178. dominance.
  179. Override if desired
  180. """
  181. return (self.value + other.value) / 2
  182. def mutate(self):
  183. """
  184. Mutate this gene's value by a random amount
  185. within the range, which is determined by
  186. multiplying self.mutAmt by the distance of the
  187. gene's current value from either endpoint of legal values
  188. perform mutation IN-PLACE, ie don't return mutated copy
  189. """
  190. if random() < 0.5:
  191. # mutate downwards
  192. self.value -= uniform(0, self.mutAmt * (self.value-self.randMin))
  193. else:
  194. # mutate upwards:
  195. self.value += uniform(0, self.mutAmt * (self.randMax-self.value))
  196. def randomValue(self):
  197. """
  198. Generates a plausible random value
  199. for this gene.
  200. Override as needed
  201. """
  202. return uniform(self.randMin, self.randMax)
  203. class FloatGeneRandom(FloatGene):
  204. """
  205. Variant of FloatGene where mutation always randomises the value
  206. """
  207. def mutate(self):
  208. """
  209. Randomise the gene
  210. perform mutation IN-PLACE, ie don't return mutated copy
  211. """
  212. self.value = self.randomValue()
  213. class FloatGeneMax(FloatGene):
  214. """
  215. phenotype of this gene is the greater of the values
  216. in the gene pair
  217. """
  218. def __add__(self, other):
  219. """
  220. produces phenotype of gene pair, as the greater of this
  221. and the other gene's values
  222. """
  223. return max(self.value, other.value)
  224. class FloatGeneExchange(FloatGene):
  225. """
  226. phenotype of this gene is the random of the values
  227. in the gene pair
  228. """
  229. def __add__(self, other):
  230. """
  231. produces phenotype of gene pair, as the random of this
  232. and the other gene's values
  233. """
  234. return choice([self.value, other.value])
  235. class IntGene(BaseGene):
  236. """
  237. Implements a gene whose values are ints,
  238. constrained within the randMin,randMax range
  239. """
  240. # minimum possible value for gene
  241. # override in subclasses as needed
  242. randMin = -sys.maxint
  243. # maximum possible value for gene
  244. # override in subclasses as needed
  245. randMax = sys.maxint + 1
  246. # maximum amount by which gene can mutate
  247. mutAmt = 1
  248. # Acceptable fields for factory
  249. fields = ["value", "mutProb", "mutAmt", "randMin", "randMax"]
  250. def mutate(self):
  251. """
  252. perform gene mutation
  253. perform mutation IN-PLACE, ie don't return mutated copy
  254. """
  255. self.value += randint(-self.mutAmt, self.mutAmt)
  256. # if the gene has wandered outside the alphabet,
  257. # rein it back in
  258. if self.value < self.randMin:
  259. self.value = self.randMin
  260. elif self.value > self.randMax:
  261. self.value = self.randMax
  262. def randomValue(self):
  263. """
  264. return a legal random value for this gene
  265. which is in the range [self.randMin, self.randMax]
  266. """
  267. return randint(self.randMin, self.randMax)
  268. def __add__(self, other):
  269. """
  270. produces the phenotype resulting from combining
  271. this gene with another gene in the pair
  272. returns an int value, based on a formula of higher
  273. numbers dominating
  274. """
  275. return max(self.value, other.value)
  276. class IntGeneExchange(IntGene):
  277. def __add__(self, other):
  278. """
  279. A variation of int gene where during the mixing a
  280. random gene is selected instead of max.
  281. """
  282. return choice([self.value, other.value])
  283. class CharGene(BaseGene):
  284. """
  285. Gene that holds a single ASCII character,
  286. as a 1-byte string
  287. """
  288. # minimum possible value for gene
  289. # override in subclasses as needed
  290. randMin = chr(0)
  291. # maximum possible value for gene
  292. # override in subclasses as needed
  293. randMax = chr(255)
  294. def __repr__(self):
  295. """
  296. Returns safely printable value
  297. """
  298. return str(self.value)
  299. def mutate(self):
  300. """
  301. perform gene mutation
  302. perform mutation IN-PLACE, ie don't return mutated copy
  303. """
  304. self.value = chr(ord(self.value) + randint(-self.mutAmt, self.mutAmt))
  305. # if the gene has wandered outside the alphabet,
  306. # rein it back in
  307. if self.value < self.randMin:
  308. self.value = self.randMin
  309. elif self.value > self.randMax:
  310. self.value = self.randMax
  311. def randomValue(self):
  312. """
  313. return a legal random value for this gene
  314. which is in the range [self.randMin, self.randMax]
  315. """
  316. return chr(randint(ord(self.randMin), ord(self.randMax)))
  317. def __add__(self, other):
  318. """
  319. produces the phenotype resulting from combining
  320. this gene with another gene in the pair
  321. returns an int value, based on a formula of higher
  322. numbers dominating
  323. """
  324. return max(self.value, other.value)
  325. class AsciiCharGene(CharGene):
  326. """
  327. Specialisation of CharGene that can only
  328. hold chars in the legal ASCII range
  329. """
  330. # minimum possible value for gene
  331. # override in subclasses as needed
  332. randMin = chr(0)
  333. # maximum possible value for gene
  334. # override in subclasses as needed
  335. randMax = chr(255)
  336. def __repr__(self):
  337. """
  338. still need to str() the value, since the range
  339. includes control chars
  340. """
  341. return self.value
  342. class PrintableCharGene(AsciiCharGene):
  343. """
  344. Specialisation of AsciiCharGene that can only
  345. hold printable chars
  346. """
  347. # minimum possible value for gene
  348. # override in subclasses as needed
  349. randMin = ' '
  350. # maximum possible value for gene
  351. # override in subclasses as needed
  352. randMax = chr(127)
  353. def __repr__(self):
  354. """
  355. don't need to str() the char, since
  356. it's already printable
  357. """
  358. return self.value
  359. class DiscreteGene(BaseGene):
  360. """
  361. Gene type with a fixed set of possible values, typically
  362. strings
  363. Mutation behaviour is that the gene's value may
  364. spontaneously change into one of its alleles
  365. """
  366. # this is the set of possible values
  367. # override in subclasses
  368. alleles = []
  369. # the dominant allele - leave as None
  370. # if gene has incomplete dominance
  371. dominant = None
  372. # the co-dominant alleles - leave empty
  373. # if gene has simple dominance
  374. codominant = []
  375. # the recessive allele - leave as None if there's a dominant
  376. recessive = None
  377. def mutate(self):
  378. """
  379. Change the gene's value into any of the possible alleles,
  380. subject to mutation probability 'self.mutProb'
  381. perform mutation IN-PLACE, ie don't return mutated copy
  382. """
  383. self.value = self.randomValue()
  384. def randomValue(self):
  385. """
  386. returns a random allele
  387. """
  388. return choice(self.alleles)
  389. def __add__(self, other):
  390. """
  391. determines the phenotype, subject to dominance properties
  392. returns a tuple of effects
  393. """
  394. # got simple dominance?
  395. if self.dominant in (self.value, other.value):
  396. # yes
  397. return (self.dominant,)
  398. # got incomplete dominance?
  399. elif self.codominant:
  400. phenotype = []
  401. for val in self.value, other.value:
  402. if val in self.codominant and val not in phenotype:
  403. phenotype.append(val)
  404. # apply recessive, if one exists and no codominant genes present
  405. if not phenotype:
  406. if self.recessive:
  407. phenotype.append(self.recessive)
  408. # done
  409. return tuple(phenotype)
  410. # got recessive?
  411. elif self.recessive:
  412. return (self.recessive,)
  413. # nothing else
  414. return ()
  415. class BitGene(BaseGene):
  416. """
  417. Implements a single-bit gene
  418. """
  419. def __add__(self, other):
  420. """
  421. Produces the 'phenotype' as xor of gene pair values
  422. """
  423. raise Exception("__add__ method not implemented")
  424. def mutate(self):
  425. """
  426. mutates this gene, toggling the bit
  427. probabilistically
  428. perform mutation IN-PLACE, ie don't return mutated copy
  429. """
  430. self.value ^= 1
  431. def randomValue(self):
  432. """
  433. Returns a legal random (boolean) value
  434. """
  435. return choice([0, 1])
  436. class AndBitGene(BitGene):
  437. """
  438. Implements a single-bit gene, whose
  439. phenotype is the AND of each gene in the pair
  440. """
  441. def __add__(self, other):
  442. """
  443. Produces the 'phenotype' as xor of gene pair values
  444. """
  445. return self.value and other.value
  446. class OrBitGene(BitGene):
  447. """
  448. Implements a single-bit gene, whose
  449. phenotype is the OR of each gene in the pair
  450. """
  451. def __add__(self, other):
  452. """
  453. Produces the 'phenotype' as xor of gene pair values
  454. """
  455. return self.value or other.value
  456. class XorBitGene(BitGene):
  457. """
  458. Implements a single-bit gene, whose
  459. phenotype is the exclusive-or of each gene in the pair
  460. """
  461. def __add__(self, other):
  462. """
  463. Produces the 'phenotype' as xor of gene pair values
  464. """
  465. return self.value ^ other.value
  466. ##
  467. # Gene factories
  468. # Necessary for config loading.
  469. ##
  470. def _new_factory(cls):
  471. "Creates gene factories"
  472. def factory(name, **kw):
  473. "Gene factory"
  474. for key in kw.iterkeys():
  475. if key not in cls.fields:
  476. raise Exception("Tried to create a gene with an invalid field: " + key)
  477. return new.classobj(name, (cls,), kw)
  478. return factory
  479. ComplexGeneFactory = _new_factory(ComplexGene)
  480. DiscreteGeneFactory = _new_factory(DiscreteGene)
  481. FloatGeneFactory = _new_factory(FloatGene)
  482. FloatGeneMaxFactory = _new_factory(FloatGeneMax)
  483. FloatGeneRandomFactory = _new_factory(FloatGeneRandom)
  484. FloatGeneExchangeFactory = _new_factory(FloatGeneExchange)
  485. IntGeneFactory = _new_factory(IntGene)
  486. IntGeneExchangeFactory = _new_factory(IntGeneExchange)
  487. CharGeneFactory = _new_factory(CharGene)
  488. AsciiCharGeneFactory = _new_factory(AsciiCharGene)
  489. PrintableCharGeneFactory = _new_factory(PrintableCharGene)
  490. # utility functions
  491. def rndPair(geneclass):
  492. """
  493. Returns a gene pair, comprising two random
  494. instances of the given gene class
  495. """
  496. return (geneclass(), geneclass())