diff --git a/deap/algorithms.py b/deap/algorithms.py index 26812f52d..d75d4b8fe 100644 --- a/deap/algorithms.py +++ b/deap/algorithms.py @@ -15,8 +15,8 @@ """The :mod:`algorithms` module is intended to contain some specific algorithms in order to execute very common evolutionary algorithms. The method used here -are more for convenience than reference as the implementation of every -evolutionary algorithm may vary infinitely. Most of the algorithms in this +are more for convenience than reference as the implementation of every +evolutionary algorithm may vary infinitely. Most of the algorithms in this module use operators registered in the toolbox. Generally, the keyword used are :meth:`mate` for crossover, :meth:`mutate` for mutation, :meth:`~deap.select` for selection and :meth:`evaluate` for evaluation. @@ -34,7 +34,7 @@ def varAnd(population, toolbox, cxpb, mutpb): (crossover **and** mutation). The modified individuals have their fitness invalidated. The individuals are cloned so returned population is independent of the input population. - + :param population: A list of individuals to vary. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. @@ -42,7 +42,7 @@ def varAnd(population, toolbox, cxpb, mutpb): :param mutpb: The probability of mutating an individual. :returns: A list of varied individuals that are independent of their parents. - + The variation goes as follow. First, the parental population :math:`P_\mathrm{p}` is duplicated using the :meth:`toolbox.clone` method and the result is put into the offspring population :math:`P_\mathrm{o}`. @@ -56,7 +56,7 @@ def varAnd(population, toolbox, cxpb, mutpb): probability *mutpb*. When an individual is mutated it replaces its not mutated version in :math:`P_\mathrm{o}`. The resulting :math:`P_\mathrm{o}` is returned. - + This variation is named *And* beceause of its propention to apply both crossover and mutation on the individuals. Note that both operators are not applied systematicaly, the resulting individuals can be generated from @@ -65,25 +65,25 @@ def varAnd(population, toolbox, cxpb, mutpb): :math:`[0, 1]`. """ offspring = [toolbox.clone(ind) for ind in population] - + # Apply crossover and mutation on the offspring for i in range(1, len(offspring), 2): if random.random() < cxpb: - offspring[i-1], offspring[i] = toolbox.mate(offspring[i-1], offspring[i]) - del offspring[i-1].fitness.values, offspring[i].fitness.values - + offspring[i - 1], offspring[i] = toolbox.mate(offspring[i - 1], offspring[i]) + del offspring[i - 1].fitness.values, offspring[i].fitness.values + for i in range(len(offspring)): if random.random() < mutpb: offspring[i], = toolbox.mutate(offspring[i]) del offspring[i].fitness.values - + return offspring def eaSimple(population, toolbox, cxpb, mutpb, ngen, stats=None, halloffame=None, verbose=__debug__): """This algorithm reproduce the simplest evolutionary algorithm as presented in chapter 7 of [Back2000]_. - + :param population: A list of individuals. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. @@ -98,7 +98,7 @@ def eaSimple(population, toolbox, cxpb, mutpb, ngen, stats=None, :returns: The final population :returns: A class:`~deap.tools.Logbook` with the statistics of the evolution - + The algorithm takes in a population and evolves it in place using the :meth:`varAnd` method. It returns the optimized population and a :class:`~deap.tools.Logbook` with the statistics of the evolution. The @@ -131,11 +131,11 @@ def eaSimple(population, toolbox, cxpb, mutpb, ngen, stats=None, Using a non-stochastic selection method will result in no selection as the operator selects *n* individuals from a pool of *n*. - + This function expects the :meth:`toolbox.mate`, :meth:`toolbox.mutate`, :meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be registered in the toolbox. - + .. [Back2000] Back, Fogel and Michalewicz, "Evolutionary Computation 1 : Basic Algorithms and Operators", 2000. """ @@ -157,31 +157,31 @@ def eaSimple(population, toolbox, cxpb, mutpb, ngen, stats=None, print logbook.stream # Begin the generational process - for gen in range(1, ngen+1): + for gen in range(1, ngen + 1): # Select the next generation individuals offspring = toolbox.select(population, len(population)) - + # Vary the pool of individuals offspring = varAnd(offspring, toolbox, cxpb, mutpb) - + # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit - + # Update the hall of fame with the generated individuals if halloffame is not None: halloffame.update(offspring) - + # Replace the current population by the offspring population[:] = offspring - + # Append the current generation statistics to the logbook record = stats.compile(population) if stats else {} logbook.record(gen=gen, nevals=len(invalid_ind), **record) if verbose: - print logbook.stream + print logbook.stream return population, logbook @@ -190,7 +190,7 @@ def varOr(population, toolbox, lambda_, cxpb, mutpb): (crossover, mutation **or** reproduction). The modified individuals have their fitness invalidated. The individuals are cloned so returned population is independent of the input population. - + :param population: A list of individuals to vary. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. @@ -200,7 +200,7 @@ def varOr(population, toolbox, lambda_, cxpb, mutpb): :returns: The final population :returns: A class:`~deap.tools.Logbook` with the statistics of the evolution - + The variation goes as follow. On each of the *lambda_* iteration, it selects one of the three operations; crossover, mutation or reproduction. In the case of a crossover, two individuals are selected at random from @@ -214,7 +214,7 @@ def varOr(population, toolbox, lambda_, cxpb, mutpb): :math:`P_\mathrm{o}`. In the case of a reproduction, one individual is selected at random from :math:`P_\mathrm{p}`, cloned and appended to :math:`P_\mathrm{o}`. - + This variation is named *Or* beceause an offspring will never result from both operations crossover and mutation. The sum of both probabilities shall be in :math:`[0, 1]`, the reproduction probability is @@ -222,7 +222,7 @@ def varOr(population, toolbox, lambda_, cxpb, mutpb): """ assert (cxpb + mutpb) <= 1.0, ("The sum of the crossover and mutation " "probabilities must be smaller or equal to 1.0.") - + offspring = [] for _ in xrange(lambda_): op_choice = random.random() @@ -238,13 +238,13 @@ def varOr(population, toolbox, lambda_, cxpb, mutpb): offspring.append(ind) else: # Apply reproduction offspring.append(random.choice(population)) - + return offspring def eaMuPlusLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, stats=None, halloffame=None, verbose=__debug__): """This is the :math:`(\mu + \lambda)` evolutionary algorithm. - + :param population: A list of individuals. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. @@ -261,7 +261,7 @@ def eaMuPlusLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, :returns: The final population :returns: A class:`~deap.tools.Logbook` with the statistics of the evolution. - + The algorithm takes in a population and evolves it in place using the :func:`varOr` function. It returns the optimized population and a :class:`~deap.tools.Logbook` with the statistics of the evolution. The @@ -307,16 +307,16 @@ def eaMuPlusLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, print logbook.stream # Begin the generational process - for gen in range(1, ngen+1): + for gen in range(1, ngen + 1): # Vary the population offspring = varOr(population, toolbox, lambda_, cxpb, mutpb) - + # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit - + # Update the hall of fame with the generated individuals if halloffame is not None: halloffame.update(offspring) @@ -331,12 +331,12 @@ def eaMuPlusLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, print logbook.stream return population, logbook - + def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, stats=None, halloffame=None, verbose=__debug__): """This is the :math:`(\mu~,~\lambda)` evolutionary algorithm. - - :param population: A list of individuals. + + :param population: A list of individuals. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. :param mu: The number of individuals to select for the next generation. @@ -352,7 +352,7 @@ def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, :returns: The final population :returns: A class:`~deap.tools.Logbook` with the statistics of the evolution - + The algorithm takes in a population and evolves it in place using the :func:`varOr` function. It returns the optimized population and a :class:`~deap.tools.Logbook` with the statistics of the evolution. The @@ -381,7 +381,7 @@ def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, selection will result in no selection at all as the operator selects *lambda* individuals from a pool of *mu*. - + This function expects :meth:`toolbox.mate`, :meth:`toolbox.mutate`, :meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be registered in the toolbox. This algorithm uses the :func:`varOr` @@ -407,7 +407,7 @@ def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, print logbook.stream # Begin the generational process - for gen in range(1, ngen+1): + for gen in range(1, ngen + 1): # Vary the population offspring = varOr(population, toolbox, lambda_, cxpb, mutpb) @@ -433,11 +433,11 @@ def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, return population, logbook -def eaGenerateUpdate(toolbox, ngen, halloffame=None, stats=None, +def eaGenerateUpdate(toolbox, ngen, halloffame=None, stats=None, verbose=__debug__): - """This is algorithm implements the ask-tell model proposed in + """This is algorithm implements the ask-tell model proposed in [Colette2010]_, where ask is called `generate` and tell is called `update`. - + :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. :param ngen: The number of generation. @@ -480,16 +480,16 @@ def eaGenerateUpdate(toolbox, ngen, halloffame=None, stats=None, fitnesses = toolbox.map(toolbox.evaluate, population) for ind, fit in zip(population, fitnesses): ind.fitness.values = fit - + if halloffame is not None: halloffame.update(population) - + # Update the strategy with the evaluated individuals toolbox.update(population) - + record = stats.compile(population) if stats is not None else {} logbook.record(gen=gen, nevals=len(population), **record) if verbose: print logbook.stream - return population, logbook \ No newline at end of file + return population, logbook diff --git a/deap/base.py b/deap/base.py index 95fe0e2d7..5e3620cf4 100644 --- a/deap/base.py +++ b/deap/base.py @@ -22,7 +22,7 @@ class used as base class, for the fitness member of any individual. """ from collections import Sequence from copy import deepcopy -from functools import partial +from functools import partial from operator import mul, truediv class Toolbox(object): @@ -45,11 +45,11 @@ def __init__(self): self.register("map", map) def register(self, alias, function, *args, **kargs): - """Register a *function* in the toolbox under the name *alias*. You - may provide default arguments that will be passed automatically when - calling the registered function. Fixed arguments can then be overriden + """Register a *function* in the toolbox under the name *alias*. You + may provide default arguments that will be passed automatically when + calling the registered function. Fixed arguments can then be overriden at function call time. - + :param alias: The name the operator will take in the toolbox. If the alias already exist it will overwrite the the operator already present. @@ -57,17 +57,17 @@ def register(self, alias, function, *args, **kargs): :param argument: One or more argument (and keyword argument) to pass automatically to the registered function when called, optional. - + The following code block is an example of how the toolbox is used. :: >>> def func(a, b, c=3): ... print a, b, c - ... + ... >>> tools = Toolbox() >>> tools.register("myFunc", func, 2, c=4) >>> tools.myFunc(3) 2 3 4 - + The registered function will be given the attributes :attr:`__name__` set to the alias and :attr:`__doc__` set to the original function's documentation. The :attr:`__dict__` attribute will also be updated @@ -76,18 +76,18 @@ def register(self, alias, function, *args, **kargs): pfunc = partial(function, *args, **kargs) pfunc.__name__ = alias pfunc.__doc__ = function.__doc__ - + if hasattr(function, "__dict__") and not isinstance(function, type): - # Some functions don't have a dictionary, in these cases + # Some functions don't have a dictionary, in these cases # simply don't copy it. Moreover, if the function is actually # a class, we do not want to copy the dictionary. pfunc.__dict__.update(function.__dict__.copy()) - + setattr(self, alias, pfunc) def unregister(self, alias): """Unregister *alias* from the toolbox. - + :param alias: The name of the operator to remove from the toolbox. """ delattr(self, alias) @@ -95,13 +95,13 @@ def unregister(self, alias): def decorate(self, alias, *decorators): """Decorate *alias* with the specified *decorators*, *alias* has to be a registered function in the current toolbox. - + :param alias: The name of the operator to decorate. :param decorator: One or more function decorator. If multiple decorators are provided they will be applied in order, with the last decorator decorating all the others. - + .. note:: Decorate a function using the toolbox makes it unpicklable, and will produce an error on pickling. Although this limitation is not @@ -121,7 +121,7 @@ class Fitness(object): """The fitness is a measure of quality of a solution. If *values* are provided as a tuple, the fitness is initalized using those values, otherwise it is empty (or invalid). - + :param values: The initial values of the fitness as a tuple, optional. Fitnesses may be compared using the ``>``, ``<``, ``>=``, ``<=``, ``==``, @@ -139,7 +139,7 @@ class Fitness(object): When comparing fitness values that are **minimized**, ``a > b`` will return :data:`True` if *a* is **smaller** than *b*. """ - + weights = None """The weights are used in the fitness comparison. They are shared among all fitnesses of the same type. When subclassing :class:`Fitness`, the @@ -148,37 +148,37 @@ class Fitness(object): the associated objective and positive weight to the maximization. .. note:: - If weights is not defined during subclassing, the following error will - occur at instantiation of a subclass fitness object: - + If weights is not defined during subclassing, the following error will + occur at instantiation of a subclass fitness object: + ``TypeError: Can't instantiate abstract with abstract attribute weights.`` """ - + wvalues = () """Contains the weighted values of the fitness, the multiplication with the weights is made when the values are set via the property :attr:`values`. Multiplication is made on setting of the values for efficiency. - + Generally it is unnecessary to manipulate wvalues as it is an internal attribute of the fitness used in the comparison operators. """ - + def __init__(self, values=()): if self.weights is None: raise TypeError("Can't instantiate abstract %r with abstract " "attribute weights." % (self.__class__)) - + if not isinstance(self.weights, Sequence): - raise TypeError("Attribute weights of %r must be a sequence." + raise TypeError("Attribute weights of %r must be a sequence." % self.__class__) - + if len(values) > 0: self.values = values - + def getValues(self): return tuple(map(truediv, self.wvalues, self.weights)) - + def setValues(self, values): try: self.wvalues = tuple(map(mul, values, self.weights)) @@ -189,7 +189,7 @@ def setValues(self, values): "%r. Currently assigning value(s) %r of %r to a fitness with " "weights %s." % (self.__class__, values, type(values), self.weights)), traceback - + def delValues(self): self.wvalues = () @@ -198,13 +198,13 @@ def delValues(self): "in order to set the fitness and ``del individual.fitness.values`` " "in order to clear (invalidate) the fitness. The (unweighted) fitness " "can be directly accessed via ``individual.fitness.values``.")) - + def dominates(self, other, obj=slice(None)): - """Return true if each objective of *self* is not strictly worse than - the corresponding objective of *other* and at least one objective is + """Return true if each objective of *self* is not strictly worse than + the corresponding objective of *other* and at least one objective is strictly better. - :param obj: Slice indicating on which objectives the domination is + :param obj: Slice indicating on which objectives the domination is tested. The default value is `slice(None)`, representing every objectives. """ @@ -213,20 +213,20 @@ def dominates(self, other, obj=slice(None)): if self_wvalue > other_wvalue: not_equal = True elif self_wvalue < other_wvalue: - return False + return False return not_equal @property def valid(self): """Assess if a fitness is valid or not.""" return len(self.wvalues) != 0 - + def __hash__(self): return hash(self.wvalues) def __gt__(self, other): return not self.__le__(other) - + def __ge__(self, other): return not self.__lt__(other) @@ -238,15 +238,15 @@ def __lt__(self, other): def __eq__(self, other): return self.wvalues == other.wvalues - + def __ne__(self, other): return not self.__eq__(other) def __deepcopy__(self, memo): """Replace the basic deepcopy function with a faster one. - - It assumes that the elements in the :attr:`values` tuple are - immutable and the fitness does not contain any other object + + It assumes that the elements in the :attr:`values` tuple are + immutable and the fitness does not contain any other object than :attr:`values` and :attr:`weights`. """ copy_ = self.__class__() diff --git a/deap/cma.py b/deap/cma.py index bb6f8bb6b..660e7c26f 100644 --- a/deap/cma.py +++ b/deap/cma.py @@ -13,11 +13,11 @@ # You should have received a copy of the GNU Lesser General Public # License along with DEAP. If not, see . -# Special thanks to Nikolaus Hansen for providing major part of +# Special thanks to Nikolaus Hansen for providing major part of # this code. The CMA-ES algorithm is provided in many other languages # and advanced versions at http://www.lri.fr/~hansen/cmaesintro.html. -"""A module that provides support for the Covariance Matrix Adaptation +"""A module that provides support for the Covariance Matrix Adaptation Evolution Strategy. """ import numpy @@ -30,13 +30,13 @@ class Strategy(object): """ A strategy that will keep track of the basic parameters of the CMA-ES algorithm. - + :param centroid: An iterable object that indicates where to start the evolution. :param sigma: The initial standard deviation of the distribution. :param parameter: One or more parameter to pass to the strategy as described in the following table, optional. - + +----------------+---------------------------+----------------------------+ | Parameter | Default | Details | +================+===========================+============================+ @@ -45,7 +45,7 @@ class Strategy(object): | | | ``N`` is the individual's | | | | size (integer). | +----------------+---------------------------+----------------------------+ - | ``mu`` | ``int(lambda_ / 2)`` | The number of parents to | + | ``mu`` | ``int(lambda_ / 2)`` | The number of parents to | | | | keep from the | | | | lambda children (integer). | +----------------+---------------------------+----------------------------+ @@ -79,35 +79,35 @@ class Strategy(object): """ def __init__(self, centroid, sigma, **kargs): self.params = kargs - + # Create a centroid as a numpy array self.centroid = numpy.array(centroid) - + self.dim = len(self.centroid) self.sigma = sigma self.pc = numpy.zeros(self.dim) self.ps = numpy.zeros(self.dim) self.chiN = sqrt(self.dim) * (1 - 1. / (4. * self.dim) + \ - 1. / (21. * self.dim**2)) - + 1. / (21. * self.dim ** 2)) + self.C = self.params.get("cmatrix", numpy.identity(self.dim)) self.diagD, self.B = numpy.linalg.eigh(self.C) indx = numpy.argsort(self.diagD) - self.diagD = self.diagD[indx]**0.5 + self.diagD = self.diagD[indx] ** 0.5 self.B = self.B[:, indx] self.BD = self.B * self.diagD - - self.cond = self.diagD[indx[-1]]/self.diagD[indx[0]] - + + self.cond = self.diagD[indx[-1]] / self.diagD[indx[0]] + self.lambda_ = self.params.get("lambda_", int(4 + 3 * log(self.dim))) self.update_count = 0 self.computeParams(self.params) - + def generate(self, ind_init): """Generate a population of :math:`\lambda` individuals of type *ind_init* from the current strategy. - + :param ind_init: A function object that is able to initialize an individual from a list. :returns: A list of individuals. @@ -115,62 +115,62 @@ def generate(self, ind_init): arz = numpy.random.standard_normal((self.lambda_, self.dim)) arz = self.centroid + self.sigma * numpy.dot(arz, self.BD.T) return map(ind_init, arz) - + def update(self, population): """Update the current covariance matrix strategy from the *population*. - + :param population: A list of individuals from which to update the parameters. """ population.sort(key=lambda ind: ind.fitness, reverse=True) - + old_centroid = self.centroid self.centroid = numpy.dot(self.weights, population[0:self.mu]) - + c_diff = self.centroid - old_centroid - + # Cumulation : update evolution path self.ps = (1 - self.cs) * self.ps \ + sqrt(self.cs * (2 - self.cs) * self.mueff) / self.sigma \ * numpy.dot(self.B, (1. / self.diagD) \ * numpy.dot(self.B.T, c_diff)) - - hsig = float((numpy.linalg.norm(self.ps) / - sqrt(1. - (1. - self.cs)**(2. * (self.update_count + 1.))) / self.chiN + + hsig = float((numpy.linalg.norm(self.ps) / + sqrt(1. - (1. - self.cs) ** (2. * (self.update_count + 1.))) / self.chiN < (1.4 + 2. / (self.dim + 1.)))) - + self.update_count += 1 - + self.pc = (1 - self.cc) * self.pc + hsig \ * sqrt(self.cc * (2 - self.cc) * self.mueff) / self.sigma \ * c_diff - + # Update covariance matrix artmp = population[0:self.mu] - old_centroid self.C = (1 - self.ccov1 - self.ccovmu + (1 - hsig) \ * self.ccov1 * self.cc * (2 - self.cc)) * self.C \ + self.ccov1 * numpy.outer(self.pc, self.pc) \ + self.ccovmu * numpy.dot((self.weights * artmp.T), artmp) \ - / self.sigma**2 - - + / self.sigma ** 2 + + self.sigma *= numpy.exp((numpy.linalg.norm(self.ps) / self.chiN - 1.) \ * self.cs / self.damps) - + self.diagD, self.B = numpy.linalg.eigh(self.C) indx = numpy.argsort(self.diagD) - - self.cond = self.diagD[indx[-1]]/self.diagD[indx[0]] - - self.diagD = self.diagD[indx]**0.5 + + self.cond = self.diagD[indx[-1]] / self.diagD[indx[0]] + + self.diagD = self.diagD[indx] ** 0.5 self.B = self.B[:, indx] self.BD = self.B * self.diagD def computeParams(self, params): """Computes the parameters depending on :math:`\lambda`. It needs to be called again if :math:`\lambda` changes during evolution. - + :param params: A dictionary of the manually set parameters. """ self.mu = params.get("mu", int(self.lambda_ / 2)) @@ -184,27 +184,27 @@ def computeParams(self, params): self.weights = numpy.ones(self.mu) else: raise RuntimeError("Unknown weights : %s" % rweights) - + self.weights /= sum(self.weights) - self.mueff = 1. / sum(self.weights**2) - + self.mueff = 1. / sum(self.weights ** 2) + self.cc = params.get("ccum", 4. / (self.dim + 4.)) - self.cs = params.get("cs", (self.mueff + 2.) / + self.cs = params.get("cs", (self.mueff + 2.) / (self.dim + self.mueff + 3.)) - self.ccov1 = params.get("ccov1", 2. / ((self.dim + 1.3)**2 + \ + self.ccov1 = params.get("ccov1", 2. / ((self.dim + 1.3) ** 2 + \ self.mueff)) self.ccovmu = params.get("ccovmu", 2. * (self.mueff - 2. + \ 1. / self.mueff) / \ - ((self.dim + 2.)**2 + self.mueff)) + ((self.dim + 2.) ** 2 + self.mueff)) self.ccovmu = min(1 - self.ccov1, self.ccovmu) self.damps = 1. + 2. * max(0, sqrt((self.mueff - 1.) / \ (self.dim + 1.)) - 1.) + self.cs self.damps = params.get("damps", self.damps) - + class StrategyOnePlusLambda(object): """ A CMA-ES strategy that uses the :math:`1 + \lambda` paradigme. - + :param parent: An iterable object that indicates where to start the evolution. The parent requires a fitness attribute. :param sigma: The initial standard deviation of the distribution. @@ -242,80 +242,80 @@ def __init__(self, parent, sigma, **kargs): self.C = numpy.identity(self.dim) self.A = numpy.identity(self.dim) - + self.pc = numpy.zeros(self.dim) - + self.computeParams(kargs) self.psucc = self.ptarg - + def computeParams(self, params): """Computes the parameters depending on :math:`\lambda`. It needs to be called again if :math:`\lambda` changes during evolution. - + :param params: A dictionary of the manually set parameters. """ # Selection : self.lambda_ = params.get("lambda_", 1) - + # Step size control : - self.d = params.get("d", 1.0 + self.dim/(2.0*self.lambda_)) - self.ptarg = params.get("ptarg", 1.0/(5+sqrt(self.lambda_)/2.0)) - self.cp = params.get("cp", self.ptarg*self.lambda_/(2+self.ptarg*self.lambda_)) - + self.d = params.get("d", 1.0 + self.dim / (2.0 * self.lambda_)) + self.ptarg = params.get("ptarg", 1.0 / (5 + sqrt(self.lambda_) / 2.0)) + self.cp = params.get("cp", self.ptarg * self.lambda_ / (2 + self.ptarg * self.lambda_)) + # Covariance matrix adaptation - self.cc = params.get("cc", 2.0/(self.dim+2.0)) - self.ccov = params.get("ccov", 2.0/(self.dim**2 + 6.0)) + self.cc = params.get("cc", 2.0 / (self.dim + 2.0)) + self.ccov = params.get("ccov", 2.0 / (self.dim ** 2 + 6.0)) self.pthresh = params.get("pthresh", 0.44) - + def generate(self, ind_init): """Generate a population of :math:`\lambda` individuals of type *ind_init* from the current strategy. - + :param ind_init: A function object that is able to initialize an individual from a list. :returns: A list of individuals. """ # self.y = numpy.dot(self.A, numpy.random.standard_normal(self.dim)) arz = numpy.random.standard_normal((self.lambda_, self.dim)) - arz = self.parent + self.sigma * numpy.dot(arz, self.A.T) + arz = self.parent + self.sigma * numpy.dot(arz, self.A.T) return map(ind_init, arz) - + def update(self, population): """Update the current covariance matrix strategy from the *population*. - + :param population: A list of individuals from which to update the parameters. """ population.sort(key=lambda ind: ind.fitness, reverse=True) lambda_succ = sum(self.parent.fitness <= ind.fitness for ind in population) p_succ = float(lambda_succ) / self.lambda_ - self.psucc = (1-self.cp)*self.psucc + self.cp*p_succ - + self.psucc = (1 - self.cp) * self.psucc + self.cp * p_succ + if self.parent.fitness <= population[0].fitness: x_step = (population[0] - numpy.array(self.parent)) / self.sigma self.parent = copy.deepcopy(population[0]) if self.psucc < self.pthresh: - self.pc = (1 - self.cc)*self.pc + sqrt(self.cc * (2 - self.cc)) * x_step - self.C = (1-self.ccov)*self.C + self.ccov * numpy.outer(self.pc, self.pc) + self.pc = (1 - self.cc) * self.pc + sqrt(self.cc * (2 - self.cc)) * x_step + self.C = (1 - self.ccov) * self.C + self.ccov * numpy.outer(self.pc, self.pc) else: - self.pc = (1 - self.cc)*self.pc - self.C = (1-self.ccov)*self.C + self.ccov * (numpy.outer(self.pc, self.pc) + self.cc*(2-self.cc)*self.C) + self.pc = (1 - self.cc) * self.pc + self.C = (1 - self.ccov) * self.C + self.ccov * (numpy.outer(self.pc, self.pc) + self.cc * (2 - self.cc) * self.C) + + self.sigma = self.sigma * exp(1.0 / self.d * (self.psucc - self.ptarg) / (1.0 - self.ptarg)) - self.sigma = self.sigma * exp(1.0/self.d * (self.psucc - self.ptarg)/(1.0-self.ptarg)) - # We use Cholesky since for now we have no use of eigen decomposition # Basically, Cholesky returns a matrix A as C = A*A.T # Eigen decomposition returns two matrix B and D^2 as C = B*D^2*B.T = B*D*D*B.T # So A == B*D # To compute the new individual we need to multiply each vector z by A # as y = centroid + sigma * A*z - # So the Cholesky is more straightforward as we don't need to compute + # So the Cholesky is more straightforward as we don't need to compute # the squareroot of D^2, and multiply B and D in order to get A, we directly get A. # This can't be done (without cost) with the standard CMA-ES as the eigen decomposition is used # to compute covariance matrix inverse in the step-size evolutionary path computation. self.A = numpy.linalg.cholesky(self.C) - + class StrategyMultiObjective(object): """Multiobjective CMA-ES strategy based on the paper [Voss2010]_. It is used similarly as the standard CMA-ES strategy with a generate-update @@ -360,15 +360,15 @@ def __init__(self, population, sigma, **params): # Selection self.mu = params.get("mu", len(self.parents)) self.lambda_ = params.get("lambda_", 1) - + # Step size control self.d = params.get("d", 1.0 + self.dim / 2.0) self.ptarg = params.get("ptarg", 1.0 / (5.0 + 0.5)) self.cp = params.get("cp", self.ptarg / (2.0 + self.ptarg)) - + # Covariance matrix adaptation self.cc = params.get("cc", 2.0 / (self.dim + 2.0)) - self.ccov = params.get("ccov", 2.0 / (self.dim**2 + 6.0)) + self.ccov = params.get("ccov", 2.0 / (self.dim ** 2 + 6.0)) self.pthresh = params.get("pthresh", 0.44) # Internal parameters associated to the mu parent @@ -385,7 +385,7 @@ def __init__(self, population, sigma, **params): def generate(self, ind_init): """Generate a population of :math:`\lambda` individuals of type *ind_init* from the current strategy. - + :param ind_init: A function object that is able to initialize an individual from a list. :returns: A list of individuals with a private attribute :attr:`_ps`. @@ -395,7 +395,7 @@ def generate(self, ind_init): """ arz = numpy.random.randn(self.lambda_, self.dim) individuals = list() - + # Make sure every parent has a parent tag and index for i, p in enumerate(self.parents): p._ps = "p", i @@ -415,13 +415,13 @@ def generate(self, ind_init): _, p_idx = ndom[j]._ps individuals.append(ind_init(self.parents[p_idx] + self.sigmas[p_idx] * numpy.dot(self.A[p_idx], arz[i]))) individuals[-1]._ps = "o", p_idx - + return individuals def _select(self, candidates): if len(candidates) <= self.mu: return candidates, [] - + pareto_fronts = tools.sortLogNondominated(candidates, len(candidates)) chosen = list() @@ -461,24 +461,24 @@ def _select(self, candidates): def _rankOneUpdate(self, invCholesky, A, alpha, beta, v): w = numpy.dot(invCholesky, v) - + # Under this threshold, the update is mostly noise if w.max() > 1e-20: w_inv = numpy.dot(w, invCholesky) - norm_w2 = numpy.sum(w**2) + norm_w2 = numpy.sum(w ** 2) a = sqrt(alpha) - root = numpy.sqrt(1 + beta/alpha * norm_w2) + root = numpy.sqrt(1 + beta / alpha * norm_w2) b = a / norm_w2 * (root - 1) A = a * A + b * numpy.outer(v, w) - invCholesky = 1.0 / a * invCholesky - b / (a**2 + a * b * norm_w2) * numpy.outer(w, w_inv) + invCholesky = 1.0 / a * invCholesky - b / (a ** 2 + a * b * norm_w2) * numpy.outer(w, w_inv) return invCholesky, A def update(self, population): """Update the current covariance matrix strategies from the *population*. - + :param population: A list of individuals from which to update the parameters. """ @@ -517,13 +517,13 @@ def update(self, population): self.psucc[p_idx] = (1.0 - cp) * self.psucc[p_idx] + cp self.sigmas[p_idx] = self.sigmas[p_idx] * exp((self.psucc[p_idx] - ptarg) / (d * (1.0 - ptarg))) - + # It is unnecessary to update the entire parameter set for not chosen individuals # Their parameters will not make it to the next generation for ind in not_chosen: t, p_idx = ind._ps - + # Only the offspring update the parameter set if t == "o": self.psucc[p_idx] = (1.0 - cp) * self.psucc[p_idx] diff --git a/deap/creator.py b/deap/creator.py index 468d823f1..56de8cc32 100644 --- a/deap/creator.py +++ b/deap/creator.py @@ -63,7 +63,7 @@ def __new__(cls, iterable): """Creates a new instance of a numpy.ndarray from a function call. Adds the possibility to instanciate from an iterable.""" return numpy.array(list(iterable)).view(cls) - + def __setstate__(self, state): self.__dict__.update(state) @@ -76,7 +76,7 @@ class _array(array.array): @staticmethod def __new__(cls, seq=()): return super(_array, cls).__new__(cls, cls.typecode, seq) - + def __deepcopy__(self, memo): """Overrides the deepcopy from array.array that does not copy the object's attributes and class type. @@ -100,32 +100,32 @@ def create(name, base, **kargs): returned instance is added as an attribute of the class' instance. Otherwise, if the argument is not a class, (for example an :class:`int`), it is added as a "static" attribute of the class. - + :param name: The name of the class to create. :param base: A base class from which to inherit. :param attribute: One or more attributes to add on instanciation of this class, optional. - + The following is used to create a class :class:`Foo` inheriting from the standard :class:`list` and having an attribute :attr:`bar` being an empty dictionary and a static attribute :attr:`spam` initialized to 1. :: - + create("Foo", list, bar=dict, spam=1) - + This above line is exactly the same as defining in the :mod:`creator` module something like the following. :: - + class Foo(list): spam = 1 - + def __init__(self): self.bar = dict() The :ref:`creating-types` tutorial gives more examples of the creator usage. - + .. warning:: - + If your are inheriting from :class:`numpy.ndarray` see the :doc:`tutorials/advanced/numpy` tutorial and the :doc:`/examples/ga_onemax_numpy` example. @@ -150,7 +150,7 @@ def __init__(self): if base in class_replacers: base = class_replacers[base] - # A DeprecationWarning is raised when the object inherits from the + # A DeprecationWarning is raised when the object inherits from the # class "object" which leave the option of passing arguments, but # raise a warning stating that it will eventually stop permitting # this option. Usually this happens when the base class does not diff --git a/deap/gp.py b/deap/gp.py index 4f449a278..d06055843 100644 --- a/deap/gp.py +++ b/deap/gp.py @@ -158,7 +158,7 @@ def height(self): for elem in self: depth = stack.pop() max_depth = max(max_depth, depth) - stack.extend([depth+1] * elem.arity) + stack.extend([depth + 1] * elem.arity) return max_depth @property @@ -259,7 +259,7 @@ def __init__(self, name, in_types, ret_type, prefix="ARG"): # setting "__builtins__" to None avoid the context # being polluted by builtins function when evaluating # GP expression. - self.context = {"__builtins__" : None} + self.context = {"__builtins__": None} self.mapping = dict() self.terms_count = 0 self.prims_count = 0 @@ -381,8 +381,8 @@ def addEphemeralConstant(self, name, ephemeral, ret_type): """ module_gp = globals() if not name in module_gp: - class_ = type(name, (Ephemeral,), {'func' : staticmethod(ephemeral), - 'ret' : ret_type}) + class_ = type(name, (Ephemeral,), {'func': staticmethod(ephemeral), + 'ret': ret_type}) module_gp[name] = class_ else: class_ = module_gp[name] @@ -396,7 +396,7 @@ def addEphemeralConstant(self, name, ephemeral, ret_type): else: raise Exception("Ephemerals should be named differently " "than classes defined in the gp module.") - + self._add(class_) self.terms_count += 1 @@ -422,7 +422,7 @@ class PrimitiveSet(PrimitiveSetTyped): definition of type. """ def __init__(self, name, arity, prefix="ARG"): - args = [__type__]*arity + args = [__type__] * arity PrimitiveSetTyped.__init__(self, name, args, __type__, prefix) def addPrimitive(self, primitive, arity, name=None): @@ -495,7 +495,7 @@ def compileADF(expr, psets): for pset, subexpr in reversed(zip(psets, expr)): pset.context.update(adfdict) func = compile(subexpr, pset) - adfdict.update({pset.name : func}) + adfdict.update({pset.name: func}) return func ###################################### @@ -608,7 +608,7 @@ def generate(pset, min_, max_, condition, type_=None): "none available." % (type_,), traceback expr.append(prim) for arg in reversed(prim.args): - stack.append((depth+1, arg)) + stack.append((depth + 1, arg)) return expr @@ -745,7 +745,7 @@ def mutNodeReplacement(individual, pset): index = random.randrange(1, len(individual)) node = individual[index] - if node.arity == 0: # Terminal + if node.arity == 0: # Terminal term = random.choice(pset.terminals[node.ret]) if isclass(term): term = term() @@ -817,7 +817,7 @@ def mutInsert(individual, pset): term = term() new_subtree[i] = term - new_subtree[position:position+1] = individual[slice_] + new_subtree[position:position + 1] = individual[slice_] new_subtree.insert(0, new_node) individual[slice_] = new_subtree return individual, @@ -841,8 +841,8 @@ def mutShrink(individual): if len(iprims) != 0: index, prim = random.choice(iprims) arg_idx = random.choice([i for i, type_ in enumerate(prim.args) if type_ == prim.ret]) - rindex = index+1 - for _ in range(arg_idx+1): + rindex = index + 1 + for _ in range(arg_idx + 1): rslice = individual.searchSubtree(rindex) subtree = individual[rslice] rindex += len(subtree) @@ -867,8 +867,8 @@ def staticLimit(key, max_value): depth), because it can ensure that no tree higher than this limit will ever be accepted in the population, except if it was generated at initialization time. - - :param key: The function to use in order the get the wanted value. For + + :param key: The function to use in order the get the wanted value. For instance, on a GP tree, ``operator.attrgetter('height')`` may be used to set a depth limit, and ``len`` to set a size limit. :param max_value: The maximum value allowed for the given measurement. @@ -920,9 +920,9 @@ def harm(population, toolbox, cxpb, mutpb, ngen, :param rho: The HARM *rho* parameter. :param nbrindsmodel: The number of individuals to generate in order to model the natural distribution. -1 is a special - value which uses the equation proposed in + value which uses the equation proposed in [Gardner2015] to set the value of this parameter : - max(2000, len(population)) + max(2000, len(population)) :param mincutoff: The absolute minimum value for the cutoff point. It is used to ensure that HARM does not shrink the population too much at the beginning of the evolution. The default @@ -948,7 +948,7 @@ def harm(population, toolbox, cxpb, mutpb, ngen, model the natural distribution and the minimum cutoff point are less important, their default value being effective in most cases. - .. [Gardner2015] M.-A. Gardner, C. Gagne, and M. Parizeau, Controlling + .. [Gardner2015] M.-A. Gardner, C. Gagne, and M. Parizeau, Controlling Code Growth by Dynamically Shaping the Genotype Size Distribution, Genetic Programming and Evolvable Machines, 2015, DOI 10.1007/s10710-015-9242-8 @@ -968,7 +968,7 @@ def _genpop(n, pickfrom=[], acceptfunc=lambda s: True, producesizes=False): producedpopsizes = [] while len(producedpop) < n: if len(pickfrom) > 0: - # If possible, use the already generated + # If possible, use the already generated # individuals (more efficient) aspirant = pickfrom.pop() if acceptfunc(len(aspirant)): @@ -979,7 +979,7 @@ def _genpop(n, pickfrom=[], acceptfunc=lambda s: True, producesizes=False): opRandom = random.random() if opRandom < cxpb: # Crossover - aspirant1, aspirant2 = toolbox.mate(*map(toolbox.clone, + aspirant1, aspirant2 = toolbox.mate(*map(toolbox.clone, toolbox.select(population, 2))) del aspirant1.fitness.values, aspirant2.fitness.values if acceptfunc(len(aspirant1)): @@ -1008,7 +1008,7 @@ def _genpop(n, pickfrom=[], acceptfunc=lambda s: True, producesizes=False): return producedpop - halflifefunc = lambda x:(x*float(alpha) + beta) + halflifefunc = lambda x: (x * float(alpha) + beta) if nbrindsmodel == -1: nbrindsmodel = max(2000, len(population)) @@ -1030,25 +1030,25 @@ def _genpop(n, pickfrom=[], acceptfunc=lambda s: True, producesizes=False): print logbook.stream # Begin the generational process - for gen in range(1, ngen+1): + for gen in range(1, ngen + 1): # Estimation population natural distribution of sizes naturalpop, naturalpopsizes = _genpop(nbrindsmodel, producesizes=True) - naturalhist = [0] * (max(naturalpopsizes)+3) + naturalhist = [0] * (max(naturalpopsizes) + 3) for indsize in naturalpopsizes: # Kernel density estimation application naturalhist[indsize] += 0.4 - naturalhist[indsize-1] += 0.2 - naturalhist[indsize+1] += 0.2 - naturalhist[indsize+2] += 0.1 + naturalhist[indsize - 1] += 0.2 + naturalhist[indsize + 1] += 0.2 + naturalhist[indsize + 2] += 0.1 if indsize - 2 >= 0: - naturalhist[indsize-2] += 0.1 + naturalhist[indsize - 2] += 0.1 # Normalization - naturalhist = [val*len(population)/nbrindsmodel for val in naturalhist] - + naturalhist = [val * len(population) / nbrindsmodel for val in naturalhist] + # Cutoff point selection - sortednatural = sorted(naturalpop, key=lambda ind:ind.fitness) + sortednatural = sorted(naturalpop, key=lambda ind: ind.fitness) cutoffcandidates = sortednatural[int(len(population) * rho - 1):] # Select the cutoff point, with an absolute minimum applied # to avoid weird cases in the first generations @@ -1057,20 +1057,20 @@ def _genpop(n, pickfrom=[], acceptfunc=lambda s: True, producesizes=False): # Compute the target distribution targetfunc = lambda x: (gamma * len(population) * math.log(2) / \ halflifefunc(x)) * math.exp(-math.log(2) * \ - (x - cutoffsize) / halflifefunc(x)) + (x - cutoffsize) / halflifefunc(x)) targethist = [naturalhist[binidx] if binidx <= cutoffsize else \ targetfunc(binidx) for binidx in range(len(naturalhist))] # Compute the probabilities distribution - probhist = [t/n if n > 0 else t for n,t in zip(naturalhist, targethist)] + probhist = [t / n if n > 0 else t for n, t in zip(naturalhist, targethist)] probfunc = lambda s: probhist[s] if s < len(probhist) else targetfunc(s) acceptfunc = lambda s: random.random() <= probfunc(s) # Generate offspring using the acceptance probabilities # previously computed - offspring = _genpop(len(population), pickfrom=naturalpop, + offspring = _genpop(len(population), pickfrom=naturalpop, acceptfunc=acceptfunc, producesizes=False) - + # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) @@ -1080,15 +1080,15 @@ def _genpop(n, pickfrom=[], acceptfunc=lambda s: True, producesizes=False): # Update the hall of fame with the generated individuals if halloffame is not None: halloffame.update(offspring) - + # Replace the current population by the offspring population[:] = offspring - + # Append the current generation statistics to the logbook record = stats.compile(population) if stats else {} logbook.record(gen=gen, nevals=len(invalid_ind), **record) if verbose: - print logbook.stream + print logbook.stream return population, logbook