| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635 |
- """
- Implements a collection of gene classes
- Genes support the following python operators:
- - + - calculates the phenotype resulting from the
- combination of a pair of genes
- These genes work via classical Mendelian genetics
- """
- import sys, new
- from random import random, randint, uniform, choice
- from math import sqrt
- from xmlio import PGXmlMixin
- class BaseGene(PGXmlMixin):
- """
- Base class from which all the gene classes are derived.
- You cannot use this class directly, because there are
- some methods that must be overridden.
- """
- # each gene should have an object in
- # which its genotype should be stored
- value = None
- # probability of a mutation occurring
- mutProb = 0.01
- # List of acceptable fields for the factory
- fields = ["value", "mutProb"]
- def __init__(self, value=None):
- # if value is not provided, it will be
- # randomly generated
- if value is not None:
- self.value = value
- elif self.__class__.value is not None:
- self.value = self.__class__.value
- else:
- self.value = self.randomValue()
- def copy(self):
- """
- returns clone of this gene
- """
- cls = self.__class__()
- cls.value = self.value
- return cls
- def __add__(self, other):
- """
- Combines two genes in a gene pair, to produce an effect
- This is used to determine the gene's phenotype
- This default method computes the arithmetic mean
- of the two genes.
- Override as needed
- Must be overridden
- """
- raise Exception("Method __add__ must be overridden")
- def __repr__(self):
- return "<%s:%s>" % (self.__class__.__name__, self.value)
- # def __cmp__(this, other):
- # return cmp(this.value, other.value)
- #
- def maybeMutate(self):
- if random() < self.mutProb:
- self.mutate()
- def mutate(self):
- """
- Perform a mutation on the gene
- You MUST override this in subclasses
- """
- raise Exception("method 'mutate' not implemented")
- def randomValue(self):
- """
- Generates a plausible random value
- for this gene.
- Must be overridden
- """
- raise Exception("Method 'randomValue' not implemented")
- def xmlDumpSelf(self, doc, parent):
- """
- dump out this gene into parent tag
- """
- genetag = doc.createElement("gene")
- parent.appendChild(genetag)
- self.xmlDumpClass(genetag)
- self.xmlDumpAttribs(genetag)
- # now dump out the value into a text tag
- ttag = doc.createTextNode(str(self.value))
- # and add it to self tag
- genetag.appendChild(ttag)
- def xmlDumpAttribs(self, tag):
- """
- sets attributes of tag
- """
- tag.setAttribute("mutProb", str(self.mutProb))
- class ComplexGene(BaseGene):
- """
- A gene whose value is a complex point number
- """
- # amount by which to mutate, will change value
- # by up to +/- this amount
- mutAmtReal = 0.1
- mutAmtImag = 0.1
- # used for random gene creation
- # override in subclasses
- randMin = -1.0
- randMax = 1.0
- # Acceptable fields for factory
- fields = ["value", "mutProb", "mutAmtReal", "mutAmtImag",
- "randMin", "randMax"]
- def __add__(self, other):
- """
- Combines two genes in a gene pair, to produce an effect
- This is used to determine the gene's phenotype
- This class computes the arithmetic mean
- of the two genes' values, so is akin to incomplete
- dominance.
- Override if desired
- """
- return (self.value + other.value) / 2
- #return abs(complex(self.value.real, other.value.imag))
- def mutate(self):
- """
- Mutate this gene's value by a random amount
- within the range +/- self.mutAmt
- perform mutation IN-PLACE, ie don't return mutated copy
- """
- self.value += complex(
- uniform(-self.mutAmtReal, self.mutAmtReal),
- uniform(-self.mutAmtImag, self.mutAmtImag)
- )
- # if the gene has wandered outside the alphabet,
- # rein it back in
- real = self.value.real
- imag = self.value.imag
- if real < self.randMin:
- real = self.randMin
- elif real > self.randMax:
- real = self.randMax
- if imag < self.randMin:
- imag = self.randMin
- elif imag > self.randMax:
- imag = self.randMax
- self.value = complex(real, imag)
- def randomValue(self):
- """
- Generates a plausible random value
- for this gene.
- Override as needed
- """
- min = self.randMin
- range = self.randMax - min
- real = uniform(self.randMin, self.randMax)
- imag = uniform(self.randMin, self.randMax)
- return complex(real, imag)
- class FloatGene(BaseGene):
- """
- A gene whose value is a floating point number
- Class variables to override:
- - mutAmt - default 0.1 - amount by which to mutate.
- The gene will will move this proportion towards
- its permissible extreme values
- - randMin - default -1.0 - minimum possible value
- for this gene. Mutation will never allow the gene's
- value to be less than this
- - randMax - default 1.0 - maximum possible value
- for this gene. Mutation will never allow the gene's
- value to be greater than this
- """
- # amount by which to mutate, will change value
- # by up to +/- this amount
- mutAmt = 0.1
- # used for random gene creation
- # override in subclasses
- randMin = -1.0
- randMax = 1.0
- # Acceptable fields for factory
- fields = ["value", "mutProb", "mutAmt", "randMin", "randMax"]
- def __add__(self, other):
- """
- Combines two genes in a gene pair, to produce an effect
- This is used to determine the gene's phenotype
- This class computes the arithmetic mean
- of the two genes' values, so is akin to incomplete
- dominance.
- Override if desired
- """
- return (self.value + other.value) / 2
- def mutate(self):
- """
- Mutate this gene's value by a random amount
- within the range, which is determined by
- multiplying self.mutAmt by the distance of the
- gene's current value from either endpoint of legal values
- perform mutation IN-PLACE, ie don't return mutated copy
- """
- if random() < 0.5:
- # mutate downwards
- self.value -= uniform(0, self.mutAmt * (self.value-self.randMin))
- else:
- # mutate upwards:
- self.value += uniform(0, self.mutAmt * (self.randMax-self.value))
- def randomValue(self):
- """
- Generates a plausible random value
- for this gene.
- Override as needed
- """
- return uniform(self.randMin, self.randMax)
- class FloatGeneRandom(FloatGene):
- """
- Variant of FloatGene where mutation always randomises the value
- """
- def mutate(self):
- """
- Randomise the gene
- perform mutation IN-PLACE, ie don't return mutated copy
- """
- self.value = self.randomValue()
- class FloatGeneMax(FloatGene):
- """
- phenotype of this gene is the greater of the values
- in the gene pair
- """
- def __add__(self, other):
- """
- produces phenotype of gene pair, as the greater of this
- and the other gene's values
- """
- return max(self.value, other.value)
- class FloatGeneExchange(FloatGene):
- """
- phenotype of this gene is the random of the values
- in the gene pair
- """
- def __add__(self, other):
- """
- produces phenotype of gene pair, as the random of this
- and the other gene's values
- """
- return choice([self.value, other.value])
- class IntGene(BaseGene):
- """
- Implements a gene whose values are ints,
- constrained within the randMin,randMax range
- """
- # minimum possible value for gene
- # override in subclasses as needed
- randMin = -sys.maxint
- # maximum possible value for gene
- # override in subclasses as needed
- randMax = sys.maxint + 1
- # maximum amount by which gene can mutate
- mutAmt = 1
- # Acceptable fields for factory
- fields = ["value", "mutProb", "mutAmt", "randMin", "randMax"]
- def mutate(self):
- """
- perform gene mutation
- perform mutation IN-PLACE, ie don't return mutated copy
- """
- self.value += randint(-self.mutAmt, self.mutAmt)
- # if the gene has wandered outside the alphabet,
- # rein it back in
- if self.value < self.randMin:
- self.value = self.randMin
- elif self.value > self.randMax:
- self.value = self.randMax
- def randomValue(self):
- """
- return a legal random value for this gene
- which is in the range [self.randMin, self.randMax]
- """
- return randint(self.randMin, self.randMax)
- def __add__(self, other):
- """
- produces the phenotype resulting from combining
- this gene with another gene in the pair
- returns an int value, based on a formula of higher
- numbers dominating
- """
- return max(self.value, other.value)
- class IntGeneExchange(IntGene):
- def __add__(self, other):
- """
- A variation of int gene where during the mixing a
- random gene is selected instead of max.
- """
- return choice([self.value, other.value])
- class CharGene(BaseGene):
- """
- Gene that holds a single ASCII character,
- as a 1-byte string
- """
- # minimum possible value for gene
- # override in subclasses as needed
- randMin = chr(0)
- # maximum possible value for gene
- # override in subclasses as needed
- randMax = chr(255)
- def __repr__(self):
- """
- Returns safely printable value
- """
- return str(self.value)
- def mutate(self):
- """
- perform gene mutation
- perform mutation IN-PLACE, ie don't return mutated copy
- """
- self.value = chr(ord(self.value) + randint(-self.mutAmt, self.mutAmt))
- # if the gene has wandered outside the alphabet,
- # rein it back in
- if self.value < self.randMin:
- self.value = self.randMin
- elif self.value > self.randMax:
- self.value = self.randMax
- def randomValue(self):
- """
- return a legal random value for this gene
- which is in the range [self.randMin, self.randMax]
- """
- return chr(randint(ord(self.randMin), ord(self.randMax)))
- def __add__(self, other):
- """
- produces the phenotype resulting from combining
- this gene with another gene in the pair
- returns an int value, based on a formula of higher
- numbers dominating
- """
- return max(self.value, other.value)
- class AsciiCharGene(CharGene):
- """
- Specialisation of CharGene that can only
- hold chars in the legal ASCII range
- """
- # minimum possible value for gene
- # override in subclasses as needed
- randMin = chr(0)
- # maximum possible value for gene
- # override in subclasses as needed
- randMax = chr(255)
- def __repr__(self):
- """
- still need to str() the value, since the range
- includes control chars
- """
- return self.value
- class PrintableCharGene(AsciiCharGene):
- """
- Specialisation of AsciiCharGene that can only
- hold printable chars
- """
- # minimum possible value for gene
- # override in subclasses as needed
- randMin = ' '
- # maximum possible value for gene
- # override in subclasses as needed
- randMax = chr(127)
- def __repr__(self):
- """
- don't need to str() the char, since
- it's already printable
- """
- return self.value
- class DiscreteGene(BaseGene):
- """
- Gene type with a fixed set of possible values, typically
- strings
- Mutation behaviour is that the gene's value may
- spontaneously change into one of its alleles
- """
- # this is the set of possible values
- # override in subclasses
- alleles = []
- # the dominant allele - leave as None
- # if gene has incomplete dominance
- dominant = None
- # the co-dominant alleles - leave empty
- # if gene has simple dominance
- codominant = []
- # the recessive allele - leave as None if there's a dominant
- recessive = None
- def mutate(self):
- """
- Change the gene's value into any of the possible alleles,
- subject to mutation probability 'self.mutProb'
- perform mutation IN-PLACE, ie don't return mutated copy
- """
- self.value = self.randomValue()
- def randomValue(self):
- """
- returns a random allele
- """
- return choice(self.alleles)
- def __add__(self, other):
- """
- determines the phenotype, subject to dominance properties
- returns a tuple of effects
- """
- # got simple dominance?
- if self.dominant in (self.value, other.value):
- # yes
- return (self.dominant,)
- # got incomplete dominance?
- elif self.codominant:
- phenotype = []
- for val in self.value, other.value:
- if val in self.codominant and val not in phenotype:
- phenotype.append(val)
- # apply recessive, if one exists and no codominant genes present
- if not phenotype:
- if self.recessive:
- phenotype.append(self.recessive)
- # done
- return tuple(phenotype)
- # got recessive?
- elif self.recessive:
- return (self.recessive,)
- # nothing else
- return ()
- class BitGene(BaseGene):
- """
- Implements a single-bit gene
- """
- def __add__(self, other):
- """
- Produces the 'phenotype' as xor of gene pair values
- """
- raise Exception("__add__ method not implemented")
- def mutate(self):
- """
- mutates this gene, toggling the bit
- probabilistically
- perform mutation IN-PLACE, ie don't return mutated copy
- """
- self.value ^= 1
- def randomValue(self):
- """
- Returns a legal random (boolean) value
- """
- return choice([0, 1])
- class AndBitGene(BitGene):
- """
- Implements a single-bit gene, whose
- phenotype is the AND of each gene in the pair
- """
- def __add__(self, other):
- """
- Produces the 'phenotype' as xor of gene pair values
- """
- return self.value and other.value
- class OrBitGene(BitGene):
- """
- Implements a single-bit gene, whose
- phenotype is the OR of each gene in the pair
- """
- def __add__(self, other):
- """
- Produces the 'phenotype' as xor of gene pair values
- """
- return self.value or other.value
- class XorBitGene(BitGene):
- """
- Implements a single-bit gene, whose
- phenotype is the exclusive-or of each gene in the pair
- """
- def __add__(self, other):
- """
- Produces the 'phenotype' as xor of gene pair values
- """
- return self.value ^ other.value
- ##
- # Gene factories
- # Necessary for config loading.
- ##
- def _new_factory(cls):
- "Creates gene factories"
- def factory(name, **kw):
- "Gene factory"
- for key in kw.iterkeys():
- if key not in cls.fields:
- raise Exception("Tried to create a gene with an invalid field: " + key)
- return new.classobj(name, (cls,), kw)
- return factory
- ComplexGeneFactory = _new_factory(ComplexGene)
- DiscreteGeneFactory = _new_factory(DiscreteGene)
- FloatGeneFactory = _new_factory(FloatGene)
- FloatGeneMaxFactory = _new_factory(FloatGeneMax)
- FloatGeneRandomFactory = _new_factory(FloatGeneRandom)
- FloatGeneExchangeFactory = _new_factory(FloatGeneExchange)
- IntGeneFactory = _new_factory(IntGene)
- IntGeneExchangeFactory = _new_factory(IntGeneExchange)
- CharGeneFactory = _new_factory(CharGene)
- AsciiCharGeneFactory = _new_factory(AsciiCharGene)
- PrintableCharGeneFactory = _new_factory(PrintableCharGene)
- # utility functions
- def rndPair(geneclass):
- """
- Returns a gene pair, comprising two random
- instances of the given gene class
- """
- return (geneclass(), geneclass())
|