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, 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 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
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