def __init__(self, genome, interactiveMode=True): """ Initializator of GSimpleGA """ #if seed is not None: random.seed(seed) # used to be like this if type(interactiveMode) != BooleanType: utils.raiseException("Interactive Mode option must be True or False", TypeError) if not isinstance(genome, GenomeBase): utils.raiseException("The genome must be a GenomeBase subclass", TypeError) self.internalPop = GPopulation(genome) self.nGenerations = constants.CDefGAGenerations self.pMutation = constants.CDefGAMutationRate self.pCrossover = constants.CDefGACrossoverRate self.nElitismReplacement = constants.CDefGAElitismReplacement self.setPopulationSize(constants.CDefGAPopulationSize) self.minimax = constants.minimaxType["maximize"] self.elitism = True # NEW self.new_population = None # Adapters self.dbAdapter = None self.migrationAdapter = None self.time_init = None self.max_time = None self.interactiveMode = interactiveMode self.interactiveGen = -1 self.GPMode = False self.selector = FunctionSlot("Selector") self.stepCallback = FunctionSlot("Generation Step Callback") self.terminationCriteria = FunctionSlot("Termination Criteria") self.selector.set(constants.CDefGASelector) self.allSlots = (self.selector, self.stepCallback, self.terminationCriteria) self.internalParams = {} self.currentGeneration = 0 # GP Testing for classes in constants.CDefGPGenomes: if isinstance(self.internalPop.oneSelfGenome, classes): self.setGPMode(True) break log.debug("A GA Engine was created, nGenerations=%d", self.nGenerations) # New self.path = None
def __init__(self): """ The constructor ... """ self.selector = FunctionSlot("Selector") self.GAEngine = None self.nMigrationRate = constants.CDefGenMigrationRate self.nIndividuals = constants.CDefMigrationNIndividuals self.nReplacement = constants.CDefGenMigrationReplacement self.networkCompression = 9
def __init__(self): """ Genome Constructor """ self.evaluator = FunctionSlot("Evaluator") self.initializator = FunctionSlot("Initializator") self.mutator = FunctionSlot("Mutator") self.crossover = FunctionSlot("Crossover") self.internalParams = {} self.score = 0.0 self.fitness = 0.0
def __init__(self, genome): """ The GPopulation Class creator """ if isinstance(genome, GPopulation): self.oneSelfGenome = genome.oneSelfGenome self.internalPop = [] self.internalPopRaw = [] self.popSize = genome.popSize self.sortType = genome.sortType self.sorted = False self.minimax = genome.minimax self.scaleMethod = genome.scaleMethod self.allSlots = [self.scaleMethod] self.internalParams = genome.internalParams self.multiProcessing = genome.multiProcessing self.statted = False self.stats = Statistics() return log.debug("New population instance, %s class genomes.", genome.__class__.__name__) self.oneSelfGenome = genome self.internalPop = [] self.internalPopRaw = [] self.popSize = 0 self.sortType = constants.CDefPopSortType self.sorted = False self.minimax = constants.CDefPopMinimax self.scaleMethod = FunctionSlot("Scale Method") self.scaleMethod.set(constants.CDefPopScale) self.allSlots = [self.scaleMethod] self.internalParams = {} self.multiProcessing = (False, False, None) # Statistics self.statted = False self.stats = Statistics()
def __init__(self, genome, interactiveMode=True): """ Initializator of GSimpleGA """ #if seed is not None: random.seed(seed) # used to be like this if type(interactiveMode) != BooleanType: utils.raiseException( "Interactive Mode option must be True or False", TypeError) if not isinstance(genome, GenomeBase): utils.raiseException("The genome must be a GenomeBase subclass", TypeError) self.internalPop = GPopulation(genome) self.nGenerations = constants.CDefGAGenerations self.pMutation = constants.CDefGAMutationRate self.pCrossover = constants.CDefGACrossoverRate self.nElitismReplacement = constants.CDefGAElitismReplacement self.setPopulationSize(constants.CDefGAPopulationSize) self.minimax = constants.minimaxType["maximize"] self.elitism = True # NEW self.new_population = None # Adapters self.dbAdapter = None self.migrationAdapter = None self.time_init = None self.max_time = None self.interactiveMode = interactiveMode self.interactiveGen = -1 self.GPMode = False self.selector = FunctionSlot("Selector") self.stepCallback = FunctionSlot("Generation Step Callback") self.terminationCriteria = FunctionSlot("Termination Criteria") self.selector.set(constants.CDefGASelector) self.allSlots = (self.selector, self.stepCallback, self.terminationCriteria) self.internalParams = {} self.currentGeneration = 0 # GP Testing for classes in constants.CDefGPGenomes: if isinstance(self.internalPop.oneSelfGenome, classes): self.setGPMode(True) break log.debug("A GA Engine was created, nGenerations=%d", self.nGenerations) # New self.path = None
class MigrationScheme(object): """ This is the base class for all migration schemes """ selector = None """ This is the function slot for the selection method if you want to change the default selector, you must do this: :: migration_scheme.selector.set(Selectors.GRouletteWheel) """ # ----------------------------------------------------------------- def __init__(self): """ The constructor ... """ self.selector = FunctionSlot("Selector") self.GAEngine = None self.nMigrationRate = constants.CDefGenMigrationRate self.nIndividuals = constants.CDefMigrationNIndividuals self.nReplacement = constants.CDefGenMigrationReplacement self.networkCompression = 9 # ----------------------------------------------------------------- def isReady(self): """ Returns true if is time to migrate """ return True if self.GAEngine.getCurrentGeneration() % self.nMigrationRate == 0 else False # ----------------------------------------------------------------- def getCompressionLevel(self): """ Get the zlib compression level of network data The values are in the interval described on the :func:`Network.pickleAndCompress` """ return self.networkCompression # ----------------------------------------------------------------- def setCompressionLevel(self, level): """ Set the zlib compression level of network data The values are in the interval described on the :func:`Network.pickleAndCompress` :param level: the zlib compression level """ self.networkCompression = level # ----------------------------------------------------------------- def getNumReplacement(self): """ Return the number of individuals that will be replaced in the migration process """ return self.nReplacement # ----------------------------------------------------------------- def setNumReplacement(self, num_individuals): """ Return the number of individuals that will be replaced in the migration process :param num_individuals: the number of individuals to be replaced """ self.nReplacement = num_individuals # ----------------------------------------------------------------- def getNumIndividuals(self): """ Return the number of individuals that will migrate :rtype: the number of individuals to be replaced """ return self.nIndividuals # ----------------------------------------------------------------- def setNumIndividuals(self, num_individuals): """ Set the number of individuals that will migrate :param num_individuals: the number of individuals """ self.nIndividuals = num_individuals # ----------------------------------------------------------------- def setMigrationRate(self, generations): """ Sets the generation frequency supposed to migrate and receive individuals. :param generations: the number of generations """ self.nMigrationRate = generations # ----------------------------------------------------------------- def getMigrationRate(self): """ Return the the generation frequency supposed to migrate and receive individuals :rtype: the number of generations """ return self.nMigrationRate # ----------------------------------------------------------------- def setGAEngine(self, ga_engine): """ Sets the GA Engine handler """ self.GAEngine = ga_engine # ----------------------------------------------------------------- def start(self): """ Initializes the migration scheme """ pass # ----------------------------------------------------------------- def stop(self): """ Stops the migration engine """ pass # ----------------------------------------------------------------- def select(self): """ Picks an individual from population using specific selection method :rtype: an individual object """ if self.selector.isEmpty(): return self.GAEngine.select(popID=self.GAEngine.currentGeneration) else: for it in self.selector.applyFunctions(self.GAEngine.internalPop, popID=self.GAEngine.currentGeneration): return it # ----------------------------------------------------------------- def selectPool(self, num_individuals): """ Select num_individuals number of individuals and return a pool :param num_individuals: the number of individuals to select :rtype: list with individuals """ pool = [self.select() for i in xrange(num_individuals)] return pool # ----------------------------------------------------------------- def exchange(self): """ Exchange individuals """ pass
class GPopulation(object): """ GPopulation Class - The container for the population **Examples** Get the population from the :class:`GSimpleGA.GSimpleGA` (GA Engine) instance >>> pop = ga_engine.get_population() Get the best fitness individual >>> bestIndividual = pop.bestFitness() Get the best raw individual >>> bestIndividual = pop.bestRaw() Get the statistics from the :class:`Statistics.Statistics` instance >>> stats = pop.getStatistics() >>> print stats["rawMax"] 10.4 Iterate, get/set individuals >>> for ind in pop: >>> print ind (...) >>> for i in xrange(len(pop)): >>> print pop[i] (...) >>> pop[10] = newGenome >>> pop[10].fitness 12.5 :param genome: the :term:`Sample genome`, or a GPopulation object, when cloning. """ def __init__(self, genome): """ The GPopulation Class creator """ if isinstance(genome, GPopulation): self.oneSelfGenome = genome.oneSelfGenome self.internalPop = [] self.internalPopRaw = [] self.popSize = genome.popSize self.sortType = genome.sortType self.sorted = False self.minimax = genome.minimax self.scaleMethod = genome.scaleMethod self.allSlots = [self.scaleMethod] self.internalParams = genome.internalParams self.multiProcessing = genome.multiProcessing self.statted = False self.stats = Statistics() return log.debug("New population instance, %s class genomes.", genome.__class__.__name__) self.oneSelfGenome = genome self.internalPop = [] self.internalPopRaw = [] self.popSize = 0 self.sortType = constants.CDefPopSortType self.sorted = False self.minimax = constants.CDefPopMinimax self.scaleMethod = FunctionSlot("Scale Method") self.scaleMethod.set(constants.CDefPopScale) self.allSlots = [self.scaleMethod] self.internalParams = {} self.multiProcessing = (False, False, None) # Statistics self.statted = False self.stats = Statistics() # ----------------------------------------------------------------- def setMultiProcessing(self, flag=True, full_copy=False, max_processes=None): """ This function sets the flag to enable/disable the use of python multiprocessing module. Use this option when you have more than one core on your CPU and when your evaluation function is very slow. The parameter "full_copy" defines where the individual data should be copied back after the evaluation or not. This parameter is useful when you change the individual in the evaluation function. :param flag: True (default) or False :param full_copy: True or False (default) :param max_processes: None (default) or an integer value .. warning:: Use this option only when your evaluation function is slow, se you will get a good tradeoff between the process communication speed and the parallel evaluation. .. versionadded:: 0.6 The `setMultiProcessing` method. """ self.multiProcessing = (flag, full_copy, max_processes) # ----------------------------------------------------------------- def setMinimax(self, minimax): """ Sets the population minimax Example: >>> pop.setMinimax(Consts.minimaxType["maximize"]) :param minimax: the minimax type """ self.minimax = minimax # ----------------------------------------------------------------- def __repr__(self): """ Returns the string representation of the population """ ret = "- GPopulation\n" ret += "\tPopulation Size:\t %d\n" % (self.popSize, ) ret += "\tSort Type:\t\t %s\n" % (constants.sortType.keys()[ constants.sortType.values().index(self.sortType)].capitalize(), ) ret += "\tMinimax Type:\t\t %s\n" % (constants.minimaxType.keys()[ constants.minimaxType.values().index(self.minimax)].capitalize(), ) for slot in self.allSlots: ret += "\t" + slot.__repr__() ret += "\n" ret += self.stats.__repr__() return ret # ----------------------------------------------------------------- def __len__(self): """ Return the length of population """ return len(self.internalPop) # ----------------------------------------------------------------- def __getitem__(self, key): """ Returns the specified individual from population """ return self.internalPop[key] # ----------------------------------------------------------------- def __iter__(self): """ Returns the iterator of the population """ return iter(self.internalPop) # ----------------------------------------------------------------- def __setitem__(self, key, value): """ Set an individual of population """ self.internalPop[key] = value self.clearFlags() # ----------------------------------------------------------------- def clearFlags(self): """ Clear the sorted and statted internal flags """ self.sorted = False self.statted = False # ----------------------------------------------------------------- def getStatistics(self): """ Return a Statistics class for statistics :rtype: the :class:`Statistics.Statistics` instance """ self.statistics() return self.stats # ----------------------------------------------------------------- def statistics(self): """ Do statistical analysis of population and set 'statted' to True """ if self.statted: return log.debug("Running statistical calculations") raw_sum = 0 len_pop = len(self) for ind in xrange(len_pop): raw_sum += self[ind].score self.stats["rawMax"] = max(self, key=key_raw_score).score self.stats["rawMin"] = min(self, key=key_raw_score).score self.stats["rawAve"] = raw_sum / float(len_pop) tmpvar = 0.0 for ind in xrange(len_pop): s = self[ind].score - self.stats["rawAve"] s *= s tmpvar += s tmpvar /= float((len(self) - 1)) try: self.stats["rawDev"] = math_sqrt(tmpvar) except: self.stats["rawDev"] = 0.0 self.stats["rawVar"] = tmpvar self.statted = True # ----------------------------------------------------------------- def bestFitness(self, index=0): """ Return the best scaled fitness individual of population :param index: the *index* best individual :rtype: the individual """ self.sort() return self.internalPop[index] # ----------------------------------------------------------------- def worstFitness(self): """ Return the worst scaled fitness individual of the population :rtype: the individual """ self.sort() return self.internalPop[-1] # ----------------------------------------------------------------- def bestRaw(self, index=0): """ Return the best raw score individual of population :param index: the *index* best raw individual :rtype: the individual .. versionadded:: 0.6 The parameter `index`. """ if self.sortType == constants.sortType["raw"]: return self.internalPop[index] else: self.sort() return self.internalPopRaw[index] # ----------------------------------------------------------------- def worstRaw(self): """ Return the worst raw score individual of population :rtype: the individual .. versionadded:: 0.6 The parameter `index`. """ if self.sortType == constants.sortType["raw"]: return self.internalPop[-1] else: self.sort() return self.internalPopRaw[-1] # ----------------------------------------------------------------- def sort(self): """ Sort the population """ if self.sorted: return rev = (self.minimax == constants.minimaxType["maximize"]) if self.sortType == constants.sortType["raw"]: self.internalPop.sort(cmp=utils.cmp_individual_raw, reverse=rev) else: self.scale() self.internalPop.sort(cmp=utils.cmp_individual_scaled, reverse=rev) self.internalPopRaw = self.internalPop[:] self.internalPopRaw.sort(cmp=utils.cmp_individual_raw, reverse=rev) self.sorted = True # ----------------------------------------------------------------- def setPopulationSize(self, size): """ This function sets the population size :param size: the population size """ self.popSize = size # ----------------------------------------------------------------- def setSortType(self, sort_type): """ Sets the sort type Example: >>> pop.setSortType(Consts.sortType["scaled"]) :param sort_type: the Sort Type """ self.sortType = sort_type # ----------------------------------------------------------------- def create(self, **args): """ Clone the example genome to fill the population """ self.minimax = args["minimax"] self.internalPop = [ self.oneSelfGenome.clone() for i in xrange(self.popSize) ] self.clearFlags() # ----------------------------------------------------------------- def __findIndividual(self, individual, end): """ This function ... :param individual: :param end: :return: """ for i in xrange(end): if individual.compare(self.internalPop[i]) == 0: return True # ----------------------------------------------------------------- def initialize(self, **args): """ Initialize all individuals of population, this calls the initialize() of individuals """ # Inform the user log.info("Initializing the population ...") if self.oneSelfGenome.getParam("full_diversity", True) and hasattr( self.oneSelfGenome, "compare"): for i in xrange(len(self.internalPop)): curr = self.internalPop[i] curr.initialize(**args) while self.__findIndividual(curr, i): curr.initialize(**args) else: for gen in self.internalPop: gen.initialize(**args) self.clearFlags() # ----------------------------------------------------------------- def evaluate(self, **args): """ Evaluate all individuals in population, calls the evaluate() method of individuals :param args: this params are passed to the evaluation function """ # Inform the user log.info("Evaluating the new population ...") # We have multiprocessing if self.multiProcessing[0] and MULTI_PROCESSING: log.debug( "Evaluating the population using the multiprocessing method") proc_pool = Pool(processes=self.multiProcessing[2]) # Multiprocessing full_copy parameter if self.multiProcessing[1]: results = proc_pool.map(multiprocessing_eval_full, self.internalPop) proc_pool.close() proc_pool.join() for i in xrange(len(self.internalPop)): self.internalPop[i] = results[i] else: results = proc_pool.map(multiprocessing_eval, self.internalPop) proc_pool.close() proc_pool.join() for individual, score in zip(self.internalPop, results): individual.score = score else: # No multiprocessing: basically just a loop over evaluate() of the individuals # Evaluate each individual for ind in self.internalPop: ind.evaluate(**args) self.clearFlags() # ----------------------------------------------------------------- def scale(self, **args): """ Scale the population using the scaling method :param args: this parameter is passed to the scale method """ for it in self.scaleMethod.applyFunctions(self, **args): pass fit_sum = 0 for ind in xrange(len(self)): fit_sum += self[ind].fitness self.stats["fitMax"] = max(self, key=key_fitness_score).fitness self.stats["fitMin"] = min(self, key=key_fitness_score).fitness self.stats["fitAve"] = fit_sum / float(len(self)) self.sorted = False # ----------------------------------------------------------------- def printStats(self): """ Print statistics of the current population """ message = "" if self.sortType == constants.sortType["scaled"]: message = "Max/Min/Avg Fitness(Raw) [%(fitMax).2f(%(rawMax).2f)/%(fitMin).2f(%(rawMin).2f)/%(fitAve).2f(%(rawAve).2f)]" % self.stats else: message = "Max/Min/Avg Raw [%(rawMax).2f/%(rawMin).2f/%(rawAve).2f]" % self.stats log.info(message) print message return message # ----------------------------------------------------------------- def copy(self, pop): """ Copy current population to 'pop' :param pop: the destination population .. warning:: this method do not copy the individuals, only the population logic """ pop.popSize = self.popSize pop.sortType = self.sortType pop.minimax = self.minimax pop.scaleMethod = self.scaleMethod pop.internalParams = self.internalParams pop.multiProcessing = self.multiProcessing # ----------------------------------------------------------------- def getParam(self, key, nvl=None): """ Gets an internal parameter Example: >>> population.getParam("tournamentPool") 5 :param key: the key of param :param nvl: if the key doesn't exist, the nvl will be returned """ return self.internalParams.get(key, nvl) # ----------------------------------------------------------------- def setParams(self, **args): """ Gets an internal parameter Example: >>> population.setParams(tournamentPool=5) :param args: parameters to set .. versionadded:: 0.6 The `setParams` method. """ self.internalParams.update(args) # ----------------------------------------------------------------- def clear(self): """ Remove all individuals from population """ del self.internalPop[:] del self.internalPopRaw[:] self.clearFlags() # ----------------------------------------------------------------- def clone(self): """ Return a brand-new cloned population """ newpop = GPopulation(self.oneSelfGenome) self.copy(newpop) return newpop
class GenomeBase(object): """ GenomeBase Class - The base of all chromosome representation """ __slots__ = [ "evaluator", "initializator", "mutator", "crossover", "internalParams", "score", "fitness" ] def __init__(self): """ Genome Constructor """ self.evaluator = FunctionSlot("Evaluator") self.initializator = FunctionSlot("Initializator") self.mutator = FunctionSlot("Mutator") self.crossover = FunctionSlot("Crossover") self.internalParams = {} self.score = 0.0 self.fitness = 0.0 # ----------------------------------------------------------------- def getRawScore(self): """ Get the Raw Score of the genome :rtype: genome raw score """ return self.score # ----------------------------------------------------------------- def getFitnessScore(self): """ Get the Fitness Score of the genome :rtype: genome fitness score """ return self.fitness # ----------------------------------------------------------------- def __repr__(self): """String representation of Genome""" allSlots = [ self.evaluator, self.initializator, self.mutator, self.crossover ] ret = "- GenomeBase\n" ret += "\tScore:\t\t\t %.6f\n" % (self.score, ) ret += "\tFitness:\t\t %.6f\n\n" % (self.fitness, ) ret += "\tParams:\t\t %s\n\n" % (self.internalParams, ) for slot in allSlots: ret += "\t" + slot.__repr__() ret += "\n" return ret # ----------------------------------------------------------------- def setParams(self, **args): """ Set the internal params Example: >>> genome.setParams(rangemin=0, rangemax=100, gauss_mu=0, gauss_sigma=1) .. note:: All the individuals of the population shares this parameters and uses the same instance of this dict. :param args: this params will saved in every chromosome for genetic op. use """ self.internalParams.update(args) # ----------------------------------------------------------------- def getParam(self, key, nvl=None): """ Gets an internal parameter Example: >>> genome.getParam("rangemax") 100 .. note:: All the individuals of the population shares this parameters and uses the same instance of this dict. :param key: the key of param :param nvl: if the key doesn't exist, the nvl will be returned """ return self.internalParams.get(key, nvl) # ----------------------------------------------------------------- def resetStats(self): """ Clear score and fitness of genome """ self.score = 0.0 self.fitness = 0.0 # ----------------------------------------------------------------- def evaluate(self, **args): """ Called to evaluate genome :param args: this parameters will be passes to the evaluator """ self.resetStats() for it in self.evaluator.applyFunctions(self, **args): self.score += it # ----------------------------------------------------------------- def initialize(self, **args): """ Called to initialize genome :param args: this parameters will be passed to the initializator """ for it in self.initializator.applyFunctions(self, **args): pass # ----------------------------------------------------------------- def mutate(self, **args): """ Called to mutate the genome :param args: this parameters will be passed to the mutator :rtype: the number of mutations returned by mutation operator """ nmuts = 0 for it in self.mutator.applyFunctions(self, **args): nmuts += it return nmuts # ----------------------------------------------------------------- def copy(self, g): """ Copy the current GenomeBase to 'g' :param g: the destination genome .. note:: If you are planning to create a new chromosome representation, you **must** implement this method on your class. """ g.score = self.score g.fitness = self.fitness g.evaluator = self.evaluator g.initializator = self.initializator g.mutator = self.mutator g.crossover = self.crossover g.internalParams = self.internalParams # ----------------------------------------------------------------- def clone(self): """ Clone this GenomeBase :rtype: the clone genome .. note:: If you are planning to create a new chromosome representation, you **must** implement this method on your class. """ newcopy = GenomeBase() self.copy(newcopy) return newcopy
class GPopulation(object): """ GPopulation Class - The container for the population **Examples** Get the population from the :class:`GSimpleGA.GSimpleGA` (GA Engine) instance >>> pop = ga_engine.get_population() Get the best fitness individual >>> bestIndividual = pop.bestFitness() Get the best raw individual >>> bestIndividual = pop.bestRaw() Get the statistics from the :class:`Statistics.Statistics` instance >>> stats = pop.getStatistics() >>> print stats["rawMax"] 10.4 Iterate, get/set individuals >>> for ind in pop: >>> print ind (...) >>> for i in xrange(len(pop)): >>> print pop[i] (...) >>> pop[10] = newGenome >>> pop[10].fitness 12.5 :param genome: the :term:`Sample genome`, or a GPopulation object, when cloning. """ def __init__(self, genome): """ The GPopulation Class creator """ if isinstance(genome, GPopulation): self.oneSelfGenome = genome.oneSelfGenome self.internalPop = [] self.internalPopRaw = [] self.popSize = genome.popSize self.sortType = genome.sortType self.sorted = False self.minimax = genome.minimax self.scaleMethod = genome.scaleMethod self.allSlots = [self.scaleMethod] self.internalParams = genome.internalParams self.multiProcessing = genome.multiProcessing self.statted = False self.stats = Statistics() return log.debug("New population instance, %s class genomes.", genome.__class__.__name__) self.oneSelfGenome = genome self.internalPop = [] self.internalPopRaw = [] self.popSize = 0 self.sortType = constants.CDefPopSortType self.sorted = False self.minimax = constants.CDefPopMinimax self.scaleMethod = FunctionSlot("Scale Method") self.scaleMethod.set(constants.CDefPopScale) self.allSlots = [self.scaleMethod] self.internalParams = {} self.multiProcessing = (False, False, None) # Statistics self.statted = False self.stats = Statistics() # ----------------------------------------------------------------- def setMultiProcessing(self, flag=True, full_copy=False, max_processes=None): """ This function sets the flag to enable/disable the use of python multiprocessing module. Use this option when you have more than one core on your CPU and when your evaluation function is very slow. The parameter "full_copy" defines where the individual data should be copied back after the evaluation or not. This parameter is useful when you change the individual in the evaluation function. :param flag: True (default) or False :param full_copy: True or False (default) :param max_processes: None (default) or an integer value .. warning:: Use this option only when your evaluation function is slow, se you will get a good tradeoff between the process communication speed and the parallel evaluation. .. versionadded:: 0.6 The `setMultiProcessing` method. """ self.multiProcessing = (flag, full_copy, max_processes) # ----------------------------------------------------------------- def setMinimax(self, minimax): """ Sets the population minimax Example: >>> pop.setMinimax(Consts.minimaxType["maximize"]) :param minimax: the minimax type """ self.minimax = minimax # ----------------------------------------------------------------- def __repr__(self): """ Returns the string representation of the population """ ret = "- GPopulation\n" ret += "\tPopulation Size:\t %d\n" % (self.popSize,) ret += "\tSort Type:\t\t %s\n" % (constants.sortType.keys()[constants.sortType.values().index(self.sortType)].capitalize(),) ret += "\tMinimax Type:\t\t %s\n" % (constants.minimaxType.keys()[constants.minimaxType.values().index(self.minimax)].capitalize(),) for slot in self.allSlots: ret += "\t" + slot.__repr__() ret += "\n" ret += self.stats.__repr__() return ret # ----------------------------------------------------------------- def __len__(self): """ Return the length of population """ return len(self.internalPop) # ----------------------------------------------------------------- def __getitem__(self, key): """ Returns the specified individual from population """ return self.internalPop[key] # ----------------------------------------------------------------- def __iter__(self): """ Returns the iterator of the population """ return iter(self.internalPop) # ----------------------------------------------------------------- def __setitem__(self, key, value): """ Set an individual of population """ self.internalPop[key] = value self.clearFlags() # ----------------------------------------------------------------- def clearFlags(self): """ Clear the sorted and statted internal flags """ self.sorted = False self.statted = False # ----------------------------------------------------------------- def getStatistics(self): """ Return a Statistics class for statistics :rtype: the :class:`Statistics.Statistics` instance """ self.statistics() return self.stats # ----------------------------------------------------------------- def statistics(self): """ Do statistical analysis of population and set 'statted' to True """ if self.statted: return log.debug("Running statistical calculations") raw_sum = 0 len_pop = len(self) for ind in xrange(len_pop): raw_sum += self[ind].score self.stats["rawMax"] = max(self, key=key_raw_score).score self.stats["rawMin"] = min(self, key=key_raw_score).score self.stats["rawAve"] = raw_sum / float(len_pop) tmpvar = 0.0 for ind in xrange(len_pop): s = self[ind].score - self.stats["rawAve"] s *= s tmpvar += s tmpvar /= float((len(self) - 1)) try: self.stats["rawDev"] = math_sqrt(tmpvar) except: self.stats["rawDev"] = 0.0 self.stats["rawVar"] = tmpvar self.statted = True # ----------------------------------------------------------------- def bestFitness(self, index=0): """ Return the best scaled fitness individual of population :param index: the *index* best individual :rtype: the individual """ self.sort() return self.internalPop[index] # ----------------------------------------------------------------- def worstFitness(self): """ Return the worst scaled fitness individual of the population :rtype: the individual """ self.sort() return self.internalPop[-1] # ----------------------------------------------------------------- def bestRaw(self, index=0): """ Return the best raw score individual of population :param index: the *index* best raw individual :rtype: the individual .. versionadded:: 0.6 The parameter `index`. """ if self.sortType == constants.sortType["raw"]: return self.internalPop[index] else: self.sort() return self.internalPopRaw[index] # ----------------------------------------------------------------- def worstRaw(self): """ Return the worst raw score individual of population :rtype: the individual .. versionadded:: 0.6 The parameter `index`. """ if self.sortType == constants.sortType["raw"]: return self.internalPop[-1] else: self.sort() return self.internalPopRaw[-1] # ----------------------------------------------------------------- def sort(self): """ Sort the population """ if self.sorted: return rev = (self.minimax == constants.minimaxType["maximize"]) if self.sortType == constants.sortType["raw"]: self.internalPop.sort(cmp=utils.cmp_individual_raw, reverse=rev) else: self.scale() self.internalPop.sort(cmp=utils.cmp_individual_scaled, reverse=rev) self.internalPopRaw = self.internalPop[:] self.internalPopRaw.sort(cmp=utils.cmp_individual_raw, reverse=rev) self.sorted = True # ----------------------------------------------------------------- def setPopulationSize(self, size): """ This function sets the population size :param size: the population size """ self.popSize = size # ----------------------------------------------------------------- def setSortType(self, sort_type): """ Sets the sort type Example: >>> pop.setSortType(Consts.sortType["scaled"]) :param sort_type: the Sort Type """ self.sortType = sort_type # ----------------------------------------------------------------- def create(self, **args): """ Clone the example genome to fill the population """ self.minimax = args["minimax"] self.internalPop = [self.oneSelfGenome.clone() for i in xrange(self.popSize)] self.clearFlags() # ----------------------------------------------------------------- def __findIndividual(self, individual, end): """ This function ... :param individual: :param end: :return: """ for i in xrange(end): if individual.compare(self.internalPop[i]) == 0: return True # ----------------------------------------------------------------- def initialize(self, **args): """ Initialize all individuals of population, this calls the initialize() of individuals """ # Inform the user log.info("Initializing the population ...") if self.oneSelfGenome.getParam("full_diversity", True) and hasattr(self.oneSelfGenome, "compare"): for i in xrange(len(self.internalPop)): curr = self.internalPop[i] curr.initialize(**args) while self.__findIndividual(curr, i): curr.initialize(**args) else: for gen in self.internalPop: gen.initialize(**args) self.clearFlags() # ----------------------------------------------------------------- def evaluate(self, **args): """ Evaluate all individuals in population, calls the evaluate() method of individuals :param args: this params are passed to the evaluation function """ # Inform the user log.info("Evaluating the new population ...") # We have multiprocessing if self.multiProcessing[0] and MULTI_PROCESSING: log.debug("Evaluating the population using the multiprocessing method") proc_pool = Pool(processes=self.multiProcessing[2]) # Multiprocessing full_copy parameter if self.multiProcessing[1]: results = proc_pool.map(multiprocessing_eval_full, self.internalPop) proc_pool.close() proc_pool.join() for i in xrange(len(self.internalPop)): self.internalPop[i] = results[i] else: results = proc_pool.map(multiprocessing_eval, self.internalPop) proc_pool.close() proc_pool.join() for individual, score in zip(self.internalPop, results): individual.score = score else: # No multiprocessing: basically just a loop over evaluate() of the individuals # Evaluate each individual for ind in self.internalPop: ind.evaluate(**args) self.clearFlags() # ----------------------------------------------------------------- def scale(self, **args): """ Scale the population using the scaling method :param args: this parameter is passed to the scale method """ for it in self.scaleMethod.applyFunctions(self, **args): pass fit_sum = 0 for ind in xrange(len(self)): fit_sum += self[ind].fitness self.stats["fitMax"] = max(self, key=key_fitness_score).fitness self.stats["fitMin"] = min(self, key=key_fitness_score).fitness self.stats["fitAve"] = fit_sum / float(len(self)) self.sorted = False # ----------------------------------------------------------------- def printStats(self): """ Print statistics of the current population """ message = "" if self.sortType == constants.sortType["scaled"]: message = "Max/Min/Avg Fitness(Raw) [%(fitMax).2f(%(rawMax).2f)/%(fitMin).2f(%(rawMin).2f)/%(fitAve).2f(%(rawAve).2f)]" % self.stats else: message = "Max/Min/Avg Raw [%(rawMax).2f/%(rawMin).2f/%(rawAve).2f]" % self.stats log.info(message) print message return message # ----------------------------------------------------------------- def copy(self, pop): """ Copy current population to 'pop' :param pop: the destination population .. warning:: this method do not copy the individuals, only the population logic """ pop.popSize = self.popSize pop.sortType = self.sortType pop.minimax = self.minimax pop.scaleMethod = self.scaleMethod pop.internalParams = self.internalParams pop.multiProcessing = self.multiProcessing # ----------------------------------------------------------------- def getParam(self, key, nvl=None): """ Gets an internal parameter Example: >>> population.getParam("tournamentPool") 5 :param key: the key of param :param nvl: if the key doesn't exist, the nvl will be returned """ return self.internalParams.get(key, nvl) # ----------------------------------------------------------------- def setParams(self, **args): """ Gets an internal parameter Example: >>> population.setParams(tournamentPool=5) :param args: parameters to set .. versionadded:: 0.6 The `setParams` method. """ self.internalParams.update(args) # ----------------------------------------------------------------- def clear(self): """ Remove all individuals from population """ del self.internalPop[:] del self.internalPopRaw[:] self.clearFlags() # ----------------------------------------------------------------- def clone(self): """ Return a brand-new cloned population """ newpop = GPopulation(self.oneSelfGenome) self.copy(newpop) return newpop
class GenomeBase(object): """ GenomeBase Class - The base of all chromosome representation """ __slots__ = ["evaluator", "initializator", "mutator", "crossover", "internalParams", "score", "fitness"] def __init__(self): """ Genome Constructor """ self.evaluator = FunctionSlot("Evaluator") self.initializator = FunctionSlot("Initializator") self.mutator = FunctionSlot("Mutator") self.crossover = FunctionSlot("Crossover") self.internalParams = {} self.score = 0.0 self.fitness = 0.0 # ----------------------------------------------------------------- def getRawScore(self): """ Get the Raw Score of the genome :rtype: genome raw score """ return self.score # ----------------------------------------------------------------- def getFitnessScore(self): """ Get the Fitness Score of the genome :rtype: genome fitness score """ return self.fitness # ----------------------------------------------------------------- def __repr__(self): """String representation of Genome""" allSlots = [self.evaluator, self.initializator, self.mutator, self.crossover] ret = "- GenomeBase\n" ret += "\tScore:\t\t\t %.6f\n" % (self.score,) ret += "\tFitness:\t\t %.6f\n\n" % (self.fitness,) ret += "\tParams:\t\t %s\n\n" % (self.internalParams,) for slot in allSlots: ret += "\t" + slot.__repr__() ret += "\n" return ret # ----------------------------------------------------------------- def setParams(self, **args): """ Set the internal params Example: >>> genome.setParams(rangemin=0, rangemax=100, gauss_mu=0, gauss_sigma=1) .. note:: All the individuals of the population shares this parameters and uses the same instance of this dict. :param args: this params will saved in every chromosome for genetic op. use """ self.internalParams.update(args) # ----------------------------------------------------------------- def getParam(self, key, nvl=None): """ Gets an internal parameter Example: >>> genome.getParam("rangemax") 100 .. note:: All the individuals of the population shares this parameters and uses the same instance of this dict. :param key: the key of param :param nvl: if the key doesn't exist, the nvl will be returned """ return self.internalParams.get(key, nvl) # ----------------------------------------------------------------- def resetStats(self): """ Clear score and fitness of genome """ self.score = 0.0 self.fitness = 0.0 # ----------------------------------------------------------------- def evaluate(self, **args): """ Called to evaluate genome :param args: this parameters will be passes to the evaluator """ self.resetStats() for it in self.evaluator.applyFunctions(self, **args): self.score += it # ----------------------------------------------------------------- def initialize(self, **args): """ Called to initialize genome :param args: this parameters will be passed to the initializator """ for it in self.initializator.applyFunctions(self, **args): pass # ----------------------------------------------------------------- def mutate(self, **args): """ Called to mutate the genome :param args: this parameters will be passed to the mutator :rtype: the number of mutations returned by mutation operator """ nmuts = 0 for it in self.mutator.applyFunctions(self, **args): nmuts += it return nmuts # ----------------------------------------------------------------- def copy(self, g): """ Copy the current GenomeBase to 'g' :param g: the destination genome .. note:: If you are planning to create a new chromosome representation, you **must** implement this method on your class. """ g.score = self.score g.fitness = self.fitness g.evaluator = self.evaluator g.initializator = self.initializator g.mutator = self.mutator g.crossover = self.crossover g.internalParams = self.internalParams # ----------------------------------------------------------------- def clone(self): """ Clone this GenomeBase :rtype: the clone genome .. note:: If you are planning to create a new chromosome representation, you **must** implement this method on your class. """ newcopy = GenomeBase() self.copy(newcopy) return newcopy
class GAEngine(object): """ This class represents the Genetic Algorithm Engine Example: >>> ga = GAEngine(genome) >>> ga.selector.set(Selectors.GRouletteWheel) >>> ga.setGenerations(120) >>> ga.terminationCriteria.set(GSimpleGA.ConvergenceCriteria) :param genome: the :term:`Sample Genome` :param interactiveMode: this flag enables the Interactive Mode, the default is True .. note:: if you use the same random seed, all the runs of algorithm will be the same """ selector = None """ This is the function slot for the selection method if you want to change the default selector, you must do this: :: ga_engine.selector.set(Selectors.GRouletteWheel) """ stepCallback = None """ This is the :term:`step callback function` slot, if you want to set the function, you must do this: :: def your_func(ga_engine): # Here you have access to the GA Engine return False ga_engine.stepCallback.set(your_func) now *"your_func"* will be called every generation. When this function returns True, the GA Engine will stop the evolution and show a warning, if False, the evolution continues. """ terminationCriteria = None """ This is the termination criteria slot, if you want to set one termination criteria, you must do this: :: ga_engine.terminationCriteria.set(GSimpleGA.ConvergenceCriteria) Now, when you run your GA, it will stop when the population converges. There are those termination criteria functions: :func:`GSimpleGA.RawScoreCriteria`, :func:`GSimpleGA.ConvergenceCriteria`, :func:`GSimpleGA.RawStatsCriteria`, :func:`GSimpleGA.FitnessStatsCriteria` But you can create your own termination function, this function receives one parameter which is the GA Engine, follows an example: :: def ConvergenceCriteria(ga_engine): pop = ga_engine.get_population() return pop[0] == pop[len(pop)-1] When this function returns True, the GA Engine will stop the evolution and show a warning, if False, the evolution continues, this function is called every generation. """ def __init__(self, genome, interactiveMode=True): """ Initializator of GSimpleGA """ #if seed is not None: random.seed(seed) # used to be like this if type(interactiveMode) != BooleanType: utils.raiseException( "Interactive Mode option must be True or False", TypeError) if not isinstance(genome, GenomeBase): utils.raiseException("The genome must be a GenomeBase subclass", TypeError) self.internalPop = GPopulation(genome) self.nGenerations = constants.CDefGAGenerations self.pMutation = constants.CDefGAMutationRate self.pCrossover = constants.CDefGACrossoverRate self.nElitismReplacement = constants.CDefGAElitismReplacement self.setPopulationSize(constants.CDefGAPopulationSize) self.minimax = constants.minimaxType["maximize"] self.elitism = True # NEW self.new_population = None # Adapters self.dbAdapter = None self.migrationAdapter = None self.time_init = None self.max_time = None self.interactiveMode = interactiveMode self.interactiveGen = -1 self.GPMode = False self.selector = FunctionSlot("Selector") self.stepCallback = FunctionSlot("Generation Step Callback") self.terminationCriteria = FunctionSlot("Termination Criteria") self.selector.set(constants.CDefGASelector) self.allSlots = (self.selector, self.stepCallback, self.terminationCriteria) self.internalParams = {} self.currentGeneration = 0 # GP Testing for classes in constants.CDefGPGenomes: if isinstance(self.internalPop.oneSelfGenome, classes): self.setGPMode(True) break log.debug("A GA Engine was created, nGenerations=%d", self.nGenerations) # New self.path = None # ----------------------------------------------------------------- @classmethod def from_file(cls, path): """ This function ... :param path: :return: """ # Inform the user log.info("Loading the genetic algorithm engine from '" + path + "' ...") # Load the GA object from file ga = serialization.load(path) # Set the path of the GA file ga.path = path # Return the GA return ga # ----------------------------------------------------------------- def save(self): """ This function ... :return: """ # Save to the current path self.saveto(self.path) # ----------------------------------------------------------------- def saveto(self, path): """ This function ... :param path: :return: """ # Inform the user log.info("Saving the genetic algorithm engine to '" + path + "' ...") # Set the new path as the current path and save self.path = path serialization.dump(self, path, protocol=2) # ----------------------------------------------------------------- def setGPMode(self, bool_value): """ Sets the Genetic Programming mode of the GA Engine :param bool_value: True or False """ self.GPMode = bool_value # ----------------------------------------------------------------- def getGPMode(self): """ Get the Genetic Programming mode of the GA Engine :rtype: True or False """ return self.GPMode # ----------------------------------------------------------------- def __call__(self, *args, **kwargs): """ A method to implement a callable object Example: >>> ga_engine(freq_stats=10) .. versionadded:: 0.6 The callable method. """ if kwargs.get("freq_stats", None): return self.evolve(kwargs.get("freq_stats")) else: return self.evolve() # ----------------------------------------------------------------- def setParams(self, **args): """ Set the internal params Example: >>> ga.setParams(gp_terminals=['x', 'y']) :param args: params to save ..versionaddd:: 0.6 Added the *setParams* method. """ self.internalParams.update(args) # ----------------------------------------------------------------- def getParam(self, key, nvl=None): """ Gets an internal parameter Example: >>> ga.getParam("gp_terminals") ['x', 'y'] :param key: the key of param :param nvl: if the key doesn't exist, the nvl will be returned ..versionaddd:: 0.6 Added the *getParam* method. """ return self.internalParams.get(key, nvl) # ----------------------------------------------------------------- def setInteractiveGeneration(self, generation): """ Sets the generation in which the GA must enter in the Interactive Mode :param generation: the generation number, use "-1" to disable .. versionadded::0.6 The *setInteractiveGeneration* method. """ if generation < -1: utils.raiseException("Generation must be >= -1", ValueError) self.interactiveGen = generation # ----------------------------------------------------------------- def getInteractiveGeneration(self): """ returns the generation in which the GA must enter in the Interactive Mode :rtype: the generation number or -1 if not set .. versionadded::0.6 The *getInteractiveGeneration* method. """ return self.interactiveGen # ----------------------------------------------------------------- def setElitismReplacement(self, numreplace): """ Set the number of best individuals to copy to the next generation on the elitism :param numreplace: the number of individuals .. versionadded:: 0.6 The *setElitismReplacement* method. """ if numreplace < 1: utils.raiseException("Replacement number must be >= 1", ValueError) self.nElitismReplacement = numreplace # ----------------------------------------------------------------- def setInteractiveMode(self, flag=True): """ Enable/disable the interactive mode :param flag: True or False .. versionadded: 0.6 The *setInteractiveMode* method. """ if type(flag) != BooleanType: utils.raiseException( "Interactive Mode option must be True or False", TypeError) self.interactiveMode = flag # ----------------------------------------------------------------- def __repr__(self): """ The string representation of the GA Engine """ minimax_type = constants.minimaxType.keys()[ constants.minimaxType.values().index(self.minimax)] ret = "- GSimpleGA\n" ret += "\tGP Mode:\t\t %s\n" % self.getGPMode() ret += "\tPopulation Size:\t %d\n" % self.internalPop.popSize ret += "\tGenerations:\t\t %d\n" % self.nGenerations ret += "\tCurrent Generation:\t %d\n" % self.currentGeneration ret += "\tMutation Rate:\t\t %.2f\n" % self.pMutation ret += "\tCrossover Rate:\t\t %.2f\n" % self.pCrossover ret += "\tMinimax Type:\t\t %s\n" % minimax_type.capitalize() ret += "\tElitism:\t\t %s\n" % self.elitism ret += "\tElitism Replacement:\t %d\n" % self.nElitismReplacement ret += "\tDB Adapter:\t\t %s\n" % self.dbAdapter for slot in self.allSlots: ret += "\t" + slot.__repr__() ret += "\n" # Return the string return ret # ----------------------------------------------------------------- def setMultiProcessing(self, flag=True, full_copy=False, max_processes=None): """ Sets the flag to enable/disable the use of python multiprocessing module. Use this option when you have more than one core on your CPU and when your evaluation function is very slow. Pyevolve will automaticly check if your Python version has **multiprocessing** support and if you have more than one single CPU core. If you don't have support or have just only one core, Pyevolve will not use the **multiprocessing** feature. Pyevolve uses the **multiprocessing** to execute the evaluation function over the individuals, so the use of this feature will make sense if you have a truly slow evaluation function (which is commom in GAs). The parameter "full_copy" defines where the individual data should be copied back after the evaluation or not. This parameter is useful when you change the individual in the evaluation function. :param flag: True (default) or False :param full_copy: True or False (default) :param max_processes: None (default) or an integer value .. warning:: Use this option only when your evaluation function is slow, so you'll get a good tradeoff between the process communication speed and the parallel evaluation. The use of the **multiprocessing** doesn't means always a better performance. .. note:: To enable the multiprocessing option, you **MUST** add the *__main__* check on your application, otherwise, it will result in errors. See more on the `Python Docs <http://docs.python.org/library/multiprocessing.html#multiprocessing-programming>`__ site. .. versionadded:: 0.6 The `setMultiProcessing` method. """ if type(flag) != BooleanType: utils.raiseException( "Multiprocessing option must be True or False", TypeError) if type(full_copy) != BooleanType: utils.raiseException( "Multiprocessing 'full_copy' option must be True or False", TypeError) self.internalPop.setMultiProcessing(flag, full_copy, max_processes) # ----------------------------------------------------------------- def setMigrationAdapter(self, migration_adapter=None): """ Sets the Migration Adapter .. versionadded:: 0.6 The `setMigrationAdapter` method. """ self.migrationAdapter = migration_adapter if self.migrationAdapter is not None: self.migrationAdapter.setGAEngine(self) # ----------------------------------------------------------------- def setDBAdapter(self, dbadapter=None): """ Sets the DB Adapter of the GA Engine :param dbadapter: one of the :mod:`DBAdapters` classes instance .. warning:: the use the of a DB Adapter can reduce the speed performance of the Genetic Algorithm. """ if (dbadapter is not None) and (not isinstance(dbadapter, DBBaseAdapter)): utils.raiseException( "The DB Adapter must be a DBBaseAdapter subclass", TypeError) self.dbAdapter = dbadapter # ----------------------------------------------------------------- def setPopulationSize(self, size): """ Sets the population size, calls setPopulationSize() of GPopulation :param size: the population size .. note:: the population size must be >= 2 """ if size < 2: utils.raiseException("population size must be >= 2", ValueError) self.internalPop.setPopulationSize(size) # ----------------------------------------------------------------- def setSortType(self, sort_type): """ Sets the sort type, constants.sortType["raw"]/constants.sortType["scaled"] Example: >>> ga_engine.setSortType(constants.sortType["scaled"]) :param sort_type: the Sort Type """ if sort_type not in constants.sortType.values(): utils.raiseException("sort type must be a constants.sortType type", TypeError) self.internalPop.sortType = sort_type # ----------------------------------------------------------------- def setMutationRate(self, rate): """ Sets the mutation rate, between 0.0 and 1.0 :param rate: the rate, between 0.0 and 1.0 """ if (rate > 1.0) or (rate < 0.0): utils.raiseException("Mutation rate must be >= 0.0 and <= 1.0", ValueError) self.pMutation = rate # ----------------------------------------------------------------- def setCrossoverRate(self, rate): """ Sets the crossover rate, between 0.0 and 1.0 :param rate: the rate, between 0.0 and 1.0 """ if (rate > 1.0) or (rate < 0.0): utils.raiseException("Crossover rate must be >= 0.0 and <= 1.0", ValueError) self.pCrossover = rate # ----------------------------------------------------------------- def setGenerations(self, num_gens): """ Sets the number of generations to evolve :param num_gens: the number of generations """ if num_gens < 1: utils.raiseException("Number of generations must be >= 1", ValueError) self.nGenerations = num_gens # ----------------------------------------------------------------- def getGenerations(self): """ Return the number of generations to evolve :rtype: the number of generations .. versionadded:: 0.6 Added the *getGenerations* method """ return self.nGenerations # ----------------------------------------------------------------- def getMinimax(self): """ Gets the minimize/maximize mode :rtype: the constants.minimaxType type """ return self.minimax # ----------------------------------------------------------------- def setMinimax(self, mtype): """ Sets the minimize/maximize mode, use constants.minimaxType :param mtype: the minimax mode, from constants.minimaxType """ if mtype not in constants.minimaxType.values(): utils.raiseException("Minimax must be maximize or minimize", TypeError) self.minimax = mtype # ----------------------------------------------------------------- def getCurrentGeneration(self): """ Gets the current generation :rtype: the current generation """ return self.currentGeneration # ----------------------------------------------------------------- def setElitism(self, flag): """ Sets the elitism option, True or False :param flag: True or False """ if type(flag) != BooleanType: utils.raiseException("Elitism option must be True or False", TypeError) self.elitism = flag # ----------------------------------------------------------------- def getDBAdapter(self): """ Gets the DB Adapter of the GA Engine :rtype: a instance from one of the :mod:`DBAdapters` classes """ return self.dbAdapter # ----------------------------------------------------------------- def setMaxTime(self, seconds): """ Sets the maximun evolve time of the GA Engine :param seconds: maximum time in seconds """ self.max_time = seconds # ----------------------------------------------------------------- def getMaxTime(self): """ Get the maximun evolve time of the GA Engine :rtype: True or False """ return self.max_time # ----------------------------------------------------------------- def bestIndividual(self): """ Returns the population best individual :rtype: the best individual """ return self.internalPop.bestRaw() # ----------------------------------------------------------------- def worstIndividual(self): """ Returns the population worst individual :rtype: the best individual """ return self.internalPop.worstRaw() # ----------------------------------------------------------------- def __gp_catch_functions(self, prefix): """ Internally used to catch functions with some specific prefix as non-terminals of the GP core """ import __main__ as mod_main function_set = {} main_dict = mod_main.__dict__ for obj, addr in main_dict.items(): if obj[0:len(prefix)] == prefix: try: op_len = addr.func_code.co_argcount except: continue function_set[obj] = op_len if len(function_set) <= 0: utils.raiseException( "No function set found using function prefix '%s' !" % prefix, ValueError) self.setParams(gp_function_set=function_set) # ----------------------------------------------------------------- def initialize(self): """ This function initializes the GA Engine. Create and initialize the first population """ # Inform the user log.info("Initializing the GA engine ...") # Keep track of the time passed self.time_init = time() # Create the first population self.internalPop.create(minimax=self.minimax) # Initialize the population (initializes all individuals of the population) self.internalPop.initialize(ga_engine=self) # ----------------------------------------------------------------- def set_scores(self, scores, check=None): """ This function ... :param scores: :param check: :return: """ # Set the scores for the initial population if self.is_initial_generation: self.set_scores_for_population(self.internalPop, scores, check) # Set the scores for the new population else: # Set scores self.set_scores_for_population(self.new_population, scores, check) # Replace if self.new_population is not None: self.replace_internal_population() # Increment the current generation number self.currentGeneration += 1 # Sort the internal population self.internalPop.sort() # Set new pop to None self.new_population = None # ----------------------------------------------------------------- def set_scores_for_population(self, population, scores, check=None): """ This function ... :param population: :param scores: :param check: :return: """ index = 0 for individual in population: if check is not None: # Get the parametr values for this individual parameter_a = individual.genomeList[0] parameter_b = individual.genomeList[1] parameter_a_check = check["Parameter a"][index] parameter_b_check = check["Parameter b"][index] rel_diff_a = abs( (parameter_a - parameter_a_check) / parameter_a) rel_diff_b = abs( (parameter_b - parameter_b_check) / parameter_b) assert np.isclose(parameter_a, parameter_a_check, rtol=1e-11), rel_diff_a assert np.isclose(parameter_b, parameter_b_check, rtol=1e-11), rel_diff_b # Set the score individual.score = scores[index] # Increment the index index += 1 # ----------------------------------------------------------------- def get_population(self): """ Return the internal population of GA Engine :rtype: the population (:class:`GPopulation.GPopulation`) """ return self.internalPop # ----------------------------------------------------------------- def getStatistics(self): """ Gets the Statistics class instance of current generation :rtype: the statistics instance (:class:`Statistics.Statistics`) """ return self.internalPop.getStatistics() # ----------------------------------------------------------------- def generate_new_population(self): """ This function ... :return: """ # Inform the user log.info("Creating generation " + str(self.currentGeneration) + " ...") # Clone the current internal population newPop = GPopulation(self.internalPop) log.debug("Population was cloned.") size_iterate = len(self.internalPop) # Odd population size if size_iterate % 2 != 0: size_iterate -= 1 crossover_empty = self.select( popID=self.currentGeneration).crossover.isEmpty() for i in xrange(0, size_iterate, 2): genomeMom = self.select(popID=self.currentGeneration) genomeDad = self.select(popID=self.currentGeneration) if not crossover_empty and self.pCrossover >= 1.0: for it in genomeMom.crossover.applyFunctions(mom=genomeMom, dad=genomeDad, count=2): (sister, brother) = it else: if not crossover_empty and utils.randomFlipCoin( self.pCrossover): for it in genomeMom.crossover.applyFunctions(mom=genomeMom, dad=genomeDad, count=2): (sister, brother) = it else: sister = genomeMom.clone() brother = genomeDad.clone() sister.mutate(pmut=self.pMutation, ga_engine=self) brother.mutate(pmut=self.pMutation, ga_engine=self) newPop.internalPop.append(sister) newPop.internalPop.append(brother) if len(self.internalPop) % 2 != 0: genomeMom = self.select(popID=self.currentGeneration) genomeDad = self.select(popID=self.currentGeneration) if utils.randomFlipCoin(self.pCrossover): for it in genomeMom.crossover.applyFunctions(mom=genomeMom, dad=genomeDad, count=1): (sister, brother) = it else: sister = prng.choice([genomeMom, genomeDad]) sister = sister.clone() sister.mutate(pmut=self.pMutation, ga_engine=self) newPop.internalPop.append(sister) # Return the new population #return newPop # NEW self.new_population = newPop # ----------------------------------------------------------------- def step(self): """ This function performs one step in the evolution, i.e. one generation """ # Inform the user log.info("Performing step in the evolutionary process ...") # Generate the new population ## NEW self.generate_new_population() # Evaluate print(self.generation_description) self.new_population.evaluate() # Replace population self.replace_internal_population() # Sort the population self.internalPop.sort() # Set new population to None self.new_population = None # Inform the user #log.success("The generation %d was finished.", self.currentGeneration) # Increment the current generation number self.currentGeneration += 1 if self.max_time: total_time = time() - self.time_init if total_time > self.max_time: return True return self.currentGeneration == self.nGenerations # ----------------------------------------------------------------- def replace_internal_population(self): """ This function ... :return: """ # Elitism: if self.elitism: self.do_elitism(self.new_population) # Set the new population as the internal population and sort it self.internalPop = self.new_population # ----------------------------------------------------------------- def do_elitism(self, new_population): """ This function ... :param new_population: :return: """ # Elitism concept has different meanings for metaheuristic and particular for GA. In general, elitism # is related with memory: "remember the best solution found" (kind of greedying). In the most traditional way, # for evolutionary algorithms (GA, EA, DE...), elitism implies the best solution found is used for to build the # next generation. Particularly, for GA it means keeping the best individual found intact for next generation. # In GA, there is something like a proof for multiobjective optimization (villalobos, coello and hernandez, 2005) # that linked convergence and elitism which is a kind of cool stuff. # # Elitism involves copying a small proportion of the fittest candidates, unchanged, into the next generation. # This can sometimes have a dramatic impact on performance by ensuring that the EA does not waste time # re-discovering previously discarded partial solutions. Candidate solutions that are preserved unchanged # through elitism remain eligible for selection as parents when breeding the remainder of the next generation. log.debug("Doing elitism ...") if self.getMinimax() == constants.minimaxType["maximize"]: for i in xrange(self.nElitismReplacement): ##re-evaluate before being sure this is the best # self.internalPop.bestRaw(i).evaluate() # IS THIS REALLY NECESSARY ? if self.internalPop.bestRaw(i).score > new_population.bestRaw( i).score: new_population[len(new_population) - 1 - i] = self.internalPop.bestRaw(i) elif self.getMinimax() == constants.minimaxType["minimize"]: for i in xrange(self.nElitismReplacement): ##re-evaluate before being sure this is the best # self.internalPop.bestRaw(i).evaluate() # IS THIS REALLY NECESSARY ? if self.internalPop.bestRaw(i).score < new_population.bestRaw( i).score: new_population[len(new_population) - 1 - i] = self.internalPop.bestRaw(i) # ----------------------------------------------------------------- def printStats(self): """ Print generation statistics :rtype: the printed statistics as string .. versionchanged:: 0.6 The return of *printStats* method. """ percent = self.currentGeneration * 100 / float(self.nGenerations) message = "Gen. %d (%.2f%%):" % (self.currentGeneration, percent) log.info(message) print(message, ) sys_stdout.flush() self.internalPop.statistics() stat_ret = self.internalPop.printStats() return message + stat_ret # ----------------------------------------------------------------- def printTimeElapsed(self): """ Shows the time elapsed since the begin of evolution """ total_time = time() - self.time_init print("Total time elapsed: %.3f seconds." % total_time) return total_time # ----------------------------------------------------------------- def dumpStatsDB(self): """ Dumps the current statistics to database adapter """ log.debug("Dumping stats to the DB Adapter") self.internalPop.statistics() self.dbAdapter.insert(self) # ----------------------------------------------------------------- def evolve(self, freq_stats=0): """ Do all the generations until the termination criteria, accepts the freq_stats (default is 0) to dump statistics at n-generation Example: >>> ga_engine.evolve(freq_stats=10) (...) :param freq_stats: if greater than 0, the statistics will be printed every freq_stats generation. :rtype: returns the best individual of the evolution .. versionadded:: 0.6 the return of the best individual """ # Initialize self.initialize_evolution() # Do the evolution loop self.evolve_loop(freq_stats) # Finish evolution, return best individual return self.finish_evolution() # ----------------------------------------------------------------- @property def is_initial_generation(self): """ This function ... :return: """ if self.new_population is None: if self.currentGeneration > 0: raise RuntimeError( "Inconsistent state: 'new_population' does exist but 'currentGeneration' >0" ) return True else: return False # ----------------------------------------------------------------- @property def generation_description(self): """ This function ... :return: """ if self.is_initial_generation: return "Initial generation" else: return "Generation " + str(self.currentGeneration) # ----------------------------------------------------------------- def initialize_evolution(self): """ This function ... :return: """ self.initialize() # Inform the user ... log.info("Evaluating and sorting the initial population ...") # Evaluate and sort the internal population self.internalPop.evaluate() self.sort_internal_population() # ----------------------------------------------------------------- def sort_internal_population(self): """ This function ... :return: """ self.internalPop.sort() # ----------------------------------------------------------------- def evolve_loop(self, freq_stats): """ This function ... :return: """ log.debug("Starting loop over evolutionary algorithm.") while True: if not self.evolve_generation(freq_stats): break # ----------------------------------------------------------------- def finish_evolution(self, silent=True): """ This function ... :return: """ if not silent: self.printStats() self.printTimeElapsed() if self.dbAdapter: log.debug("Closing the DB Adapter") if not (self.currentGeneration % self.dbAdapter.getStatsGenFreq() == 0): self.dumpStatsDB() self.dbAdapter.commitAndClose() if self.migrationAdapter: log.debug("Closing the Migration Adapter") self.migrationAdapter.stop() return self.bestIndividual() # ----------------------------------------------------------------- def evolve_generation(self, freq_stats): """ This function ... :param freq_stats: :return: """ stopFlagCallback = False stopFlagTerminationCriteria = False if self.migrationAdapter: log.debug("Migration adapter: exchange") self.migrationAdapter.exchange() self.internalPop.clearFlags() self.internalPop.sort() if not self.stepCallback.isEmpty(): for it in self.stepCallback.applyFunctions(self): stopFlagCallback = it if not self.terminationCriteria.isEmpty(): for it in self.terminationCriteria.applyFunctions(self): stopFlagTerminationCriteria = it if freq_stats: if (self.currentGeneration % freq_stats == 0) or (self.getCurrentGeneration() == 0): self.printStats() if self.dbAdapter: if self.currentGeneration % self.dbAdapter.getStatsGenFreq() == 0: self.dumpStatsDB() if stopFlagTerminationCriteria: log.debug("Evolution stopped by the Termination Criteria !") if freq_stats: print( "\n\tEvolution stopped by Termination Criteria function !\n" ) return False if stopFlagCallback: log.debug("Evolution stopped by Step Callback function !") if freq_stats: print("\n\tEvolution stopped by Step Callback function !\n") return False if self.step(): return False return True # ----------------------------------------------------------------- def select(self, **args): """ Select one individual from population :param args: this parameters will be sent to the selector """ for it in self.selector.applyFunctions(self.internalPop, **args): return it
class GAEngine(object): """ This class represents the Genetic Algorithm Engine Example: >>> ga = GAEngine(genome) >>> ga.selector.set(Selectors.GRouletteWheel) >>> ga.setGenerations(120) >>> ga.terminationCriteria.set(GSimpleGA.ConvergenceCriteria) :param genome: the :term:`Sample Genome` :param interactiveMode: this flag enables the Interactive Mode, the default is True .. note:: if you use the same random seed, all the runs of algorithm will be the same """ selector = None """ This is the function slot for the selection method if you want to change the default selector, you must do this: :: ga_engine.selector.set(Selectors.GRouletteWheel) """ stepCallback = None """ This is the :term:`step callback function` slot, if you want to set the function, you must do this: :: def your_func(ga_engine): # Here you have access to the GA Engine return False ga_engine.stepCallback.set(your_func) now *"your_func"* will be called every generation. When this function returns True, the GA Engine will stop the evolution and show a warning, if False, the evolution continues. """ terminationCriteria = None """ This is the termination criteria slot, if you want to set one termination criteria, you must do this: :: ga_engine.terminationCriteria.set(GSimpleGA.ConvergenceCriteria) Now, when you run your GA, it will stop when the population converges. There are those termination criteria functions: :func:`GSimpleGA.RawScoreCriteria`, :func:`GSimpleGA.ConvergenceCriteria`, :func:`GSimpleGA.RawStatsCriteria`, :func:`GSimpleGA.FitnessStatsCriteria` But you can create your own termination function, this function receives one parameter which is the GA Engine, follows an example: :: def ConvergenceCriteria(ga_engine): pop = ga_engine.get_population() return pop[0] == pop[len(pop)-1] When this function returns True, the GA Engine will stop the evolution and show a warning, if False, the evolution continues, this function is called every generation. """ def __init__(self, genome, interactiveMode=True): """ Initializator of GSimpleGA """ #if seed is not None: random.seed(seed) # used to be like this if type(interactiveMode) != BooleanType: utils.raiseException("Interactive Mode option must be True or False", TypeError) if not isinstance(genome, GenomeBase): utils.raiseException("The genome must be a GenomeBase subclass", TypeError) self.internalPop = GPopulation(genome) self.nGenerations = constants.CDefGAGenerations self.pMutation = constants.CDefGAMutationRate self.pCrossover = constants.CDefGACrossoverRate self.nElitismReplacement = constants.CDefGAElitismReplacement self.setPopulationSize(constants.CDefGAPopulationSize) self.minimax = constants.minimaxType["maximize"] self.elitism = True # NEW self.new_population = None # Adapters self.dbAdapter = None self.migrationAdapter = None self.time_init = None self.max_time = None self.interactiveMode = interactiveMode self.interactiveGen = -1 self.GPMode = False self.selector = FunctionSlot("Selector") self.stepCallback = FunctionSlot("Generation Step Callback") self.terminationCriteria = FunctionSlot("Termination Criteria") self.selector.set(constants.CDefGASelector) self.allSlots = (self.selector, self.stepCallback, self.terminationCriteria) self.internalParams = {} self.currentGeneration = 0 # GP Testing for classes in constants.CDefGPGenomes: if isinstance(self.internalPop.oneSelfGenome, classes): self.setGPMode(True) break log.debug("A GA Engine was created, nGenerations=%d", self.nGenerations) # New self.path = None # ----------------------------------------------------------------- @classmethod def from_file(cls, path): """ This function ... :param path: :return: """ # Inform the user log.info("Loading the genetic algorithm engine from '" + path + "' ...") # Load the GA object from file ga = serialization.load(path) # Set the path of the GA file ga.path = path # Return the GA return ga # ----------------------------------------------------------------- def save(self): """ This function ... :return: """ # Save to the current path self.saveto(self.path) # ----------------------------------------------------------------- def saveto(self, path): """ This function ... :param path: :return: """ # Inform the user log.info("Saving the genetic algorithm engine to '" + path + "' ...") # Set the new path as the current path and save self.path = path serialization.dump(self, path, protocol=2) # ----------------------------------------------------------------- def setGPMode(self, bool_value): """ Sets the Genetic Programming mode of the GA Engine :param bool_value: True or False """ self.GPMode = bool_value # ----------------------------------------------------------------- def getGPMode(self): """ Get the Genetic Programming mode of the GA Engine :rtype: True or False """ return self.GPMode # ----------------------------------------------------------------- def __call__(self, *args, **kwargs): """ A method to implement a callable object Example: >>> ga_engine(freq_stats=10) .. versionadded:: 0.6 The callable method. """ if kwargs.get("freq_stats", None): return self.evolve(kwargs.get("freq_stats")) else: return self.evolve() # ----------------------------------------------------------------- def setParams(self, **args): """ Set the internal params Example: >>> ga.setParams(gp_terminals=['x', 'y']) :param args: params to save ..versionaddd:: 0.6 Added the *setParams* method. """ self.internalParams.update(args) # ----------------------------------------------------------------- def getParam(self, key, nvl=None): """ Gets an internal parameter Example: >>> ga.getParam("gp_terminals") ['x', 'y'] :param key: the key of param :param nvl: if the key doesn't exist, the nvl will be returned ..versionaddd:: 0.6 Added the *getParam* method. """ return self.internalParams.get(key, nvl) # ----------------------------------------------------------------- def setInteractiveGeneration(self, generation): """ Sets the generation in which the GA must enter in the Interactive Mode :param generation: the generation number, use "-1" to disable .. versionadded::0.6 The *setInteractiveGeneration* method. """ if generation < -1: utils.raiseException("Generation must be >= -1", ValueError) self.interactiveGen = generation # ----------------------------------------------------------------- def getInteractiveGeneration(self): """ returns the generation in which the GA must enter in the Interactive Mode :rtype: the generation number or -1 if not set .. versionadded::0.6 The *getInteractiveGeneration* method. """ return self.interactiveGen # ----------------------------------------------------------------- def setElitismReplacement(self, numreplace): """ Set the number of best individuals to copy to the next generation on the elitism :param numreplace: the number of individuals .. versionadded:: 0.6 The *setElitismReplacement* method. """ if numreplace < 1: utils.raiseException("Replacement number must be >= 1", ValueError) self.nElitismReplacement = numreplace # ----------------------------------------------------------------- def setInteractiveMode(self, flag=True): """ Enable/disable the interactive mode :param flag: True or False .. versionadded: 0.6 The *setInteractiveMode* method. """ if type(flag) != BooleanType: utils.raiseException("Interactive Mode option must be True or False", TypeError) self.interactiveMode = flag # ----------------------------------------------------------------- def __repr__(self): """ The string representation of the GA Engine """ minimax_type = constants.minimaxType.keys()[constants.minimaxType.values().index(self.minimax)] ret = "- GSimpleGA\n" ret += "\tGP Mode:\t\t %s\n" % self.getGPMode() ret += "\tPopulation Size:\t %d\n" % self.internalPop.popSize ret += "\tGenerations:\t\t %d\n" % self.nGenerations ret += "\tCurrent Generation:\t %d\n" % self.currentGeneration ret += "\tMutation Rate:\t\t %.2f\n" % self.pMutation ret += "\tCrossover Rate:\t\t %.2f\n" % self.pCrossover ret += "\tMinimax Type:\t\t %s\n" % minimax_type.capitalize() ret += "\tElitism:\t\t %s\n" % self.elitism ret += "\tElitism Replacement:\t %d\n" % self.nElitismReplacement ret += "\tDB Adapter:\t\t %s\n" % self.dbAdapter for slot in self.allSlots: ret += "\t" + slot.__repr__() ret += "\n" # Return the string return ret # ----------------------------------------------------------------- def setMultiProcessing(self, flag=True, full_copy=False, max_processes=None): """ Sets the flag to enable/disable the use of python multiprocessing module. Use this option when you have more than one core on your CPU and when your evaluation function is very slow. Pyevolve will automaticly check if your Python version has **multiprocessing** support and if you have more than one single CPU core. If you don't have support or have just only one core, Pyevolve will not use the **multiprocessing** feature. Pyevolve uses the **multiprocessing** to execute the evaluation function over the individuals, so the use of this feature will make sense if you have a truly slow evaluation function (which is commom in GAs). The parameter "full_copy" defines where the individual data should be copied back after the evaluation or not. This parameter is useful when you change the individual in the evaluation function. :param flag: True (default) or False :param full_copy: True or False (default) :param max_processes: None (default) or an integer value .. warning:: Use this option only when your evaluation function is slow, so you'll get a good tradeoff between the process communication speed and the parallel evaluation. The use of the **multiprocessing** doesn't means always a better performance. .. note:: To enable the multiprocessing option, you **MUST** add the *__main__* check on your application, otherwise, it will result in errors. See more on the `Python Docs <http://docs.python.org/library/multiprocessing.html#multiprocessing-programming>`__ site. .. versionadded:: 0.6 The `setMultiProcessing` method. """ if type(flag) != BooleanType: utils.raiseException("Multiprocessing option must be True or False", TypeError) if type(full_copy) != BooleanType: utils.raiseException("Multiprocessing 'full_copy' option must be True or False", TypeError) self.internalPop.setMultiProcessing(flag, full_copy, max_processes) # ----------------------------------------------------------------- def setMigrationAdapter(self, migration_adapter=None): """ Sets the Migration Adapter .. versionadded:: 0.6 The `setMigrationAdapter` method. """ self.migrationAdapter = migration_adapter if self.migrationAdapter is not None: self.migrationAdapter.setGAEngine(self) # ----------------------------------------------------------------- def setDBAdapter(self, dbadapter=None): """ Sets the DB Adapter of the GA Engine :param dbadapter: one of the :mod:`DBAdapters` classes instance .. warning:: the use the of a DB Adapter can reduce the speed performance of the Genetic Algorithm. """ if (dbadapter is not None) and (not isinstance(dbadapter, DBBaseAdapter)): utils.raiseException("The DB Adapter must be a DBBaseAdapter subclass", TypeError) self.dbAdapter = dbadapter # ----------------------------------------------------------------- def setPopulationSize(self, size): """ Sets the population size, calls setPopulationSize() of GPopulation :param size: the population size .. note:: the population size must be >= 2 """ if size < 2: utils.raiseException("population size must be >= 2", ValueError) self.internalPop.setPopulationSize(size) # ----------------------------------------------------------------- def setSortType(self, sort_type): """ Sets the sort type, constants.sortType["raw"]/constants.sortType["scaled"] Example: >>> ga_engine.setSortType(constants.sortType["scaled"]) :param sort_type: the Sort Type """ if sort_type not in constants.sortType.values(): utils.raiseException("sort type must be a constants.sortType type", TypeError) self.internalPop.sortType = sort_type # ----------------------------------------------------------------- def setMutationRate(self, rate): """ Sets the mutation rate, between 0.0 and 1.0 :param rate: the rate, between 0.0 and 1.0 """ if (rate > 1.0) or (rate < 0.0): utils.raiseException("Mutation rate must be >= 0.0 and <= 1.0", ValueError) self.pMutation = rate # ----------------------------------------------------------------- def setCrossoverRate(self, rate): """ Sets the crossover rate, between 0.0 and 1.0 :param rate: the rate, between 0.0 and 1.0 """ if (rate > 1.0) or (rate < 0.0): utils.raiseException("Crossover rate must be >= 0.0 and <= 1.0", ValueError) self.pCrossover = rate # ----------------------------------------------------------------- def setGenerations(self, num_gens): """ Sets the number of generations to evolve :param num_gens: the number of generations """ if num_gens < 1: utils.raiseException("Number of generations must be >= 1", ValueError) self.nGenerations = num_gens # ----------------------------------------------------------------- def getGenerations(self): """ Return the number of generations to evolve :rtype: the number of generations .. versionadded:: 0.6 Added the *getGenerations* method """ return self.nGenerations # ----------------------------------------------------------------- def getMinimax(self): """ Gets the minimize/maximize mode :rtype: the constants.minimaxType type """ return self.minimax # ----------------------------------------------------------------- def setMinimax(self, mtype): """ Sets the minimize/maximize mode, use constants.minimaxType :param mtype: the minimax mode, from constants.minimaxType """ if mtype not in constants.minimaxType.values(): utils.raiseException("Minimax must be maximize or minimize", TypeError) self.minimax = mtype # ----------------------------------------------------------------- def getCurrentGeneration(self): """ Gets the current generation :rtype: the current generation """ return self.currentGeneration # ----------------------------------------------------------------- def setElitism(self, flag): """ Sets the elitism option, True or False :param flag: True or False """ if type(flag) != BooleanType: utils.raiseException("Elitism option must be True or False", TypeError) self.elitism = flag # ----------------------------------------------------------------- def getDBAdapter(self): """ Gets the DB Adapter of the GA Engine :rtype: a instance from one of the :mod:`DBAdapters` classes """ return self.dbAdapter # ----------------------------------------------------------------- def setMaxTime(self, seconds): """ Sets the maximun evolve time of the GA Engine :param seconds: maximum time in seconds """ self.max_time = seconds # ----------------------------------------------------------------- def getMaxTime(self): """ Get the maximun evolve time of the GA Engine :rtype: True or False """ return self.max_time # ----------------------------------------------------------------- def bestIndividual(self): """ Returns the population best individual :rtype: the best individual """ return self.internalPop.bestRaw() # ----------------------------------------------------------------- def worstIndividual(self): """ Returns the population worst individual :rtype: the best individual """ return self.internalPop.worstRaw() # ----------------------------------------------------------------- def __gp_catch_functions(self, prefix): """ Internally used to catch functions with some specific prefix as non-terminals of the GP core """ import __main__ as mod_main function_set = {} main_dict = mod_main.__dict__ for obj, addr in main_dict.items(): if obj[0:len(prefix)] == prefix: try: op_len = addr.func_code.co_argcount except: continue function_set[obj] = op_len if len(function_set) <= 0: utils.raiseException("No function set found using function prefix '%s' !" % prefix, ValueError) self.setParams(gp_function_set=function_set) # ----------------------------------------------------------------- def initialize(self): """ This function initializes the GA Engine. Create and initialize the first population """ # Inform the user log.info("Initializing the GA engine ...") # Keep track of the time passed self.time_init = time() # Create the first population self.internalPop.create(minimax=self.minimax) # Initialize the population (initializes all individuals of the population) self.internalPop.initialize(ga_engine=self) # ----------------------------------------------------------------- def set_scores(self, scores, check=None): """ This function ... :param scores: :param check: :return: """ # Set the scores for the initial population if self.is_initial_generation: self.set_scores_for_population(self.internalPop, scores, check) # Set the scores for the new population else: # Set scores self.set_scores_for_population(self.new_population, scores, check) # Replace if self.new_population is not None: self.replace_internal_population() # Increment the current generation number self.currentGeneration += 1 # Sort the internal population self.internalPop.sort() # Set new pop to None self.new_population = None # ----------------------------------------------------------------- def set_scores_for_population(self, population, scores, check=None): """ This function ... :param population: :param scores: :param check: :return: """ index = 0 for individual in population: if check is not None: # Get the parametr values for this individual parameter_a = individual.genomeList[0] parameter_b = individual.genomeList[1] parameter_a_check = check["Parameter a"][index] parameter_b_check = check["Parameter b"][index] rel_diff_a = abs((parameter_a - parameter_a_check) / parameter_a) rel_diff_b = abs((parameter_b - parameter_b_check) / parameter_b) assert np.isclose(parameter_a, parameter_a_check, rtol=1e-11), rel_diff_a assert np.isclose(parameter_b, parameter_b_check, rtol=1e-11), rel_diff_b # Set the score individual.score = scores[index] # Increment the index index += 1 # ----------------------------------------------------------------- def get_population(self): """ Return the internal population of GA Engine :rtype: the population (:class:`GPopulation.GPopulation`) """ return self.internalPop # ----------------------------------------------------------------- def getStatistics(self): """ Gets the Statistics class instance of current generation :rtype: the statistics instance (:class:`Statistics.Statistics`) """ return self.internalPop.getStatistics() # ----------------------------------------------------------------- def generate_new_population(self): """ This function ... :return: """ # Inform the user log.info("Creating generation " + str(self.currentGeneration) + " ...") # Clone the current internal population newPop = GPopulation(self.internalPop) log.debug("Population was cloned.") size_iterate = len(self.internalPop) # Odd population size if size_iterate % 2 != 0: size_iterate -= 1 crossover_empty = self.select(popID=self.currentGeneration).crossover.isEmpty() for i in xrange(0, size_iterate, 2): genomeMom = self.select(popID=self.currentGeneration) genomeDad = self.select(popID=self.currentGeneration) if not crossover_empty and self.pCrossover >= 1.0: for it in genomeMom.crossover.applyFunctions(mom=genomeMom, dad=genomeDad, count=2): (sister, brother) = it else: if not crossover_empty and utils.randomFlipCoin(self.pCrossover): for it in genomeMom.crossover.applyFunctions(mom=genomeMom, dad=genomeDad, count=2): (sister, brother) = it else: sister = genomeMom.clone() brother = genomeDad.clone() sister.mutate(pmut=self.pMutation, ga_engine=self) brother.mutate(pmut=self.pMutation, ga_engine=self) newPop.internalPop.append(sister) newPop.internalPop.append(brother) if len(self.internalPop) % 2 != 0: genomeMom = self.select(popID=self.currentGeneration) genomeDad = self.select(popID=self.currentGeneration) if utils.randomFlipCoin(self.pCrossover): for it in genomeMom.crossover.applyFunctions(mom=genomeMom, dad=genomeDad, count=1): (sister, brother) = it else: sister = prng.choice([genomeMom, genomeDad]) sister = sister.clone() sister.mutate(pmut=self.pMutation, ga_engine=self) newPop.internalPop.append(sister) # Return the new population #return newPop # NEW self.new_population = newPop # ----------------------------------------------------------------- def step(self): """ This function performs one step in the evolution, i.e. one generation """ # Inform the user log.info("Performing step in the evolutionary process ...") # Generate the new population ## NEW self.generate_new_population() # Evaluate print(self.generation_description) self.new_population.evaluate() # Replace population self.replace_internal_population() # Sort the population self.internalPop.sort() # Set new population to None self.new_population = None # Inform the user #log.success("The generation %d was finished.", self.currentGeneration) # Increment the current generation number self.currentGeneration += 1 if self.max_time: total_time = time() - self.time_init if total_time > self.max_time: return True return self.currentGeneration == self.nGenerations # ----------------------------------------------------------------- def replace_internal_population(self): """ This function ... :return: """ # Elitism: if self.elitism: self.do_elitism(self.new_population) # Set the new population as the internal population and sort it self.internalPop = self.new_population # ----------------------------------------------------------------- def do_elitism(self, new_population): """ This function ... :param new_population: :return: """ # Elitism concept has different meanings for metaheuristic and particular for GA. In general, elitism # is related with memory: "remember the best solution found" (kind of greedying). In the most traditional way, # for evolutionary algorithms (GA, EA, DE...), elitism implies the best solution found is used for to build the # next generation. Particularly, for GA it means keeping the best individual found intact for next generation. # In GA, there is something like a proof for multiobjective optimization (villalobos, coello and hernandez, 2005) # that linked convergence and elitism which is a kind of cool stuff. # # Elitism involves copying a small proportion of the fittest candidates, unchanged, into the next generation. # This can sometimes have a dramatic impact on performance by ensuring that the EA does not waste time # re-discovering previously discarded partial solutions. Candidate solutions that are preserved unchanged # through elitism remain eligible for selection as parents when breeding the remainder of the next generation. log.debug("Doing elitism ...") if self.getMinimax() == constants.minimaxType["maximize"]: for i in xrange(self.nElitismReplacement): ##re-evaluate before being sure this is the best # self.internalPop.bestRaw(i).evaluate() # IS THIS REALLY NECESSARY ? if self.internalPop.bestRaw(i).score > new_population.bestRaw(i).score: new_population[len(new_population) - 1 - i] = self.internalPop.bestRaw(i) elif self.getMinimax() == constants.minimaxType["minimize"]: for i in xrange(self.nElitismReplacement): ##re-evaluate before being sure this is the best # self.internalPop.bestRaw(i).evaluate() # IS THIS REALLY NECESSARY ? if self.internalPop.bestRaw(i).score < new_population.bestRaw(i).score: new_population[len(new_population) - 1 - i] = self.internalPop.bestRaw(i) # ----------------------------------------------------------------- def printStats(self): """ Print generation statistics :rtype: the printed statistics as string .. versionchanged:: 0.6 The return of *printStats* method. """ percent = self.currentGeneration * 100 / float(self.nGenerations) message = "Gen. %d (%.2f%%):" % (self.currentGeneration, percent) log.info(message) print(message,) sys_stdout.flush() self.internalPop.statistics() stat_ret = self.internalPop.printStats() return message + stat_ret # ----------------------------------------------------------------- def printTimeElapsed(self): """ Shows the time elapsed since the begin of evolution """ total_time = time() - self.time_init print("Total time elapsed: %.3f seconds." % total_time) return total_time # ----------------------------------------------------------------- def dumpStatsDB(self): """ Dumps the current statistics to database adapter """ log.debug("Dumping stats to the DB Adapter") self.internalPop.statistics() self.dbAdapter.insert(self) # ----------------------------------------------------------------- def evolve(self, freq_stats=0): """ Do all the generations until the termination criteria, accepts the freq_stats (default is 0) to dump statistics at n-generation Example: >>> ga_engine.evolve(freq_stats=10) (...) :param freq_stats: if greater than 0, the statistics will be printed every freq_stats generation. :rtype: returns the best individual of the evolution .. versionadded:: 0.6 the return of the best individual """ # Initialize self.initialize_evolution() # Do the evolution loop self.evolve_loop(freq_stats) # Finish evolution, return best individual return self.finish_evolution() # ----------------------------------------------------------------- @property def is_initial_generation(self): """ This function ... :return: """ if self.new_population is None: if self.currentGeneration > 0: raise RuntimeError("Inconsistent state: 'new_population' does exist but 'currentGeneration' >0") return True else: return False # ----------------------------------------------------------------- @property def generation_description(self): """ This function ... :return: """ if self.is_initial_generation: return "Initial generation" else: return "Generation " + str(self.currentGeneration) # ----------------------------------------------------------------- def initialize_evolution(self): """ This function ... :return: """ self.initialize() # Inform the user ... log.info("Evaluating and sorting the initial population ...") # Evaluate and sort the internal population self.internalPop.evaluate() self.sort_internal_population() # ----------------------------------------------------------------- def sort_internal_population(self): """ This function ... :return: """ self.internalPop.sort() # ----------------------------------------------------------------- def evolve_loop(self, freq_stats): """ This function ... :return: """ log.debug("Starting loop over evolutionary algorithm.") while True: if not self.evolve_generation(freq_stats): break # ----------------------------------------------------------------- def finish_evolution(self, silent=True): """ This function ... :return: """ if not silent: self.printStats() self.printTimeElapsed() if self.dbAdapter: log.debug("Closing the DB Adapter") if not (self.currentGeneration % self.dbAdapter.getStatsGenFreq() == 0): self.dumpStatsDB() self.dbAdapter.commitAndClose() if self.migrationAdapter: log.debug("Closing the Migration Adapter") self.migrationAdapter.stop() return self.bestIndividual() # ----------------------------------------------------------------- def evolve_generation(self, freq_stats): """ This function ... :param freq_stats: :return: """ stopFlagCallback = False stopFlagTerminationCriteria = False if self.migrationAdapter: log.debug("Migration adapter: exchange") self.migrationAdapter.exchange() self.internalPop.clearFlags() self.internalPop.sort() if not self.stepCallback.isEmpty(): for it in self.stepCallback.applyFunctions(self): stopFlagCallback = it if not self.terminationCriteria.isEmpty(): for it in self.terminationCriteria.applyFunctions(self): stopFlagTerminationCriteria = it if freq_stats: if (self.currentGeneration % freq_stats == 0) or (self.getCurrentGeneration() == 0): self.printStats() if self.dbAdapter: if self.currentGeneration % self.dbAdapter.getStatsGenFreq() == 0: self.dumpStatsDB() if stopFlagTerminationCriteria: log.debug("Evolution stopped by the Termination Criteria !") if freq_stats: print("\n\tEvolution stopped by Termination Criteria function !\n") return False if stopFlagCallback: log.debug("Evolution stopped by Step Callback function !") if freq_stats: print("\n\tEvolution stopped by Step Callback function !\n") return False if self.step(): return False return True # ----------------------------------------------------------------- def select(self, **args): """ Select one individual from population :param args: this parameters will be sent to the selector """ for it in self.selector.applyFunctions(self.internalPop, **args): return it
class MigrationScheme(object): """ This is the base class for all migration schemes """ selector = None """ This is the function slot for the selection method if you want to change the default selector, you must do this: :: migration_scheme.selector.set(Selectors.GRouletteWheel) """ # ----------------------------------------------------------------- def __init__(self): """ The constructor ... """ self.selector = FunctionSlot("Selector") self.GAEngine = None self.nMigrationRate = constants.CDefGenMigrationRate self.nIndividuals = constants.CDefMigrationNIndividuals self.nReplacement = constants.CDefGenMigrationReplacement self.networkCompression = 9 # ----------------------------------------------------------------- def isReady(self): """ Returns true if is time to migrate """ return True if self.GAEngine.getCurrentGeneration( ) % self.nMigrationRate == 0 else False # ----------------------------------------------------------------- def getCompressionLevel(self): """ Get the zlib compression level of network data The values are in the interval described on the :func:`Network.pickleAndCompress` """ return self.networkCompression # ----------------------------------------------------------------- def setCompressionLevel(self, level): """ Set the zlib compression level of network data The values are in the interval described on the :func:`Network.pickleAndCompress` :param level: the zlib compression level """ self.networkCompression = level # ----------------------------------------------------------------- def getNumReplacement(self): """ Return the number of individuals that will be replaced in the migration process """ return self.nReplacement # ----------------------------------------------------------------- def setNumReplacement(self, num_individuals): """ Return the number of individuals that will be replaced in the migration process :param num_individuals: the number of individuals to be replaced """ self.nReplacement = num_individuals # ----------------------------------------------------------------- def getNumIndividuals(self): """ Return the number of individuals that will migrate :rtype: the number of individuals to be replaced """ return self.nIndividuals # ----------------------------------------------------------------- def setNumIndividuals(self, num_individuals): """ Set the number of individuals that will migrate :param num_individuals: the number of individuals """ self.nIndividuals = num_individuals # ----------------------------------------------------------------- def setMigrationRate(self, generations): """ Sets the generation frequency supposed to migrate and receive individuals. :param generations: the number of generations """ self.nMigrationRate = generations # ----------------------------------------------------------------- def getMigrationRate(self): """ Return the the generation frequency supposed to migrate and receive individuals :rtype: the number of generations """ return self.nMigrationRate # ----------------------------------------------------------------- def setGAEngine(self, ga_engine): """ Sets the GA Engine handler """ self.GAEngine = ga_engine # ----------------------------------------------------------------- def start(self): """ Initializes the migration scheme """ pass # ----------------------------------------------------------------- def stop(self): """ Stops the migration engine """ pass # ----------------------------------------------------------------- def select(self): """ Picks an individual from population using specific selection method :rtype: an individual object """ if self.selector.isEmpty(): return self.GAEngine.select(popID=self.GAEngine.currentGeneration) else: for it in self.selector.applyFunctions( self.GAEngine.internalPop, popID=self.GAEngine.currentGeneration): return it # ----------------------------------------------------------------- def selectPool(self, num_individuals): """ Select num_individuals number of individuals and return a pool :param num_individuals: the number of individuals to select :rtype: list with individuals """ pool = [self.select() for i in xrange(num_individuals)] return pool # ----------------------------------------------------------------- def exchange(self): """ Exchange individuals """ pass