GeneticAlgorithm  0.5 (beta)
A python framework for rapid GA prototyping
src/GeneticAlgorithm/Core.py
Go to the documentation of this file.
00001 import copy
00002 import random
00003 
00004 
00005 ## @class GABaseObject
00006 #  @brief The base object provides the functions repr and str
00007 #
00008 #  The GABaseObject provides a custom behavior for GA objects, which allows for a useful repr() and str() implementation.
00009 #  Because every object on this library is derived from this class, the whole scope of the library can be appreciated in this class' inheritance diagram.
00010 class GABaseObject(object):
00011     ## @__init__(self, **kwargs)
00012     #  @brief The default behavior for GA objects 
00013     # 
00014     #  The default behavior of GA objects is to accept any kind of named arguments on their constructor, and save them in properties with the same name.
00015     def __init__(self, **kwargs):
00016         for param, val in kwargs.items():
00017             self.__setattr__(param, val)    
00018     ## @fn __repr__(self)
00019     #  @brief The string representation of the object
00020     #
00021     #  The default behavior of GA objects is return representations as the Python code needed to reproduce the instance self
00022     def __repr__(self):
00023         return '%(class)s(%(params)s)' %   \
00024             { 'class':type(self).__name__, \
00025               'params':', '.join( [str(prop)+' = '+ repr(value) for prop, value in vars(self).iteritems()] ) }
00026     ## @fn __str__(self)
00027     #  @brief The string representation of the object
00028     #
00029     #  The default str() representation of GA objects is to return repr()
00030     def __str__(self):
00031         return repr(self)
00032 
00033 ## @class GeneticOperator
00034 #  @brief GeneticOperator is the base class that should be used to implement selection, crossover and mutation
00035 #
00036 #  The class GeneticAlgorithm is a scheduler for that periodically uses objects of this kind to transform the current population. This class can be derived to perform any activity, inclyuding but not limited to:
00037 #  <ul>
00038 #    <li>Logging, computing statistics about populations</li>
00039 #    <li>Interpretation or decoding: Genotype \f$ \rightarrow \f$ Phenotype</li>
00040 #    <li>Evaluation: Phenotype \f$ \rightarrow \f$ fitness</li>
00041 #    <li>Crossover: Genotype \f$\times\f$ Genotype \f$ \rightarrow \f$ Genotype</li>
00042 #    <li>Mutation: Genotype \f$ \rightarrow \f$ Genotype</li>
00043 #  </ul>
00044 #  Every genetic operator may overload any combination of these functions:
00045 #  <ul>
00046 #    <li>initialize</li>
00047 #    <li>iterate</li>
00048 #    <li>finalize</li>
00049 #  </ul>
00050 #  All of these functions receive a population object, which is expected to be modified by the function in order to implement the desired transformation.
00051 #  Please note that a single operator could implement a full genetic algorithm, but modularization is encouraged to allow easy re-use of operators 
00052 class GeneticOperator(GABaseObject):
00053     ## @fn initialize(population)
00054     #  @param population The population object to operate upon
00055     #  @brief This function is called once, during the startup phase of the algorithm
00056     def initialize(self, population):
00057         pass
00058     ## @fn iterate(population)
00059     #  @param population The population object to operate upon
00060     #  @brief This function is called once every iteration
00061     def iterate(self, population):
00062         pass
00063     ## @fn finalize(population)
00064     #  @param population The population object to operate upon
00065     #  @brief This function is called once at the end of the algorithm run
00066     def finalize(self, population):
00067         pass
00068 
00069 ## @class BasePeriodicOperator
00070 #  @brief A class that serves as a base for all periodic operators
00071 #
00072 #  This class automatically counts the number iterate has been called, and the number of individuals that have been evaluated so far.
00073 #  The operator calls the iterationCallback function whenever the iteration call count is an integer multiple of iterationFrequency.
00074 #  The operator calls the evaluationCallback function whenever the number of evaluated individuals is an integer multiple of evaluationFrequency
00075 class BasePeriodicOperator(GeneticOperator):
00076     ## @fn __init__(self, iterationCounter=0, evaluationCounter=0, iterationFrequency=0, evaluationFrequency=0, **kwargs)
00077     #  @brief Base periodic logger constructor
00078     #  @param iterationCounter    An initial value for the iteration counter
00079     #  @param evaluationCounter   An initial value for the individual evaluation counter
00080     #  @param iterationFrequency  This parameter controls how many iterations have to pass between iterationCallback calls
00081     #  @param evaluationFrequency This parameter controls how many individuals are evaluated between evaluationCallback calls
00082     def __init__(self, **kwargs):
00083         super(BasePeriodicOperator, self).__init__(**kwargs)
00084         self.iterationCounter    = kwargs.get('iterationCounter'   , 0)
00085         self.evaluationCounter   = kwargs.get('evaluationCounter'  , 0)
00086         self.iterationFrequency  = kwargs.get('iterationFrequency' , None)
00087         self.evaluationFrequency = kwargs.get('evaluationFrequency', None)
00088 
00089     
00090     ## @fn iterationCallback(self, population)
00091     #  @brief Overload this function to perform logging every time that the iterationCounter counter is an integer multiple of iterationFrequency
00092     def iterationCallback(self, population):
00093         pass
00094     
00095     ## @fn evaluationCallback(self, population)
00096     #  @brief Overload this function to perform logging every time that the evaluationCounter is an integer multiple of evaluationFrequency
00097     def evaluationCallback(self, population):
00098         pass
00099     
00100     ## @fn iterate(self, population)
00101     #  This function counts the number of times it has been called, and the number of individuals that have been evaluated so far, and calls the iterationCallback and evaluatioLogger functions accordingly
00102     def iterate(self, population):
00103         # Update the iteration counter and call the iterationCallback function whenever (self.iterationCounter % self.iterationFrequency) == 0
00104         self.iterationCounter  += 1
00105         # Update the evaluationCounter and call the evaluationCallback whenever (self.evaluationCounter % self.evaluationFrequency)==0
00106         self.evaluationCounter   += getattr(population, 'genSize', len(population.individuals) )
00107         if self.iterationFrequency and ((self.iterationCounter % self.iterationFrequency) == 0):
00108             self.iterationCallback(population)
00109         if self.evaluationFrequency and ((self.evaluationCounter % self.evaluationFrequency)==0):
00110             self.evaluationCallback(population)
00111 
00112     
00113 ## @class Mutate
00114 #  @brief Mutate an individual
00115 class Mutate(GeneticOperator):
00116     def mutate(self, population):
00117         pm = getattr(population, 'mutation_probability', 0.01 )        
00118         # Evaluate only recently generated items (pointed to by population.lethals)        
00119         lethals = getattr(population, 'lethals', None )
00120         #  If population.lethals does not exist, update every individual (and set the lethals list to contain every index)
00121         if not lethals:
00122             lethals = range(len(population.individuals))
00123         # Iterate over recently replaced individuals
00124         for i in lethals:
00125             if random.random() < pm:
00126                 population.individuals[i].mutate()
00127     iterate = mutate
00128 
00129 ## @class Crossover
00130 #  @brief Crossover two individuals from population.matingPool with probability population.crossoverProbability
00131 class Crossover(GeneticOperator):
00132     def cross(self, population):
00133         pc = getattr(population, 'crossover_probability', 1.0 )
00134         # Get the vector of pointers to lethals from population        
00135         lethals = getattr(population, 'lethals', None)
00136         # If no vector is available, then all individuals are scheduled for replacement
00137         if not lethals:
00138             lethals = xrange(len(population.individuals))
00139         # Get the number of individuals to replace
00140         nLethals = len(lethals)
00141         # Initialize a list of offspring vectors
00142         offspring = [None] * nLethals        
00143         # Get the mating pool from the population
00144         matingPool = getattr(population, 'matingPool', None)
00145         # If no mating pool is available, raise an error
00146         if not matingPool:
00147             raise RuntimeError('No mating pool found on population, a selection operator must come before Crossover')
00148         # Generate the offspring and insert them in different loops, to conserve the parents unchanged for crossover   
00149         for i in xrange(nLethals):
00150             offspring[i] = population.individuals[ matingPool[0] ]
00151             if random.random() < pc:         
00152                 offspring[i] = offspring[i].crossover( population.individuals[matingPool[1]] )
00153             # Make a deep copy of the new individual, to avoid a single segment to be referenced by several genotypes
00154             offspring[i] = copy.deepcopy(offspring[i])
00155             # Remove the first two parents from the mating pool
00156             matingPool = matingPool[2:]
00157         # Insert the offspring in the population
00158         for i, o in zip(lethals, offspring):
00159             population.individuals[i] = o
00160     iterate = cross;
00161     
00162 ## @class BaseChromosomeSegment
00163 #  @brief This class defines the minimal expression of a chromosome segment.
00164 #
00165 #  This class defines the minimal expresion of a chromosome. This interface contains:
00166 #  <ul>
00167 #    <li>data: The data contained in this segment</li>
00168 #    <li>crossover: Implements the algorithm to combine two chromosome segments</li>
00169 #    <li>mutation: Implements the algorithm to mutate this segment</li>
00170 #    <li>randomize: Assign a valid, random value to self.data</li>
00171 #  </ul> 
00172 class BaseChromosomeSegment(GABaseObject):
00173     ## @fn __init__(self, data=0, **kwargs)
00174     #  @brief A constructor that takes data and any number of named arguments, and adds all properties to the object
00175     #  @param data The data to assign to this segment
00176     # 
00177     #  Deriving from this class guarantees the following basic behavior, which is assumed by other GAObject classes:
00178     # 
00179     #  <ul>
00180     #    <li> The data property contains will be used by decode functions to produce a Penotype </li>
00181     #    <li> The constructor generates a random value for data whenever its not provided </li>
00182     #    <li> The constructor accepts any number of named arguments, which will be stored in properties of the same name</li>
00183     # </ul>
00184     #
00185     # The methods randomize, crossover and mutate must be re implemented on every specialization of this class. 
00186     def __init__(self, data=None, **kwargs):
00187         # Set all named properties first
00188         super(BaseChromosomeSegment, self).__init__(**kwargs)
00189         ## @property data This property returns the data used to produce a Genotype
00190         if data==None:
00191             # If no data was provided, randomize the chromosome segment
00192             self.randomize()
00193         else:
00194             ## @property data
00195             #  @brief The property that stores the chromosome segment value
00196             self.data  = data
00197         
00198     ## @fn randomize(self)
00199     #  @brief Assign a valid, random value to self.data
00200     def randomize(self):
00201         pass
00202     
00203     ## @fn crossover(self, other)
00204     #  @param other Another chromosome segment to be combined with self
00205     #  @brief This function is the crossover operator interface, and must be implemented for the default crossover functions to work
00206     #  @note It is recommended that all specializations of this function return a new object, sing the classes Genotype and Individual are containers and handle refereces exclusively. The generation of new chromosome segments is always delegated to this and the constructor functions.
00207     def crossover(self, other):
00208         pass
00209     ## @fn mutate(self)
00210     #  @brief This function is the mutation operator interface, and must be implemented for the default mutation functions to work 
00211     def mutate(self, chromo):
00212         pass
00213 
00214 ## @class Genotype
00215 #  @brief The base Genotype class from which all chromosomes must derived
00216 #
00217 #  The Genotype class is primarily a container that supports the minimum GA interface:
00218 #  <ul>
00219 #      <li>randomize(self)</li>
00220 #      <li>mutate(self)</li>
00221 #      <li>crossover(self, other)</li>
00222 #  </ul>
00223 class Genotype(GABaseObject):
00224     ## @fn init(segments=[])
00225     #  @brief Initialize the genotype
00226     #  @param segments A list of BaseChromosomeSegment or derived objects 
00227     def __init__(self, segments=[]):
00228         self.segments = segments
00229     ## @fn __setattr__(self, attribute, value)
00230     #  @brief This function allows typechecking to occur for segments, without the need for accessors
00231     def __setattr__(self, attribute, value):
00232         if attribute == 'segments':
00233             super(Genotype, self).__setattr__(attribute, [])
00234             for seg in value:
00235                 self.addSegment(seg)
00236         else:
00237             super(Genotype, self).__setattr__(attribute, value)
00238     ## @fn randomie(self)
00239     #  @brief Assign a random value to every segment
00240     def randomize(self):
00241         for s in self.segments:
00242             s.randomize()
00243     ## @fn addSegment(self, segment)
00244     #  @brief Add a segment to the genotype.        
00245     def addSegment(self, segment):
00246         if isinstance(segment, BaseChromosomeSegment):
00247             self.segments.append(segment)
00248     ## @fn crossover(self, other)
00249     #  @brief Perform a one-point crossover between self an and other Genotype
00250     #  @return A Genotype object that contains the new genotype
00251     #  @warning Segments are references to objects. It is recommended that 
00252     def crossover(self, other):
00253         crossPoint = random.randrange( len(self.segments) )
00254         return Genotype( self.segments[:crossPoint] + \
00255                            [ self.segments[crossPoint].crossover(other.segments[crossPoint]) ] + \
00256                            other.segments[crossPoint+1:] )
00257     ## @fn mutate(self)
00258     #  @brief Select one segment randomly and call mutate() on it
00259     def mutate(self):
00260         mutant = random.randrange( len(self.segments) )
00261         self.segments[mutant].mutate()
00262     ## @fn __str__(self)
00263     #        
00264     def __str__(self):
00265         return '[%s]' % ', '.join([str(s) for s in self.segments])
00266     
00267 ##  @example GABaseObject-demo.py
00268 #   This example shows the usage model for the GABaseObject class
00269 ## @class Individual
00270 #  @brief This class is a container intended to store all information relevant to an individual.
00271 #
00272 #  This class is a container, intended to store all information relevant to an individual, including but not limited to:
00273 #  <ul>
00274 #    <li>Genotype</li>
00275 #    <li>Phenotype</li>
00276 #    <li>Evaluation</li>
00277 #    <li>Non-generational information, such as:</li>
00278 #      <ul>
00279 #        <li>Age</li>
00280 #        <li>Fitness moving average</li>
00281 #        <li>Fitness variance</li>
00282 #      </ul>
00283 #  </ul>             
00284 class Individual(GABaseObject):
00285     ## @fn __init__(self, genotype=[], **kwargs)
00286     #  @brief The individual constructor
00287     #
00288     #  The minimum expression of an individual is to have a genotype and fitness value. Any other imaginable property can be added at this stage by passing a named parameter 
00289     def __init__(self, genotype=Genotype(), fitness=0.0, **kwargs):
00290         super(Individual, self).__init__(genotype=genotype, fitness=fitness, **kwargs)        
00291             
00292     ## @fn randomize(self)
00293     #  @brief Call self.genotype.randomize()
00294     def randomize(self):
00295         self.genotype.randomize()
00296         
00297     ## @fn valuesToStr(self)
00298     #  @brief Return the string representation of the values of all properties
00299     #  @param separator The string that separates property values
00300     #  @return The string representation of the values of all properties separated by the optional separator string      
00301     def valuesToStr(self, separator='\t'):        
00302         return separator.join( [str(value) for value in vars(self).itervalues()] )
00303     
00304     ## @fn propertiesToStr(self)
00305     #  @brief Return the names of all properties in the order that valuesToStr prints them
00306     #  @param separator The string that separates property names
00307     #  @return The names properties separated by the optional separator string          
00308     def propertiesToStr(self, separator='\t'):
00309         return separator.join( [str(prop) for prop in vars(self).iterkeys()] )
00310     
00311     ## @fn __str__(self)
00312     #  @brief Return a string representation of self, accordin to the function toStr
00313     # 
00314     #  The behavior of this function can be easily changed with the following technique:
00315     #  @code
00316     #  Individual.__str__ = lambda(self) : self.toStr(propertyList=['property1', 'property2', ...])
00317     #  @endcode
00318     #  Where the propertyList is a list of strings containing the names of the properties to display
00319     def __str__(self):
00320         return self.valuesToStr()
00321 
00322     ## @fn crossover(self, other)
00323     #  @brief Crossover self and another genotype
00324     #  @return an Individual object containing the crossover of self and other
00325     def crossover(self, other):
00326         offspring = copy.deepcopy(self)
00327         offspring.genotype = offspring.genotype.crossover( other.genotype )
00328         return offspring
00329     
00330     ## @fn mutate
00331     #  @brief call mutate() on self's chromosome
00332     def mutate(self):
00333         self.genotype.mutate()
00334     
00335 ## @class Population
00336 #  @brief This class is a container, intended to store references to individuals and all their     
00337 class Population(GABaseObject):
00338     ## @fn __init__(self, individuals=[], individualSchema=Genotype(), **kwargs)
00339     #  @brief The Population constructor
00340     #  @param individuals A list of Individual objects
00341     #  @param schema A Genotype that will be used as template to produce the genotypes of the population
00342     #  @parap popSize If popSize is provided, self.populate(popSize) and self.randomize() are called after initialization, note that this overrides the value passed to individuals
00343     def __init__(self, name='', individuals=[], schema=Genotype(), popSize=None, maximize=True, **kwargs):
00344         super(Population, self).__init__(name=name, individuals=individuals, schema=schema, maximize=maximize, **kwargs)
00345         if popSize:
00346             self.populate(popSize)
00347             self.randomize()
00348         
00349     ## @fn __str__(self)
00350     #  @brief The string representation of the population
00351     #
00352     #  This function prints all variables associated with self. Note that any user-defined property will be printed as well
00353     def __str__(self):
00354         msg = [ self.name ]
00355         for prop, value in vars(self).iteritems():
00356             if prop == 'name' or prop == 'schema' or prop == 'individuals' :
00357                 continue
00358             else:
00359                 msg+=[ str(prop) + ': ' + str(value) ]
00360         msg+= [ 'schema: ' + repr(self.schema) ]
00361         msg+= [ 'Individuals: (' + self.individuals[0].propertiesToStr(',') + ')' ] + [str(i) + '\t' + str(ind) for i, ind in enumerate(self.individuals) ]    
00362         return '\n'.join(msg)
00363     
00364     ## @fn populate(n=100)
00365     #  @brief Generate the list of individuals by copying the schema n times
00366     #  @param n The number of individuals to contain in the population
00367     def populate(self, n=100):
00368         self.individuals = [ Individual(genotype=copy.deepcopy(self.schema)) for i in xrange(n) ]
00369     
00370     ## @fn randomize(self)
00371     #  @brief Randomize the population by calling randomize on each individual
00372     def randomize(self):
00373         for individual in self.individuals:
00374             individual.randomize()
00375 
00376 ## @class Scheduler
00377 #  @brief A class that encapsulates a Population and a list of GeneticOperator
00378 #
00379 #  The class GAShceduler encapsulates a list of GeneticOperator objects and a population.
00380 #  This class is designed to be the highest level of abstraction of a GA. The scheduler applies the genetic operators in order, and provides the following interface:
00381 #
00382 #  <ul>
00383 #    <li>initialize</li>
00384 #    <li>iterate</li>
00385 #    <li>initialize</li>
00386 #  <ul>
00387 class Scheduler(GABaseObject):
00388     ## @fn __init__(self, name='Untitled', operators=[], population=Population()):
00389     #  @brief GAScheduler
00390     def __init__(self, name='Untitled', operators=[], population=Population(), **kwargs):
00391         super(Scheduler, self).__init__(name=name, operators=operators, population=population, **kwargs)
00392     ## @fn __str__(self)
00393     #  @brief Return the string representation of the scheduler
00394     def __str__(self):
00395         msg = ['name: ' + str(self.name)]
00396         msg+= ['\n'.join(['operators:'] + [str(o) for o in self.operators])]
00397         msg+= ['population: ' + str(self.population)]
00398         return '\n'.join(msg)
00399 
00400     ## @fn initialize(self)
00401     #  @brief This function is called at the begining of the runGA method
00402     #
00403     #  This function calls the initialize function of every operator on population once. Genetic operators are expected to initialize any variables 
00404     def initialize(self):
00405         for o in self.operators:
00406             o.initialize(self.population)
00407 
00408     ## @fn iterate(self)
00409     #  @brief This function is called every iteration of the runGA method
00410     # 
00411     #  Every operator iterate method over self.population
00412     def iterate(self):
00413         for o in self.operators:
00414             o.iterate(self.population)
00415     
00416     ## @fn finalize(self)
00417     #  @brief This function is called once at the end of the runGA method
00418     #
00419     #  Callthe finalize method of every operator at the end of runGA
00420     def finalize(self):
00421         for o in self.operators:
00422             o.finalize(self.population)
00423     
00424     ## @fn runGA
00425     #  @brief Initialize, run n iterations and finalize the GA run
00426     #  @param n The number of iterations to run
00427     def runGA(self, n):
00428         self.initialize()
00429         for i in xrange(n):
00430             self.iterate()
00431         self.finalize()
 All Classes Namespaces Files Functions Variables