From 9f619926606dcadb0ef0511bdd0f9f9308e4a907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Michel=20De=20Rainville?= Date: Mon, 8 Feb 2021 21:55:38 -0500 Subject: [PATCH 1/4] Reworking individual creation system --- deap/base.py | 726 +++++++++++++++--------- deap/tools/__init__.py | 104 +++- tests/base/test_attributes.py | 27 + tests/base/test_fitness.py | 191 +++++++ tests/base/test_individual.py | 285 ++++++++++ tests/tools/test_variation_decorator.py | 0 6 files changed, 1036 insertions(+), 297 deletions(-) create mode 100644 tests/base/test_attributes.py create mode 100644 tests/base/test_fitness.py create mode 100644 tests/base/test_individual.py create mode 100644 tests/tools/test_variation_decorator.py diff --git a/deap/base.py b/deap/base.py index a7f3b0446..33ea3e2ea 100644 --- a/deap/base.py +++ b/deap/base.py @@ -18,336 +18,514 @@ to store evolutionary operators, and a virtual :class:`~deap.base.Fitness` class used as base class, for the fitness member of any individual. """ -import sys - -from collections import Sequence +# from collections import Sequence from copy import deepcopy from functools import partial from operator import mul, truediv +from abc import ABC +from collections.abc import Callable, Sequence +from copy import deepcopy +from functools import wraps +from operator import attrgetter +from typing import Any, Callable, Collection, List, Tuple -class Toolbox(object): - """A toolbox for evolution that contains the evolutionary operators. At - first the toolbox contains a :meth:`~deap.toolbox.clone` method that - duplicates any element it is passed as argument, this method defaults to - the :func:`copy.deepcopy` function. and a :meth:`~deap.toolbox.map` - method that applies the function given as first argument to every items - of the iterables given as next arguments, this method defaults to the - :func:`map` function. You may populate the toolbox with any other - function by using the :meth:`~deap.base.Toolbox.register` method. +import numpy - Concrete usages of the toolbox are shown for initialization in the - :ref:`creating-types` tutorial and for tools container in the - :ref:`next-step` tutorial. - """ - def __init__(self): - self.register("clone", deepcopy) - 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 - 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. - :param function: The function to which refer the alias. - :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 - with the original function's instance dictionary, if any. - """ - pfunc = partial(function, *args, **kargs) - pfunc.__name__ = alias - pfunc.__doc__ = function.__doc__ +class Fitness: + """Class for initializing fitnesses in an individual. - if hasattr(function, "__dict__") and not isinstance(function, type): - # 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()) + The fitness is a measure of quality of a solution. If *initval* is + provided as a single value or a tuple, the fitness is initalized using + those values, otherwise it is empty (or invalid). The values are converted + to numpy.float64 when set, ensure your values can be safely casted using + ``numpy.asarray(value, dtype=numpy.float64)``. - setattr(self, alias, pfunc) + Fitnesses may be compared using the ``>``, ``<``, ``>=``, ``<=``, ``==``, + ``!=``. The comparisons are made through a strict dominance criteria meaning + that two fitnesses are considered equal if neither dominates the other. - def unregister(self, alias): - """Unregister *alias* from the toolbox. + Constraint handling can be provided. A fitness violating a constraint (invalid) + will always be dominated by a valid fitness. Two invalid fitnesses compare + as if they were valid. - :param alias: The name of the operator to remove from the toolbox. - """ - delattr(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 - relevant in most cases, it may have an impact on distributed - environments like multiprocessing. - A function can still be decorated manually before it is added to - the toolbox (using the @ notation) in order to be picklable. - """ - pfunc = getattr(self, alias) - function, args, kargs = pfunc.func, pfunc.args, pfunc.keywords - for decorator in decorators: - function = decorator(function) - self.register(alias, function, *args, **kargs) + Arguments: + objectives (scalar or sequence): Usualy one of :data:`Fitness.MAXIMIZE` + or :data:`Fitness.MINIMIZE` or a tuple thereof. + initval (scalar or sequence): Initial value for a fitness. When not provided + the fitness remains invalid until its value is set. + violated_constraints (sequence of boolean): A list of booleans which assess if + if a constraint is violated. -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). + Examples: + Fitnesses can be compared directly:: - :param values: The initial values of the fitness as a tuple, optional. + >>> obj = Fitness.MAXIMIZE + >>> f1 = Fitness(obj, initval=2) + >>> f2 = Fitness(obj, initval=3) - Fitnesses may be compared using the ``>``, ``<``, ``>=``, ``<=``, ``==``, - ``!=``. The comparison of those operators is made lexicographically. - Maximization and minimization are taken care off by a multiplication - between the :attr:`weights` and the fitness :attr:`values`. The comparison - can be made between fitnesses of different size, if the fitnesses are - equal until the extra elements, the longer fitness will be superior to the - shorter. - - Different types of fitnesses are created in the :ref:`creating-types` - tutorial. - - .. note:: - When comparing fitness values that are **minimized**, ``a > b`` will - return :data:`True` if *a* is **smaller** than *b*. - """ + >>> f1 > f2 + False + >>> f1 < f2 + True - 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 - weights must be defined as a tuple where each element is associated to an - objective. A negative weight element corresponds to the minimization of - the associated objective and positive weight to the maximization. + Multiobjective fitnesses compare using strict domimance:: - .. note:: - If weights is not defined during subclassing, the following error will - occur at instantiation of a subclass fitness object: + >>> mobj = (Fitness.MAXIMIZE, Fitness.MAXIMIZE) + >>> f1 = Fitness(mobj, initval=(2, 3)) + >>> f2 = Fitness(mobj, initval=(2, 4)) - ``TypeError: Can't instantiate abstract with - abstract attribute weights.`` - """ + >>> f1 < f2 + True + >>> f1 > f2 + False + + Strict dominance imply that all objective must be atleast be better + or equal for one fitness to dominate the other. In this example neither + fitness dominates the other:: + + >>> mobj = (Fitness.MAXIMIZE, Fitness.MAXIMIZE) + >>> f1 = Fitness(mobj, initval=(2, 3)) + >>> f2 = Fitness(mobj, initval=(1, 4)) + + >>> f1 < f2 + False + >>> f1 > f2 + False + >>> f1 == f2 + True + + Constraint handling can be done inside fitnesses:: + + >>> f1 = Fitness(Fitness.MAXIMIZE, + ... initval=11, + ... violated_constraints=[True, False]) + >>> f1.valid + False + + >>> f2 = Fitness(Fitness.MAXIMIZE, + ... initval=5, + ... violated_constraints=[False, False]) + >>> f2.valid + True + + >>> f2 > f1 + True - 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. """ + MAXIMIZE = 1.0 + MINIMIZE = -1.0 - 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." - % 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)) - except TypeError: - _, _, traceback = sys.exc_info() - raise TypeError, ("Both weights and assigned values must be a " - "sequence of numbers when assigning to values of " - "%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 = () - - values = property(getValues, setValues, delValues, - ("Fitness values. Use directly ``individual.fitness.values = values`` " - "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 - strictly better. - - :param obj: Slice indicating on which objectives the domination is - tested. The default value is `slice(None)`, representing - every objectives. - """ - not_equal = False - for self_wvalue, other_wvalue in zip(self.wvalues[obj], other.wvalues[obj]): - if self_wvalue > other_wvalue: - not_equal = True - elif self_wvalue < other_wvalue: - return False - return not_equal + def __init__(self, objectives, initval=(), violated_constraints=()): + if not isinstance(objectives, Sequence): + objectives = (objectives,) + + self.objectives = numpy.asarray(objectives, dtype=numpy.float64) + self._value: numpy.ndarray = numpy.asarray((), dtype=numpy.float64) + self._wvalue: numpy.ndarray = numpy.array((), dtype=numpy.float64) + self._violated_constraints = violated_constraints + + self.value = initval + + def _getvalue(self) -> numpy.ndarray: + """Raw values of this fitness as numpy.array of type float64.""" + return self._value + + def _setvalue(self, val): + if val is None: + val = () + + if isinstance(val, str) \ + or (not isinstance(val, Sequence) + and not isinstance(val, numpy.ndarray)): + val = (val,) + + if len(val) > 0 and len(val) != len(self.objectives): + raise ValueError(f"setting fitness with {len(val)} " + f"value{'s' if len(val) > 1 else ''}, " + f"{len(self.objectives)} expected.") + + self._value = numpy.asarray(val, dtype=numpy.float64) + + if len(val) > 0: + # Store objective relative values for performance + self._wvalue = self._value * self.objectives + + def _delvalue(self): + self._value = numpy.asarray((), dtype=numpy.float64) + + value = property(_getvalue, _setvalue, _delvalue) + + @property + def violated_constraints(self): + """Status of constraint violations for this fitness.""" + return self._violated_constraints + + @violated_constraints.setter + def violated_constraints(self, violated_constraints): + self._violated_constraints = violated_constraints + + @violated_constraints.deleter + def violated_constraints(self): + self._violated_constraints = () + + def reset(self): + """Reset the values and constraint violations of this fitness.""" + del self.value + del self.violated_constraints @property def valid(self): - """Assess if a fitness is valid or not.""" - return len(self.wvalues) != 0 + """Assess if a fitness is valid or not. Either the fitness never + received a value or it violates one or more constraints.""" + return self.evaluated and not any(self.violated_constraints) - def __hash__(self): - return hash(self.wvalues) + @property + def evaluated(self): + """Assess if a fitness received a value.""" + return self._value.size > 0 - def __gt__(self, other): - return not self.__le__(other) + def __lt__(self, other: "Fitness") -> bool: + return other._dominates(self) - def __ge__(self, other): - return not self.__lt__(other) + def __le__(self, other: "Fitness") -> bool: + return self < other or self == other - def __le__(self, other): - return self.wvalues <= other.wvalues + def __eq__(self, other: "Fitness") -> bool: + return not self._dominates(other) and not other._dominates(self) - def __lt__(self, other): - return self.wvalues < other.wvalues + def __ge__(self, other: "Fitness") -> bool: + return not self < other - def __eq__(self, other): - return self.wvalues == other.wvalues + def __gt__(self, other: "Fitness") -> bool: + return not self <= other - def __ne__(self, other): - return not self.__eq__(other) + def _dominates(self, other: "Fitness") -> bool: + if not self.evaluated or not other.evaluated: + raise ValueError("Cannot compare fitnesses without values in" + f", {self} dominates {other}") - def __deepcopy__(self, memo): - """Replace the basic deepcopy function with a faster one. + self_valid = self.valid + other_valid = other.valid - 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__() - copy_.wvalues = self.wvalues - return copy_ + if self_valid and not other_valid: + return True + elif not self_valid and other_valid: + return False + + if numpy.any(self._wvalue < other._wvalue): + return False + if numpy.any(self._wvalue > other._wvalue): + return True + return False def __str__(self): - """Return the values of the Fitness object.""" - return str(self.values if self.valid else tuple()) + return str(self._value) - def __repr__(self): - """Return the Python code to build a copy of the object.""" - return "%s.%s(%r)" % (self.__module__, self.__class__.__name__, - self.values if self.valid else tuple()) +class Attribute: + """General attribute in an :class:`Individual`. -def _violates_constraint(fitness): - return not fitness.valid \ - and fitness.constraint_violation is not None \ - and sum(fitness.constraint_violation) > 0 + Attribute are placeholders in an :class:`Individual`. Individuals will initialize + attribute as properties allowing to set and get their values properly. + Arguments: + initval (any): Initial value for the attribute. Can be of any type. -class ConstrainedFitness(Fitness): - def __init__(self, values=(), constraint_violation=None): - super(ConstrainedFitness, self).__init__(values) - self.constraint_violation = constraint_violation + """ + def __init__(self, initval: Any = None): + self.value = initval - @Fitness.values.deleter - def values(self): - self.wvalues = () - self.constraint_violation = None + def _getvalue(self) -> None: + return self.value - def __gt__(self, other): - return not self.__le__(other) + def _setvalue(self, val: Any) -> None: + self.value = val - def __ge__(self, other): - return not self.__lt__(other) + def _delvalue(self) -> None: + self.value = None - def __le__(self, other): - self_violates_constraints = _violates_constraint(self) - other_violates_constraints = _violates_constraint(other) + def __str__(self) -> str: + return str(self.value) - if self_violates_constraints and other_violates_constraints: - return True - elif self_violates_constraints: - return True - elif other_violates_constraints: - return False - return self.wvalues <= other.wvalues +class Individual: + """Base class for individuals. - def __lt__(self, other): - self_violates_constraints = _violates_constraint(self) - other_violates_constraints = _violates_constraint(other) + Abstract base class for individuals. This class turns fitness and attribute + members into property like objects that can be set directly with their values. - if self_violates_constraints and other_violates_constraints: - return False - elif self_violates_constraints: - return True - elif other_violates_constraints: - return False + The individual is a mix of attribute and fitnesses. An individuals can have as + many attribute and fitnesses as desired. However, when having multiple attribute + or fitnesses the key argument in variations and/or selections becomes mandatory. - return self.wvalues < other.wvalues + Note: + Predefined algorithms are not capable of handling multiple fitnesses. - def __eq__(self, other): - self_violates_constraints = _violates_constraint(self) - other_violates_constraints = _violates_constraint(other) + Examples: - if self_violates_constraints and other_violates_constraints: - return True - elif self_violates_constraints: - return False - elif other_violates_constraints: - return False + An individual with a single array of attribute and a single fitness. - return self.wvalues == other.wvalues + >>> from operator import attrgetter + >>> import numpy - def __ne__(self, other): - return not self.__eq__(other) + >>> class AwesomeIndividual(Individual): + ... def __init__(self, alpha, beta, size): + ... super().__init__() + ... self.fitness = Fitness(Fitness.MINIMIZE) + ... values = numpy.random.beta(alpha, beta, size) + ... self.attrs = Attribute(values) - def dominates(self, other): - self_violates_constraints = _violates_constraint(self) - other_violates_constraints = _violates_constraint(other) + >>> i1 = AwesomeIndividual(alpha=1, beta=2, size=5) + >>> i2 = AwesomeIndividual(alpha=1, beta=2, size=5) + >>> i1.fitness = 2 + >>> i2.fitness = 5 + >>> ordered = sorted([i1, i2], key=attrgetter("fitness")) + >>> ordered == [i2, i1] + True - if self_violates_constraints and other_violates_constraints: - return False - elif self_violates_constraints: - return False - elif other_violates_constraints: - return True + An individual with two arrays of attribute and a single fitness. + + >>> from operator import attrgetter + >>> import numpy + + >>> class AwesomeIndividual(Individual): + ... def __init__(self, alpha, beta, lam, fsize, isize): + ... super().__init__() + ... self.fitness = Fitness(Fitness.MINIMIZE) + ... val_float = numpy.random.beta(alpha, beta, fsize) + ... self.attrs_float = Attribute(val_float) + ... val_int = numpy.random.poisson(lam, isize) + ... self.attrs_int = Attribute(val_int) + + >>> i1 = AwesomeIndividual(alpha=1, beta=2, lam=5, fsize=5, isize=10) + + An individual with a single array of attribute and two fitnesses. + + >>> from operator import attrgetter + >>> import numpy + + >>> class AwesomeIndividual(Individual): + ... def __init__(self, alpha, beta, size): + ... super().__init__() + ... self.fitness_a = Fitness(Fitness.MINIMIZE) + ... self.fitness_b = Fitness((Fitness.MINIMIZE, Fitness.MAXIMIZE)) + ... val_float = numpy.random.beta(alpha, beta, size) + ... self.attrs_float = Attribute(val_float) + + >>> i1 = AwesomeIndividual(alpha=1, beta=2, size=5) + >>> i1.fitness_a = 2 + >>> i1.fitness_b = (5, 3) + + """ + def __init__(self): + self._fitnesses = dict() + self._attribute = dict() + + def _register_fitness(self, name, fitness): + self._fitnesses[name] = fitness + + def _register_attribute(self, name, attribute): + self._attribute[name] = attribute + + def _register_property(self, name, type_): + def _getter(self): + # Return the object and not the values to be able to use them + # with all their instance methods (i.e., return a Fitness not a ndarray) + return self.__getattribute__(type_)[name] + + def _setter(self, val): + self.__getattribute__(type_)[name]._setvalue(val) + + def _deletter(self): + self.__getattribute__(type_)[name]._delvalue() + + # Property is a class attribute + setattr(Individual, name, property(_getter, _setter, _deletter)) + + def _getattribute(self, name=None): + """Retrieve the attribute of this individual. If *name* is provided, retrieve + the attribute with name *name*. Name is mandatory for individuals having + more than one attribute. + + Note: + This method is generally for internal use. + """ + if name is None and len(self._attribute) == 1: + name = next(iter(self._attribute.keys())) + elif name is None: + raise AttributeError("individual with multiple attribute " + "require the 'name' argument in operators") + return getattr(self, name) + + def _setattribute(self, name=None, value=None): + """Set the attribute of this individual. If *name* is provided, set + the attribute with name *name*. Name is mandatory for individuals having + more than one attribute. + + Note: + This method is generally for internal use. + """ + if name is None and len(self._attribute) == 1: + name = next(iter(self._attribute.keys())) + elif name is None: + raise AttributeError("individual with multiple attribute " + "require the 'name' argument in operators") + return setattr(self, name, value) + + def _getfitness(self, name=None): + """Retrieve the fitness of this individual. If *name* is provided, retrieve + the fitness with name *name*. Name is mandatory for individuals having + more than one fitness. + + Note: + This method is generally for internal use. + """ + if name is None and len(self._fitnesses) == 1: + name = next(iter(self._fitnesses.keys())) + elif name is None: + raise AttributeError("individual with multiple fitnesses " + "require the 'name' argument in operators") + return getattr(self, name) + + def _setfitness(self, name=None, value=None): + """Set the fitness of this individual. If *name* is provided, set + the fitness with name *name*. Name is mandatory for individuals having + more than one fitness. + + Note: + This method is generally for internal use. + """ + if name is None and len(self._fitnesses) == 1: + name = next(iter(self._fitnesses.keys())) + elif name is None: + raise AttributeError("individual with multiple fitnesses " + "require the 'name' argument in operators") + return setattr(self, name, value) + + def invalidate_fitness(self): + """Invalidate all fitnesses of this individual. + + Note: + This method is generally for internal use. + """ + for f in self._fitnesses.values(): + f.reset() + + def __setattr__(self, name, value): + if isinstance(value, Fitness): + if getattr(self, "_fitnesses", None) is None: + self._fitnesses = dict() + + self._register_fitness(name, value) + self._register_property(name, "_fitnesses") + + elif isinstance(value, Attribute): + if getattr(self, "_attribute", None) is None: + self._attribute = dict() + + self._register_attribute(name, value) + self._register_property(name, "_attribute") - return super(ConstrainedFitness, self).dominates(other) + else: + super().__setattr__(name, value) def __str__(self): - """Return the values of the Fitness object.""" - return str((self.values if self.valid else tuple(), self.constraint_violation)) - - def __repr__(self): - """Return the Python code to build a copy of the object.""" - return "%s.%s(%r, %r)" % (self.__module__, self.__class__.__name__, - self.values if self.valid else tuple(), - self.constraint_violation) \ No newline at end of file + str_values = ', '.join('='.join((name, str(attr.getvalue()))) + for name, attr in self._attribute.items()) + return f"{self.__class__.__name__}({str_values})" + + +# class Toolbox(object): +# """A toolbox for the evolutionary operators. At +# first the toolbox contains a :meth:`~deap.toolbox.clone` method that +# duplicates any element it is passed as argument, this method defaults to +# the :func:`copy.deepcopy` function. and a :meth:`~deap.toolbox.map` +# method that applies the function given as first argument to every items +# of the iterables given as next arguments, this method defaults to the +# :func:`map` function. You may populate the toolbox with any other +# function by using the :meth:`~deap.base.Toolbox.register` method. + +# Concrete usages of the toolbox are shown for initialization in the +# :ref:`creating-types` tutorial and for tools container in the +# :ref:`next-step` tutorial. +# """ + +# def __init__(self): +# self.register("clone", deepcopy) +# 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 +# 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. +# :param function: The function to which refer the alias. +# :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 attribute :attr:`__name__` +# set to the alias and :attr:`__doc__` set to the original function's +# documentation. The :attr:`__dict__` attribute will also be updated +# with the original function's instance dictionary, if any. +# """ +# 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 +# # 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) + +# 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 +# relevant in most cases, it may have an impact on distributed +# environments like multiprocessing. +# A function can still be decorated manually before it is added to +# the toolbox (using the @ notation) in order to be picklable. +# """ +# pfunc = getattr(self, alias) +# function, args, kargs = pfunc.func, pfunc.args, pfunc.keywords +# for decorator in decorators: +# function = decorator(function) +# self.register(alias, function, *args, **kargs) diff --git a/deap/tools/__init__.py b/deap/tools/__init__.py index 0e9c0dab8..e8b5ef0bd 100644 --- a/deap/tools/__init__.py +++ b/deap/tools/__init__.py @@ -1,17 +1,3 @@ -# This file is part of DEAP. -# -# DEAP is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# DEAP is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with DEAP. If not, see . """The :mod:`~deap.tools` module contains the operators for evolutionary algorithms. They are used to modify, select and move the individuals in their environment. The set of operators it contains are readily usable in the @@ -20,12 +6,84 @@ :class:`Statistics`, :class:`HallOfFame`, and :class:`History`. """ -from .constraint import * -from .crossover import * -from .emo import * -from .indicator import * -from .init import * -from .migration import * -from .mutation import * -from .selection import * -from .support import * +from copy import deepcopy +from functools import wraps +from typing import Callable, List + +# from .constraint import * +# from .crossover import * +# from .emo import * +# from .indicator import * +# from .init import * +# from .migration import * +# from .mutation import * +# from .selection import * +# from .support import * + +from ..base import Individual + + +def variation(func: Callable): + """Decorator for variation functions. With this decorator, variations + (crossovers and mutations) can be implemented as if they received attributes + directly without regards to the attribute name inside the individual. The + returned individuals are deepcopies of the original individuals with their + attributes changed to the values returned by the variation. It is assumed + the new attributes are different from the old ones and thus, the fitness is + invalidated for each individual that received new attributes. + + Arguments: + func (callable): A function operating on attributes of an individual. + + Returns: + (callable): The decorated variation operator. + + Note: + The callable shall return at most *n* new attributes, where *n* is the + number of input attributes. + """ + + @wraps(func) + def wrapper(*args, **kwargs): + key = kwargs.pop("key", None) + individuals: List[Individual] = list() + variator_args = list() + variator_kwargs = dict() + + # Deepcopy passed individuals + for argument in args: + if isinstance(argument, Individual): + copy_ = deepcopy(argument) + individuals.append(copy_) + argument = copy_._getattributes(key) + + variator_args.append(argument) + + # Deepcopy passed individuals + for keyword, argument in kwargs.items(): + if isinstance(argument, Individual): + copy_ = deepcopy(argument) + individuals.append(copy_) + argument = copy_._getattributes(key) + + variator_kwargs[keyword] = argument + + # Get variated attributes + attributes = func(*variator_args, **variator_kwargs) + + # Replace the attributes by the variated attribues inside each + # individual + if len(individuals) > 1: + for i, a in zip(individuals, attributes): + i._setattributes(key, a) + i.invalidate_fitness() + + else: + individuals = individuals[0] + individuals._setattributes(key, attributes) + individuals.invalidate_fitness() + + # Return the whole individuals with their fitness invalidated + return individuals + + return wrapper diff --git a/tests/base/test_attributes.py b/tests/base/test_attributes.py new file mode 100644 index 000000000..255fd2fba --- /dev/null +++ b/tests/base/test_attributes.py @@ -0,0 +1,27 @@ +import unittest + +import numpy + +from deap.base import Attribute + + +class TestBaseAttribute(unittest.TestCase): + def test_get_value(self): + a = Attribute(3) + self.assertEqual(a._getvalue(), 3) + + def test_set_value(self): + a = Attribute() + a._setvalue(3) + self.assertEqual(a.value, 3) + + def test_del_value(self): + a = Attribute(3) + a._delvalue() + self.assertEqual(a.value, None) + + def test_str(self): + value = 3 + a = Attribute(value) + str_a = str(a) + self.assertEqual(str_a, str(value)) diff --git a/tests/base/test_fitness.py b/tests/base/test_fitness.py new file mode 100644 index 000000000..2db19abc0 --- /dev/null +++ b/tests/base/test_fitness.py @@ -0,0 +1,191 @@ +import unittest + +import numpy + +from deap.base import Fitness + + +MAX = Fitness.MAXIMIZE +MIN = Fitness.MINIMIZE + + +class TestBaseFitness(unittest.TestCase): + def test_is_greater_maximize_single_value(self): + f1 = Fitness(MAX, 3) + f2 = Fitness(MAX, 0) + self.assertGreater(f1, f2) + + def test_is_less_maximize_single_value(self): + f1 = Fitness(MAX, 3) + f2 = Fitness(MAX, 0) + self.assertLess(f2, f1) + + def test_is_equal_maximize_single_value(self): + f1 = Fitness(MAX, 3) + f2 = Fitness(MAX, 3) + self.assertEqual(f1, f2) + + def test_is_greater_minimize_single_value(self): + f1 = Fitness(MIN, 0) + f2 = Fitness(MIN, 3) + self.assertGreater(f1, f2) + + def test_is_less_minimize_single_value(self): + f1 = Fitness(MIN, 0) + f2 = Fitness(MIN, 3) + self.assertLess(f2, f1) + + def test_is_equal_minimize_single_value(self): + f1 = Fitness(MIN, 3) + f2 = Fitness(MIN, 3) + self.assertEqual(f1, f2) + + def test_dominates_multi_value(self): + f1 = Fitness((MAX, MAX), (3, 3)) + f2 = Fitness((MAX, MAX), (0, 0)) + self.assertGreater(f1, f2) + + def test_is_dominated_multi_value(self): + f1 = Fitness((MAX, MAX), (3, 3)) + f2 = Fitness((MAX, MAX), (0, 0)) + self.assertLess(f2, f1) + + def test_equal_multi_value_different(self): + f1 = Fitness((MAX, MAX), (3, 3)) + f2 = Fitness((MAX, MAX), (2, 4)) + self.assertEqual(f1, f2) + + def test_equal_multi_value_same(self): + f1 = Fitness((MAX, MAX), (3, 3)) + f2 = Fitness((MAX, MAX), (3, 3)) + self.assertEqual(f1, f2) + + def test_different_len_raises(self): + self.assertRaises(ValueError, Fitness, (MAX, MAX), 1) + self.assertRaises(ValueError, Fitness, MAX, (1, 1)) + + def test_string_initvalue_fails(self): + self.assertRaises(ValueError, Fitness, MAX, "z") + + def test_string_number_initvalue(self): + f = Fitness(MAX, "3") + self.assertEqual(f.value, 3) + + def test_string_numbers_initvalue(self): + f = Fitness(MAX, "33") + self.assertEqual(f.value, 33) + + def test_none_initvalue(self): + f = Fitness(MAX, None) + self.assertFalse(f.evaluated) + self.assertFalse(f.valid) + + def test_no_initvalue_invalid(self): + f = Fitness(MAX) + self.assertFalse(f.valid) + + def test_no_initvalue_not_evaluated(self): + f = Fitness(MAX) + self.assertFalse(f.evaluated) + + def test_no_multi_initvalue_invalid(self): + f = Fitness((MAX, MAX)) + self.assertFalse(f.valid) + + def test_no_multi_initvalue_not_evaluated(self): + f = Fitness((MAX, MAX)) + self.assertFalse(f.evaluated) + + def test_single_value_valid(self): + f = Fitness(MAX, 3) + self.assertTrue(f.valid) + + def test_single_value_evaluated(self): + f = Fitness(MAX, 3) + self.assertTrue(f.evaluated) + + def test_multi_value_valid(self): + f = Fitness((MAX, MAX), (3, 3)) + self.assertTrue(f.valid) + + def test_multi_value_evaluated(self): + f = Fitness((MAX, MAX), (3, 3)) + self.assertTrue(f.evaluated) + + def test_setget_value_single_value(self): + value = 3 + f = Fitness(MAX) + f.value = value + self.assertEqual(f.value, value) + + def test_setget_value_single_tuple_value(self): + value = (3,) + f = Fitness(MAX) + f.value = value + self.assertEqual(f.value, value) + + def test_setget_value_mutli_value(self): + value = (3, 3) + f = Fitness((MAX, MAX)) + f.value = value + numpy.testing.assert_array_equal(f.value, value) + + def test_deleted_single_value_invalid(self): + f = Fitness(MAX, 3) + del f.value + self.assertFalse(f.valid) + + def test_deleted_single_value_not_evaluated(self): + f = Fitness(MAX, 3) + del f.value + self.assertFalse(f.evaluated) + + def test_deleted_multi_value_invalid(self): + f = Fitness((MAX, MAX), (3, 3)) + del f.value + self.assertFalse(f.valid) + + def test_deleted_multi_value_not_evaluated(self): + f = Fitness((MAX, MAX), (3, 3)) + del f.value + self.assertFalse(f.evaluated) + + def test_is_greater_maximize_single_value_constraint_violated(self): + f1 = Fitness(MAX, 0, [False]) + f2 = Fitness(MAX, 3, [True]) + self.assertGreater(f1, f2) + + def test_is_less_maximize_single_value_constraint_violated(self): + f1 = Fitness(MAX, 3, [True]) + f2 = Fitness(MAX, 0, [False]) + self.assertLess(f1, f2) + + def test_is_equal_maximize_single_value_constraint_violated(self): + f1 = Fitness(MAX, 3, [True]) + f2 = Fitness(MAX, 3, [True]) + self.assertEqual(f1, f2) + + def test_is_equal_maximize_single_value_constraint_not_violated(self): + f1 = Fitness(MAX, 3, [False]) + f2 = Fitness(MAX, 3, [False]) + self.assertEqual(f1, f2) + + def test_is_greater_maximize_multi_value_constraint_violated(self): + f1 = Fitness((MAX, MAX), (0, 0), [False]) + f2 = Fitness((MAX, MAX), (3, 3), [True]) + self.assertGreater(f1, f2) + + def test_is_less_maximize_multi_value_constraint_violated(self): + f1 = Fitness((MAX, MAX), (3, 3), [True]) + f2 = Fitness((MAX, MAX), (0, 0), [False]) + self.assertLess(f1, f2) + + def test_is_equal_maximize_multi_value_constraint_violated(self): + f1 = Fitness((MAX, MAX), (3, 3), [True]) + f2 = Fitness((MAX, MAX), (3, 3), [True]) + self.assertEqual(f1, f2) + + def test_is_equal_maximize_multi_value_constraint_not_violated(self): + f1 = Fitness((MAX, MAX), (3, 3), [False]) + f2 = Fitness((MAX, MAX), (3, 3), [False]) + self.assertEqual(f1, f2) diff --git a/tests/base/test_individual.py b/tests/base/test_individual.py new file mode 100644 index 000000000..8054cd579 --- /dev/null +++ b/tests/base/test_individual.py @@ -0,0 +1,285 @@ +import unittest + +import numpy + +from deap.base import Attribute, Fitness, Individual + + +class TestIndividualFitness(unittest.TestCase): + def test_register_single_fitness(self): + fitness = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness = fitness + self.assertIn("my_fitness", ind._fitnesses) + self.assertIs(ind._fitnesses["my_fitness"], fitness) + + def test_register_multi_fitnesses(self): + fitness1 = Fitness(Fitness.MAXIMIZE) + fitness2 = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness1 = fitness1 + ind.my_fitness2 = fitness2 + self.assertIn("my_fitness1", ind._fitnesses) + self.assertIs(ind._fitnesses["my_fitness1"], fitness1) + self.assertIn("my_fitness2", ind._fitnesses) + self.assertIs(ind._fitnesses["my_fitness2"], fitness2) + + def test_get_single_fitness_no_key(self): + fitness = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness = fitness + self.assertIs(ind._getfitness(), fitness) + + def test_get_single_fitness_with_key(self): + fitness = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness = fitness + self.assertIs(ind._getfitness("my_fitness"), fitness) + + def test_get_multi_fitnesses_no_key_raises(self): + fitness1 = Fitness(Fitness.MAXIMIZE) + fitness2 = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness1 = fitness1 + ind.my_fitness2 = fitness2 + self.assertRaises(AttributeError, ind._getfitness) + + def test_get_multi_fitnesses_with_key(self): + fitness1 = Fitness(Fitness.MAXIMIZE) + fitness2 = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness1 = fitness1 + ind.my_fitness2 = fitness2 + self.assertIs(ind._getfitness("my_fitness1"), fitness1) + self.assertIs(ind._getfitness("my_fitness2"), fitness2) + + def test_set_single_fitness_no_key(self): + fitness = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness = fitness + ind._setfitness(None, (3,)) + self.assertIs(ind._getfitness("my_fitness"), fitness) + numpy.testing.assert_array_equal(fitness.value, + numpy.asarray((3,), + dtype=numpy.float64)) + + def test_set_single_fitness_with_key(self): + fitness = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness = fitness + ind._setfitness("my_fitness", (3,)) + self.assertIs(ind._getfitness("my_fitness"), fitness) + numpy.testing.assert_array_equal(fitness.value, + numpy.asarray((3,), + dtype=numpy.float64)) + + def test_set_single_fitness_direct(self): + fitness = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness = fitness + ind.my_fitness = (3,) + self.assertIs(ind._getfitness("my_fitness"), fitness) + numpy.testing.assert_array_equal(fitness.value, + numpy.asarray((3,), + dtype=numpy.float64)) + + def test_set_single_fitness_single_value(self): + fitness = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness = fitness + ind._setfitness("my_fitness", 3) + self.assertIs(ind._getfitness("my_fitness"), fitness) + numpy.testing.assert_array_equal(fitness.value, + numpy.asarray((3,), + dtype=numpy.float64)) + + def test_set_multi_fitnesses_no_key_raises(self): + fitness1 = Fitness(Fitness.MAXIMIZE) + fitness2 = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness1 = fitness1 + ind.my_fitness2 = fitness2 + self.assertRaises(AttributeError, ind._setfitness, None, (3,)) + + def test_set_multi_fitnesses_with_key(self): + fitness1 = Fitness(Fitness.MAXIMIZE) + fitness2 = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness1 = fitness1 + ind.my_fitness2 = fitness2 + ind._setfitness("my_fitness1", (3,)) + self.assertIs(ind._getfitness("my_fitness1"), fitness1) + numpy.testing.assert_array_equal(fitness1.value, + numpy.asarray((3,), + dtype=numpy.float64)) + ind._setfitness("my_fitness2", (5,)) + self.assertIs(ind._getfitness("my_fitness2"), fitness2) + numpy.testing.assert_array_equal(fitness2.value, + numpy.asarray((5,), + dtype=numpy.float64)) + + def test_set_multi_fitnesses_direct(self): + fitness1 = Fitness(Fitness.MAXIMIZE) + fitness2 = Fitness(Fitness.MAXIMIZE) + ind = Individual() + ind.my_fitness1 = fitness1 + ind.my_fitness2 = fitness2 + ind.my_fitness1 = (3,) + self.assertIs(ind._getfitness("my_fitness1"), fitness1) + numpy.testing.assert_array_equal(fitness1.value, + numpy.asarray((3,), + dtype=numpy.float64)) + ind.my_fitness2 = (5,) + self.assertIs(ind._getfitness("my_fitness2"), fitness2) + numpy.testing.assert_array_equal(fitness2.value, + numpy.asarray((5,), + dtype=numpy.float64)) + + def test_invalidate_single_fitness(self): + fitness = Fitness(Fitness.MAXIMIZE, 3) + ind = Individual() + ind.my_fitness = fitness + ind.invalidate_fitness() + self.assertIs(ind.my_fitness, fitness) + self.assertFalse(ind.my_fitness.valid) + + def test_invalidate_single_fitness_constraints(self): + fitness = Fitness(Fitness.MAXIMIZE, 3, [True]) + ind = Individual() + ind.my_fitness = fitness + ind.invalidate_fitness() + self.assertEqual(len(ind.my_fitness.violated_constraints), 0) + + def test_invalidate_multi_fitness(self): + fitness1 = Fitness(Fitness.MAXIMIZE, 3) + fitness2 = Fitness(Fitness.MAXIMIZE, 4) + ind = Individual() + ind.my_fitness1 = fitness1 + ind.my_fitness2 = fitness2 + ind.invalidate_fitness() + self.assertIs(ind.my_fitness1, fitness1) + self.assertIs(ind.my_fitness2, fitness2) + self.assertFalse(ind.my_fitness1.valid) + self.assertFalse(ind.my_fitness2.valid) + + +class TestIndividualAttribute(unittest.TestCase): + def test_register_single_attribute(self): + attr = Attribute() + ind = Individual() + ind.my_attr = attr + self.assertIn("my_attr", ind._attribute) + self.assertIs(ind._attribute["my_attr"], attr) + + def test_register_multi_attributes(self): + attr1 = Attribute() + attr2 = Attribute() + ind = Individual() + ind.my_attr1 = attr1 + ind.my_attr2 = attr2 + self.assertIn("my_attr1", ind._attribute) + self.assertIs(ind._attribute["my_attr1"], attr1) + self.assertIn("my_attr2", ind._attribute) + self.assertIs(ind._attribute["my_attr2"], attr2) + + def test_get_single_attribute_no_key(self): + attr = Attribute() + ind = Individual() + ind.my_attr = attr + self.assertIs(ind._getattribute(), attr) + + def test_get_single_attribute_with_key(self): + attr = Attribute() + ind = Individual() + ind.my_attr = attr + self.assertIs(ind._getattribute("my_attr"), attr) + + def test_get_single_attribute_direct(self): + attr = Attribute() + ind = Individual() + ind.my_attr = attr + self.assertIs(ind.my_attr, attr) + + def test_get_multi_attributes_no_key_raises(self): + attr1 = Attribute() + attr2 = Attribute() + ind = Individual() + ind.my_attr1 = attr1 + ind.my_attr2 = attr2 + self.assertRaises(AttributeError, ind._getattribute) + + def test_get_multi_fitnesses_with_key(self): + attr1 = Attribute() + attr2 = Attribute() + ind = Individual() + ind.my_attr1 = attr1 + ind.my_attr2 = attr2 + self.assertIs(ind._getattribute("my_attr1"), attr1) + self.assertIs(ind._getattribute("my_attr2"), attr2) + + def test_get_multi_fitnesses_direct(self): + attr1 = Attribute() + attr2 = Attribute() + ind = Individual() + ind.my_attr1 = attr1 + ind.my_attr2 = attr2 + self.assertIs(ind.my_attr1, attr1) + self.assertIs(ind.my_attr2, attr2) + + def test_set_single_attribute_no_key(self): + attr = Attribute() + ind = Individual() + ind.my_attr = attr + ind._setattribute(None, "abc") + self.assertIs(ind._getattribute("my_attr"), attr) + self.assertEqual(ind.my_attr.value, "abc") + + def test_set_single_attribute_with_key(self): + attr = Attribute() + ind = Individual() + ind.my_attr = attr + ind._setattribute("my_attr", "abc") + self.assertIs(ind._getattribute("my_attr"), attr) + self.assertEqual(ind.my_attr.value, "abc") + + def test_set_single_attribute_direct(self): + attr = Attribute() + ind = Individual() + ind.my_attr = attr + ind.my_attr = "abc" + self.assertIs(ind._getattribute("my_attr"), attr) + self.assertEqual(ind.my_attr.value, "abc") + + def test_set_multi_attributes_no_key_raises(self): + attr1 = Attribute() + attr2 = Attribute() + ind = Individual() + ind.my_attr1 = attr1 + ind.my_attr2 = attr2 + self.assertRaises(AttributeError, ind._setattribute, None, "abc") + + def test_set_multi_attributes_with_key(self): + attr1 = Attribute() + attr2 = Attribute() + ind = Individual() + ind.my_attr1 = attr1 + ind.my_attr2 = attr2 + ind._setattribute("my_attr1", "abc") + self.assertIs(ind._getattribute("my_attr1"), attr1) + self.assertEqual(attr1.value, "abc") + ind._setattribute("my_attr2", "def") + self.assertIs(ind._getattribute("my_attr2"), attr2) + numpy.testing.assert_array_equal(attr2.value, "def") + + def test_set_multi_attributes_direct(self): + attr1 = Attribute() + attr2 = Attribute() + ind = Individual() + ind.my_attr1 = attr1 + ind.my_attr2 = attr2 + ind.my_attr1 = "abc" + self.assertIs(ind._getattribute("my_attr1"), attr1) + self.assertEqual(attr1.value, "abc") + ind.my_attr2 = "def" + self.assertIs(ind._getattribute("my_attr2"), attr2) + numpy.testing.assert_array_equal(attr2.value, "def") diff --git a/tests/tools/test_variation_decorator.py b/tests/tools/test_variation_decorator.py new file mode 100644 index 000000000..e69de29bb From ec9ba5e2e01f3e326609c308813c34609900e3b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Michel=20De=20Rainville?= Date: Tue, 9 Feb 2021 18:43:20 -0500 Subject: [PATCH 2/4] Remove old creation mechanism --- deap/creator.py | 171 ------------------------------------------------ 1 file changed, 171 deletions(-) delete mode 100644 deap/creator.py diff --git a/deap/creator.py b/deap/creator.py deleted file mode 100644 index 552c40671..000000000 --- a/deap/creator.py +++ /dev/null @@ -1,171 +0,0 @@ -# This file is part of DEAP. -# -# DEAP is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# DEAP is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with DEAP. If not, see . - -"""The :mod:`~deap.creator` is a meta-factory allowing to create classes that -will fulfill the needs of your evolutionary algorithms. In effect, new -classes can be built from any imaginable type, from :class:`list` to -:class:`set`, :class:`dict`, :class:`~deap.gp.PrimitiveTree` and more, -providing the possibility to implement genetic algorithms, genetic -programming, evolution strategies, particle swarm optimizers, and many more. -""" - -import array -import copy -import warnings - -class_replacers = {} -"""Some classes in Python's standard library as well as third party library -may be in part incompatible with the logic used in DEAP. To palliate -this problem, the method :func:`create` uses the dictionary -`class_replacers` to identify if the base type provided is problematic, and if -so the new class inherits from the replacement class instead of the -original base class. - -`class_replacers` keys are classes to be replaced and the values are the -replacing classes. -""" - -try: - import numpy - _ = (numpy.ndarray, numpy.array) -except ImportError: - # Numpy is not present, skip the definition of the replacement class. - pass -except AttributeError: - # Numpy is present, but there is either no ndarray or array in numpy, - # also skip the definition of the replacement class. - pass -else: - class _numpy_array(numpy.ndarray): - def __deepcopy__(self, memo): - """Overrides the deepcopy from numpy.ndarray that does not copy - the object's attributes. This one will deepcopy the array and its - :attr:`__dict__` attribute. - """ - copy_ = numpy.ndarray.copy(self) - copy_.__dict__.update(copy.deepcopy(self.__dict__, memo)) - return copy_ - - @staticmethod - 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) - - def __reduce__(self): - return (self.__class__, (list(self),), self.__dict__) - - class_replacers[numpy.ndarray] = _numpy_array - - -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. - """ - cls = self.__class__ - copy_ = cls.__new__(cls, self) - memo[id(self)] = copy_ - copy_.__dict__.update(copy.deepcopy(self.__dict__, memo)) - return copy_ - - def __reduce__(self): - return (self.__class__, (list(self),), self.__dict__) -class_replacers[array.array] = _array - - -def create(name, base, **kargs): - """Creates a new class named *name* inheriting from *base* in the - :mod:`~deap.creator` module. The new class can have attributes defined by - the subsequent keyword arguments passed to the function create. If the - argument is a class (without the parenthesis), the __init__ function is - called in the initialization of an instance of the new object and the - 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 instantiation 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. - - """ - - if name in globals(): - warnings.warn("A class named '{0}' has already been created and it " - "will be overwritten. Consider deleting previous " - "creation of that class or rename it.".format(name), - RuntimeWarning) - - dict_inst = {} - dict_cls = {} - for obj_name, obj in kargs.iteritems(): - if isinstance(obj, type): - dict_inst[obj_name] = obj - else: - dict_cls[obj_name] = obj - - # Check if the base class has to be replaced - if base in class_replacers: - base = class_replacers[base] - - # 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 - # override the __init__ method from object. - def initType(self, *args, **kargs): - """Replace the __init__ function of the new type, in order to - add attributes that were defined with **kargs to the instance. - """ - for obj_name, obj in dict_inst.iteritems(): - setattr(self, obj_name, obj()) - if base.__init__ is not object.__init__: - base.__init__(self, *args, **kargs) - - objtype = type(str(name), (base,), dict_cls) - objtype.__init__ = initType - globals()[name] = objtype From a7e8ac2e0effdd6e13515067d752e6403c3cc3b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Michel=20De=20Rainville?= Date: Tue, 9 Feb 2021 21:10:47 -0500 Subject: [PATCH 3/4] Add toolbox back and some tests --- deap/base.py | 197 +++++++++++++++++-------------------- tests/base/test_toolbox.py | 48 +++++++++ 2 files changed, 137 insertions(+), 108 deletions(-) create mode 100644 tests/base/test_toolbox.py diff --git a/deap/base.py b/deap/base.py index 33ea3e2ea..88a0295a0 100644 --- a/deap/base.py +++ b/deap/base.py @@ -1,29 +1,11 @@ -# This file is part of DEAP. -# -# DEAP is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# DEAP is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with DEAP. If not, see . - """The :mod:`~deap.base` module provides basic structures to build evolutionary algorithms. It contains the :class:`~deap.base.Toolbox`, useful to store evolutionary operators, and a virtual :class:`~deap.base.Fitness` 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 operator import mul, truediv -from abc import ABC from collections.abc import Callable, Sequence from copy import deepcopy from functools import wraps @@ -439,93 +421,92 @@ def __str__(self): return f"{self.__class__.__name__}({str_values})" -# class Toolbox(object): -# """A toolbox for the evolutionary operators. At -# first the toolbox contains a :meth:`~deap.toolbox.clone` method that -# duplicates any element it is passed as argument, this method defaults to -# the :func:`copy.deepcopy` function. and a :meth:`~deap.toolbox.map` -# method that applies the function given as first argument to every items -# of the iterables given as next arguments, this method defaults to the -# :func:`map` function. You may populate the toolbox with any other -# function by using the :meth:`~deap.base.Toolbox.register` method. - -# Concrete usages of the toolbox are shown for initialization in the -# :ref:`creating-types` tutorial and for tools container in the -# :ref:`next-step` tutorial. -# """ - -# def __init__(self): -# self.register("clone", deepcopy) -# 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 -# 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. -# :param function: The function to which refer the alias. -# :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 attribute :attr:`__name__` -# set to the alias and :attr:`__doc__` set to the original function's -# documentation. The :attr:`__dict__` attribute will also be updated -# with the original function's instance dictionary, if any. -# """ -# 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 -# # 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) - -# 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 -# relevant in most cases, it may have an impact on distributed -# environments like multiprocessing. -# A function can still be decorated manually before it is added to -# the toolbox (using the @ notation) in order to be picklable. -# """ -# pfunc = getattr(self, alias) -# function, args, kargs = pfunc.func, pfunc.args, pfunc.keywords -# for decorator in decorators: -# function = decorator(function) -# self.register(alias, function, *args, **kargs) +class Toolbox(object): + """A toolbox for the evolutionary operators. At + first the toolbox contains a :meth:`~deap.toolbox.clone` method that + duplicates any element it is passed as argument, this method defaults to + the :func:`copy.deepcopy` function. and a :meth:`~deap.toolbox.map` + method that applies the function given as first argument to every items + of the iterables given as next arguments, this method defaults to the + :func:`map` function. You may populate the toolbox with any other + function by using the :meth:`~deap.base.Toolbox.register` method. + + Concrete usages of the toolbox are shown for initialization in the + :ref:`creating-types` tutorial and for tools container in the + :ref:`next-step` tutorial. + """ + + 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 + 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. + :param function: The function to which refer the alias. + :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 attribute :attr:`__name__` + set to the alias and :attr:`__doc__` set to the original function's + documentation. The :attr:`__dict__` attribute will also be updated + with the original function's instance dictionary, if any. + """ + 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 + # 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) + + 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 + relevant in most cases, it may have an impact on distributed + environments like multiprocessing. + A function can still be decorated manually before it is added to + the toolbox (using the @ notation) in order to be picklable. + """ + pfunc = getattr(self, alias) + function, args, kargs = pfunc.func, pfunc.args, pfunc.keywords + for decorator in decorators: + function = decorator(function) + self.register(alias, function, *args, **kargs) diff --git a/tests/base/test_toolbox.py b/tests/base/test_toolbox.py new file mode 100644 index 000000000..9d0e55387 --- /dev/null +++ b/tests/base/test_toolbox.py @@ -0,0 +1,48 @@ +import unittest + +from deap.base import Toolbox + + +class TestToolbox(unittest.TestCase): + has_attr_default_message = "{obj} misses attribute {attrname}" + + def assertHasAttr(self, obj, attrname, message=None): + if message is None: + message = self.has_attr_default_message.format( + obj=obj, + attrname=attrname + ) + + self.assertTrue( + hasattr(obj, attrname), + msg=message + ) + + def test_has_map(self): + tb = Toolbox() + self.assertHasAttr(tb, "map") + + def test_register_has_attr(self): + def func(x): + return x + + tb = Toolbox() + tb.register("abc", func) + self.assertHasAttr(tb, "abc") + + def test_register_attr_has_new_name(self): + def func(x): + return x + + tb = Toolbox() + tb.register("abc", func) + self.assertEqual(tb.abc.__name__, "abc") + + def test_register_attr_has_docstring(self): + def func(x): + "docstring" + return x + + tb = Toolbox() + tb.register("abc", func) + self.assertEqual(tb.abc.__doc__, "docstring") From be537681ee30f7306d5a324387494d7ac9efccbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Michel=20De=20Rainville?= Date: Mon, 17 Jan 2022 10:01:57 -0500 Subject: [PATCH 4/4] Started migration to new creator --- deap/algorithms.py | 17 +- deap/base.py | 120 ++++--- deap/benchmarks/__init__.py | 453 +++++++++++++------------ deap/cma.py | 19 +- deap/tools/__init__.py | 55 ++- deap/tools/constraint.py | 2 +- examples/es/cma_minfct.py | 18 +- tests/base/test_attributes.py | 57 +++- tests/base/test_individual.py | 142 ++++++-- tests/test_benchmarks.py | 89 ++--- tests/test_convergence.py | 622 +++++++++++++++++----------------- tests/test_creator.py | 77 ----- tests/test_multiproc.py | 36 +- tests/test_pickle.py | 316 ++++++++--------- 14 files changed, 1053 insertions(+), 970 deletions(-) delete mode 100644 tests/test_creator.py diff --git a/deap/algorithms.py b/deap/algorithms.py index cf5cbf99d..7e22b8690 100644 --- a/deap/algorithms.py +++ b/deap/algorithms.py @@ -1,9 +1,12 @@ from copy import deepcopy -from itertools import cycle, islice, izip +from itertools import cycle, islice import random +from typing import Sequence +from .base import Individual -def evaluate_invalids(individuals, eval_func, map=map): + +def evaluate_invalids(individuals: Sequence[Individual], eval_func, map=map): """Evaluate all individuals marked invalid. Args: @@ -22,7 +25,7 @@ def evaluate_invalids(individuals, eval_func, map=map): invalid_ind = [ind for ind in individuals if not ind.fitness.valid] fitnesses = map(eval_func, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): - ind.fitness.values = fit + ind._setfitness(None, fit) return len(invalid_ind) @@ -68,7 +71,7 @@ def and_variation(population, toolbox, cxpb, mutpb): individuals = cycle(population) # zip(iter, iter) produces l[i], l[i+1] - for i1, i2 in izip(individuals, individuals): + for i1, i2 in zip(individuals, individuals): # TODO: put deepcopy in operators # Must deepcopy separately to ensure full deepcopy if the same # individual is selected twice, it is deepcopied twice (what is @@ -126,7 +129,7 @@ def or_variation(population, toolbox, cxpb, mutpb): # adjust probabilities since both crossover children are appended cxpb_adj = cxpb / (2 - cxpb) - if cxpb != 1.0: # Avoid zero division + if cxpb != 1.0: # Avoid zero division mutpb_adj = mutpb / (mutpb + (1 - mutpb - cxpb)) * (1 - cxpb_adj) else: mutpb_adj = mutpb @@ -345,12 +348,16 @@ class GenerateUpdateAlgorithm: """ def __init__(self, toolbox): self.toolbox = toolbox + self.population = None self.nevals = 0 def __iter__(self): return self def next(self): + return self.__next__() + + def __next__(self): # Generate a new population self.population = self.toolbox.generate() diff --git a/deap/base.py b/deap/base.py index 88a0295a0..092880dfa 100644 --- a/deap/base.py +++ b/deap/base.py @@ -7,7 +7,7 @@ class used as base class, for the fitness member of any individual. """ from operator import mul, truediv from collections.abc import Callable, Sequence -from copy import deepcopy +from copy import copy, deepcopy from functools import wraps from operator import attrgetter from typing import Any, Callable, Collection, List, Tuple @@ -209,29 +209,21 @@ def __str__(self): class Attribute: - """General attribute in an :class:`Individual`. + """General attributes in an :class:`Individual`. Attribute are placeholders in an :class:`Individual`. Individuals will initialize - attribute as properties allowing to set and get their values properly. + attribute as property-like objects allowing to set and get their values properly. Arguments: initval (any): Initial value for the attribute. Can be of any type. """ def __init__(self, initval: Any = None): - self.value = initval - - def _getvalue(self) -> None: - return self.value - - def _setvalue(self, val: Any) -> None: - self.value = val - - def _delvalue(self) -> None: - self.value = None + self.wrapped = initval - def __str__(self) -> str: - return str(self.value) + def unwrap(self): + """Drop the wrapper.""" + return self.wrapped class Individual: @@ -240,9 +232,9 @@ class Individual: Abstract base class for individuals. This class turns fitness and attribute members into property like objects that can be set directly with their values. - The individual is a mix of attribute and fitnesses. An individuals can have as - many attribute and fitnesses as desired. However, when having multiple attribute - or fitnesses the key argument in variations and/or selections becomes mandatory. + The individual is a mix of attributes and fitnesses. An individuals can have as + many attributes and fitnesses as desired. However, when having multiple attributes + or fitnesses the key argument in variations and selections becomes mandatory. Note: Predefined algorithms are not capable of handling multiple fitnesses. @@ -305,28 +297,19 @@ class Individual: """ def __init__(self): self._fitnesses = dict() - self._attribute = dict() + self._attributes = dict() def _register_fitness(self, name, fitness): - self._fitnesses[name] = fitness + if not hasattr(self, "_fitnesses"): + raise AttributeError( + "cannot assign fitness before Individual.__init__() call") + super().__getattribute__("_fitnesses")[name] = fitness def _register_attribute(self, name, attribute): - self._attribute[name] = attribute - - def _register_property(self, name, type_): - def _getter(self): - # Return the object and not the values to be able to use them - # with all their instance methods (i.e., return a Fitness not a ndarray) - return self.__getattribute__(type_)[name] - - def _setter(self, val): - self.__getattribute__(type_)[name]._setvalue(val) - - def _deletter(self): - self.__getattribute__(type_)[name]._delvalue() - - # Property is a class attribute - setattr(Individual, name, property(_getter, _setter, _deletter)) + if not hasattr(self, "_attributes"): + raise AttributeError( + "cannot assign attribute before Individual.__init__() call") + super().__getattribute__("_attributes")[name] = attribute.unwrap() def _getattribute(self, name=None): """Retrieve the attribute of this individual. If *name* is provided, retrieve @@ -336,8 +319,8 @@ def _getattribute(self, name=None): Note: This method is generally for internal use. """ - if name is None and len(self._attribute) == 1: - name = next(iter(self._attribute.keys())) + if name is None and len(self._attributes) == 1: + name = next(iter(self._attributes.keys())) elif name is None: raise AttributeError("individual with multiple attribute " "require the 'name' argument in operators") @@ -351,11 +334,11 @@ def _setattribute(self, name=None, value=None): Note: This method is generally for internal use. """ - if name is None and len(self._attribute) == 1: - name = next(iter(self._attribute.keys())) + if name is None and len(self._attributes) == 1: + name = next(iter(self._attributes.keys())) elif name is None: - raise AttributeError("individual with multiple attribute " - "require the 'name' argument in operators") + raise AttributeError("individuals with multiple attribute " + "require argument 'name' in operators") return setattr(self, name, value) def _getfitness(self, name=None): @@ -369,8 +352,8 @@ def _getfitness(self, name=None): if name is None and len(self._fitnesses) == 1: name = next(iter(self._fitnesses.keys())) elif name is None: - raise AttributeError("individual with multiple fitnesses " - "require the 'name' argument in operators") + raise AttributeError("individuals with multiple fitnesses " + "require argument 'name' in operators") return getattr(self, name) def _setfitness(self, name=None, value=None): @@ -398,26 +381,53 @@ def invalidate_fitness(self): f.reset() def __setattr__(self, name, value): + # Avoid this class __getattribute__ small overhead + getter = super().__getattribute__ if isinstance(value, Fitness): - if getattr(self, "_fitnesses", None) is None: - self._fitnesses = dict() - self._register_fitness(name, value) - self._register_property(name, "_fitnesses") - + elif hasattr(self, "_fitnesses") and name in getter("_fitnesses"): + getter("_fitnesses")[name]._setvalue(value) elif isinstance(value, Attribute): - if getattr(self, "_attribute", None) is None: - self._attribute = dict() - self._register_attribute(name, value) - self._register_property(name, "_attribute") - + elif hasattr(self, "_attributes") and name in getter("_attributes"): + getter("_attributes")[name] = value else: super().__setattr__(name, value) + def __getattribute__(self, name): + getter = super().__getattribute__ + try: + fitnesses = getter("_fitnesses") + except AttributeError: + fitnesses = {} + + try: + attributes = getter("_attributes") + except AttributeError: + attributes = {} + + if name in fitnesses: + return fitnesses[name] + + elif name in attributes: + return attributes[name] + + return getter(name) + + def __delattr__(self, name): + getter = super().__getattribute__ + if name in getter("_fitnesses"): + self._fitnesses[name].reset() + + elif name in getter("_attributes"): + del self._attributes[name] + + else: + super().__delattr__(name) + def __str__(self): str_values = ', '.join('='.join((name, str(attr.getvalue()))) - for name, attr in self._attribute.items()) + for name, attr in self._attributes.items()) return f"{self.__class__.__name__}({str_values})" diff --git a/deap/benchmarks/__init__.py b/deap/benchmarks/__init__.py index b4dbc49c1..44006049b 100644 --- a/deap/benchmarks/__init__.py +++ b/deap/benchmarks/__init__.py @@ -1,17 +1,4 @@ # This file is part of DEAP. -# -# DEAP is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# DEAP is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with DEAP. If not, see . """ Regroup typical EC benchmarks functions to import easily and benchmark examples. @@ -22,11 +9,14 @@ from operator import mul from functools import reduce +from ..tools import evaluation + # Unimodal -def rand(individual): +@evaluation +def rand(attributes): """Random test objective function. - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -40,11 +30,12 @@ def rand(individual): - :math:`f(\mathbf{x}) = \\text{\\texttt{random}}(0,1)` """ return random.random(), - -def plane(individual): + +@evaluation +def plane(attributes): """Plane test objective function. - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -57,12 +48,13 @@ def plane(individual): * - Function - :math:`f(\mathbf{x}) = x_0` """ - return individual[0], + return attributes[0], -def sphere(individual): +@evaluation +def sphere(attributes): """Sphere test objective function. - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -75,12 +67,13 @@ def sphere(individual): * - Function - :math:`f(\mathbf{x}) = \sum_{i=1}^Nx_i^2` """ - return sum(gene * gene for gene in individual), + return sum(gene * gene for gene in attributes), -def cigar(individual): +@evaluation +def cigar(attributes): """Cigar test objective function. - - .. list-table:: + + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -93,12 +86,13 @@ def cigar(individual): * - Function - :math:`f(\mathbf{x}) = x_0^2 + 10^6\\sum_{i=1}^N\,x_i^2` """ - return individual[0]**2 + 1e6 * sum(gene * gene for gene in individual), + return attributes[0]**2 + 1e6 * sum(gene * gene for gene in attributes), -def rosenbrock(individual): +@evaluation +def rosenbrock(attributes): """Rosenbrock test objective function. - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -110,20 +104,21 @@ def rosenbrock(individual): - :math:`x_i = 1, \\forall i \in \\lbrace 1 \\ldots N\\rbrace`, :math:`f(\mathbf{x}) = 0` * - Function - :math:`f(\\mathbf{x}) = \\sum_{i=1}^{N-1} (1-x_i)^2 + 100 (x_{i+1} - x_i^2 )^2` - + .. plot:: code/benchmarks/rosenbrock.py :width: 67 % """ return sum(100 * (x * x - y)**2 + (1. - x)**2 \ - for x, y in zip(individual[:-1], individual[1:])), + for x, y in zip(attributes[:-1], attributes[1:])), -def h1(individual): +@evaluation +def h1(attributes): """ Simple two-dimensional function containing several local maxima. - From: The Merits of a Parallel Genetic Algorithm in Solving Hard - Optimization Problems, A. J. Knoek van Soest and L. J. R. Richard + From: The Merits of a Parallel Genetic Algorithm in Solving Hard + Optimization Problems, A. J. Knoek van Soest and L. J. R. Richard Casius, J. Biomech. Eng. 125, 141 (2003) - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -141,16 +136,16 @@ def h1(individual): .. plot:: code/benchmarks/h1.py :width: 67 % """ - num = (sin(individual[0] - individual[1] / 8))**2 + (sin(individual[1] + individual[0] / 8))**2 - denum = ((individual[0] - 8.6998)**2 + (individual[1] - 6.7665)**2)**0.5 + 1 + num = (sin(attributes[0] - attributes[1] / 8))**2 + (sin(attributes[1] + attributes[0] / 8))**2 + denum = ((attributes[0] - 8.6998)**2 + (attributes[1] - 6.7665)**2)**0.5 + 1 return num / denum, - # # Multimodal -def ackley(individual): +@evaluation +def ackley(attributes): """Ackley test objective function. - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -163,18 +158,19 @@ def ackley(individual): * - Function - :math:`f(\\mathbf{x}) = 20 - 20\exp\left(-0.2\sqrt{\\frac{1}{N} \ \\sum_{i=1}^N x_i^2} \\right) + e - \\exp\\left(\\frac{1}{N}\sum_{i=1}^N \\cos(2\pi x_i) \\right)` - + .. plot:: code/benchmarks/ackley.py :width: 67 % """ - N = len(individual) - return 20 - 20 * exp(-0.2*sqrt(1.0/N * sum(x**2 for x in individual))) \ - + e - exp(1.0/N * sum(cos(2*pi*x) for x in individual)), - -def bohachevsky(individual): + N = len(attributes) + return 20 - 20 * exp(-0.2*sqrt(1.0/N * sum(x**2 for x in attributes))) \ + + e - exp(1.0/N * sum(cos(2*pi*x) for x in attributes)), + +@evaluation +def bohachevsky(attributes): """Bohachevsky test objective function. - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -187,17 +183,18 @@ def bohachevsky(individual): * - Function - :math:`f(\mathbf{x}) = \sum_{i=1}^{N-1}(x_i^2 + 2x_{i+1}^2 - \ 0.3\cos(3\pi x_i) - 0.4\cos(4\pi x_{i+1}) + 0.7)` - + .. plot:: code/benchmarks/bohachevsky.py :width: 67 % """ - return sum(x**2 + 2*x1**2 - 0.3*cos(3*pi*x) - 0.4*cos(4*pi*x1) + 0.7 - for x, x1 in zip(individual[:-1], individual[1:])), + return sum(x**2 + 2*x1**2 - 0.3*cos(3*pi*x) - 0.4*cos(4*pi*x1) + 0.7 + for x, x1 in zip(attributes[:-1], attributes[1:])), -def griewank(individual): +@evaluation +def griewank(attributes): """Griewank test objective function. - - .. list-table:: + + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -214,13 +211,14 @@ def griewank(individual): .. plot:: code/benchmarks/griewank.py :width: 67 % """ - return 1.0/4000.0 * sum(x**2 for x in individual) - \ - reduce(mul, (cos(x/sqrt(i+1.0)) for i, x in enumerate(individual)), 1) + 1, - -def rastrigin(individual): + return 1.0/4000.0 * sum(x**2 for x in attributes) - \ + reduce(mul, (cos(x/sqrt(i+1.0)) for i, x in enumerate(attributes)), 1) + 1, + +@evaluation +def rastrigin(attributes): """Rastrigin test objective function. - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -235,39 +233,42 @@ def rastrigin(individual): .. plot:: code/benchmarks/rastrigin.py :width: 67 % - """ - return 10 * len(individual) + sum(gene * gene - 10 * \ - cos(2 * pi * gene) for gene in individual), + """ + return 10 * len(attributes) + sum(gene * gene - 10 * \ + cos(2 * pi * gene) for gene in attributes), -def rastrigin_scaled(individual): +@evaluation +def rastrigin_scaled(attributes): """Scaled Rastrigin test objective function. - + :math:`f_{\\text{RastScaled}}(\mathbf{x}) = 10N + \sum_{i=1}^N \ \left(10^{\left(\\frac{i-1}{N-1}\\right)} x_i \\right)^2 x_i)^2 - \ 10\cos\\left(2\\pi 10^{\left(\\frac{i-1}{N-1}\\right)} x_i \\right)` """ - N = len(individual) - return 10*N + sum((10**(i/(N-1))*x)**2 - - 10*cos(2*pi*10**(i/(N-1))*x) for i, x in enumerate(individual)), + N = len(attributes) + return 10*N + sum((10**(i/(N-1))*x)**2 - + 10*cos(2*pi*10**(i/(N-1))*x) for i, x in enumerate(attributes)), -def rastrigin_skew(individual): +@evaluation +def rastrigin_skew(attributes): """Skewed Rastrigin test objective function. - + :math:`f_{\\text{RastSkew}}(\mathbf{x}) = 10N \sum_{i=1}^N \left(y_i^2 - 10 \\cos(2\\pi x_i)\\right)` - + :math:`\\text{with } y_i = \ \\begin{cases} \ 10\\cdot x_i & \\text{ if } x_i > 0,\\\ \ x_i & \\text{ otherwise } \ \\end{cases}` """ - N = len(individual) - return 10*N + sum((10*x if x > 0 else x)**2 - - 10*cos(2*pi*(10*x if x > 0 else x)) for x in individual), -def schaffer(individual): + N = len(attributes) + return 10*N + sum((10*x if x > 0 else x)**2 + - 10*cos(2*pi*(10*x if x > 0 else x)) for x in attributes), +@evaluation +def schaffer(attributes): """Schaffer test objective function. - - .. list-table:: + + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -285,13 +286,14 @@ def schaffer(individual): .. plot:: code/benchmarks/schaffer.py :width: 67 % """ - return sum((x**2+x1**2)**0.25 * ((sin(50*(x**2+x1**2)**0.1))**2+1.0) - for x, x1 in zip(individual[:-1], individual[1:])), + return sum((x**2+x1**2)**0.25 * ((sin(50*(x**2+x1**2)**0.1))**2+1.0) + for x, x1 in zip(attributes[:-1], attributes[1:])), -def schwefel(individual): +@evaluation +def schwefel(attributes): """Schwefel test objective function. - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -308,15 +310,16 @@ def schwefel(individual): .. plot:: code/benchmarks/schwefel.py :width: 67 % - """ - N = len(individual) - return 418.9828872724339*N-sum(x*sin(sqrt(abs(x))) for x in individual), + """ + N = len(attributes) + return 418.9828872724339*N-sum(x*sin(sqrt(abs(x))) for x in attributes), -def himmelblau(individual): - """The Himmelblau's function is multimodal with 4 defined minimums in +@evaluation +def himmelblau(attributes): + """The Himmelblau's function is multimodal with 4 defined minimums in :math:`[-6, 6]^2`. - .. list-table:: + .. list-table:: :widths: 10 50 :stub-columns: 1 @@ -335,90 +338,96 @@ def himmelblau(individual): .. plot:: code/benchmarks/himmelblau.py :width: 67 % """ - return (individual[0] * individual[0] + individual[1] - 11)**2 + \ - (individual[0] + individual[1] * individual[1] - 7)**2, + return (attributes[0] * attributes[0] + attributes[1] - 11)**2 + \ + (attributes[0] + attributes[1] * attributes[1] - 7)**2, -def shekel(individual, a, c): +@evaluation +def shekel(attributes, a, c): """The Shekel multimodal function can have any number of maxima. The number of maxima is given by the length of any of the arguments *a* or *c*, *a* is a matrix of size :math:`M\\times N`, where *M* is the number of maxima and *N* the number of dimensions and *c* is a :math:`M\\times 1` vector. - - :math:`f_\\text{Shekel}(\mathbf{x}) = \\sum_{i = 1}^{M} \\frac{1}{c_{i} + + + :math:`f_\\text{Shekel}(\mathbf{x}) = \\sum_{i = 1}^{M} \\frac{1}{c_{i} + \\sum_{j = 1}^{N} (x_{j} - a_{ij})^2 }` - + The following figure uses - - :math:`\\mathcal{A} = \\begin{bmatrix} 0.5 & 0.5 \\\\ 0.25 & 0.25 \\\\ + + :math:`\\mathcal{A} = \\begin{bmatrix} 0.5 & 0.5 \\\\ 0.25 & 0.25 \\\\ 0.25 & 0.75 \\\\ 0.75 & 0.25 \\\\ 0.75 & 0.75 \\end{bmatrix}` and :math:`\\mathbf{c} = \\begin{bmatrix} 0.002 \\\\ 0.005 \\\\ 0.005 \\\\ 0.005 \\\\ 0.005 \\end{bmatrix}`, thus defining 5 maximums in :math:`\\mathbb{R}^2`. - + .. plot:: code/benchmarks/shekel.py :width: 67 % """ - return sum((1. / (c[i] + sum((individual[j] - aij)**2 for j, aij in enumerate(a[i])))) for i in range(len(c))), + return sum((1. / (c[i] + sum((attributes[j] - aij)**2 for j, aij in enumerate(a[i])))) for i in range(len(c))), # Multiobjectives -def kursawe(individual): +@evaluation +def kursawe(attributes): """Kursawe multiobjective function. - + :math:`f_{\\text{Kursawe}1}(\\mathbf{x}) = \\sum_{i=1}^{N-1} -10 e^{-0.2 \\sqrt{x_i^2 + x_{i+1}^2} }` - + :math:`f_{\\text{Kursawe}2}(\\mathbf{x}) = \\sum_{i=1}^{N} |x_i|^{0.8} + 5 \\sin(x_i^3)` .. plot:: code/benchmarks/kursawe.py :width: 100 % """ - f1 = sum(-10 * exp(-0.2 * sqrt(x * x + y * y)) for x, y in zip(individual[:-1], individual[1:])) - f2 = sum(abs(x)**0.8 + 5 * sin(x * x * x) for x in individual) + f1 = sum(-10 * exp(-0.2 * sqrt(x * x + y * y)) for x, y in zip(attributes[:-1], attributes[1:])) + f2 = sum(abs(x)**0.8 + 5 * sin(x * x * x) for x in attributes) return f1, f2 -def schaffer_mo(individual): - """Schaffer's multiobjective function on a one attribute *individual*. +@evaluation +def schaffer_mo(attributes): + """Schaffer's multiobjective function on a one attribute *attributes*. From: J. D. Schaffer, "Multiple objective optimization with vector evaluated genetic algorithms", in Proceedings of the First International - Conference on Genetic Algorithms, 1987. - + Conference on Genetic Algorithms, 1987. + :math:`f_{\\text{Schaffer}1}(\\mathbf{x}) = x_1^2` - + :math:`f_{\\text{Schaffer}2}(\\mathbf{x}) = (x_1-2)^2` """ - return individual[0] ** 2, (individual[0] - 2) ** 2 - -def zdt1(individual): + return attributes[0] ** 2, (attributes[0] - 2) ** 2 + +@evaluation +def zdt1(attributes): """ZDT1 multiobjective function. - + :math:`g(\\mathbf{x}) = 1 + \\frac{9}{n-1}\\sum_{i=2}^n x_i` - + :math:`f_{\\text{ZDT1}1}(\\mathbf{x}) = x_1` - + :math:`f_{\\text{ZDT1}2}(\\mathbf{x}) = g(\\mathbf{x})\\left[1 - \\sqrt{\\frac{x_1}{g(\\mathbf{x})}}\\right]` """ - g = 1.0 + 9.0*sum(individual[1:])/(len(individual)-1) - f1 = individual[0] + g = 1.0 + 9.0*sum(attributes[1:])/(len(attributes)-1) + f1 = attributes[0] f2 = g * (1 - sqrt(f1/g)) return f1, f2 -def zdt2(individual): +@evaluation +def zdt2(attributes): """ZDT2 multiobjective function. - + :math:`g(\\mathbf{x}) = 1 + \\frac{9}{n-1}\\sum_{i=2}^n x_i` - + :math:`f_{\\text{ZDT2}1}(\\mathbf{x}) = x_1` - + :math:`f_{\\text{ZDT2}2}(\\mathbf{x}) = g(\\mathbf{x})\\left[1 - \\left(\\frac{x_1}{g(\\mathbf{x})}\\right)^2\\right]` - + """ - g = 1.0 + 9.0*sum(individual[1:])/(len(individual)-1) - f1 = individual[0] + g = 1.0 + 9.0*sum(attributes[1:])/(len(attributes)-1) + f1 = attributes[0] f2 = g * (1 - (f1/g)**2) return f1, f2 - -def zdt3(individual): + +@evaluation +def zdt3(attributes): """ZDT3 multiobjective function. :math:`g(\\mathbf{x}) = 1 + \\frac{9}{n-1}\\sum_{i=2}^n x_i` @@ -429,165 +438,172 @@ def zdt3(individual): """ - g = 1.0 + 9.0*sum(individual[1:])/(len(individual)-1) - f1 = individual[0] + g = 1.0 + 9.0*sum(attributes[1:])/(len(attributes)-1) + f1 = attributes[0] f2 = g * (1 - sqrt(f1/g) - f1/g * sin(10*pi*f1)) return f1, f2 -def zdt4(individual): +@evaluation +def zdt4(attributes): """ZDT4 multiobjective function. - + :math:`g(\\mathbf{x}) = 1 + 10(n-1) + \\sum_{i=2}^n \\left[ x_i^2 - 10\\cos(4\\pi x_i) \\right]` :math:`f_{\\text{ZDT4}1}(\\mathbf{x}) = x_1` - + :math:`f_{\\text{ZDT4}2}(\\mathbf{x}) = g(\\mathbf{x})\\left[ 1 - \\sqrt{x_1/g(\\mathbf{x})} \\right]` - + """ - g = 1 + 10*(len(individual)-1) + sum(xi**2 - 10*cos(4*pi*xi) for xi in individual[1:]) - f1 = individual[0] + g = 1 + 10*(len(attributes)-1) + sum(xi**2 - 10*cos(4*pi*xi) for xi in attributes[1:]) + f1 = attributes[0] f2 = g * (1 - sqrt(f1/g)) return f1, f2 - -def zdt6(individual): + +@evaluation +def zdt6(attributes): """ZDT6 multiobjective function. - + :math:`g(\\mathbf{x}) = 1 + 9 \\left[ \\left(\\sum_{i=2}^n x_i\\right)/(n-1) \\right]^{0.25}` - + :math:`f_{\\text{ZDT6}1}(\\mathbf{x}) = 1 - \\exp(-4x_1)\\sin^6(6\\pi x_1)` - + :math:`f_{\\text{ZDT6}2}(\\mathbf{x}) = g(\\mathbf{x}) \left[ 1 - (f_{\\text{ZDT6}1}(\\mathbf{x})/g(\\mathbf{x}))^2 \\right]` - + """ - g = 1 + 9 * (sum(individual[1:]) / (len(individual)-1))**0.25 - f1 = 1 - exp(-4*individual[0]) * sin(6*pi*individual[0])**6 + g = 1 + 9 * (sum(attributes[1:]) / (len(attributes)-1))**0.25 + f1 = 1 - exp(-4*attributes[0]) * sin(6*pi*attributes[0])**6 f2 = g * (1 - (f1/g)**2) return f1, f2 -def dtlz1(individual, obj): - """DTLZ1 multiobjective function. It returns a tuple of *obj* values. - The individual must have at least *obj* elements. - From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective +@evaluation +def dtlz1(attributes, obj): + """DTLZ1 multiobjective function. It returns a tuple of *obj* values. + The attributes must have at least *obj* elements. + From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective Optimization Test Problems. CEC 2002, p. 825 - 830, IEEE Press, 2002. :math:`g(\\mathbf{x}_m) = 100\\left(|\\mathbf{x}_m| + \sum_{x_i \in \\mathbf{x}_m}\\left((x_i - 0.5)^2 - \\cos(20\pi(x_i - 0.5))\\right)\\right)` :math:`f_{\\text{DTLZ1}1}(\\mathbf{x}) = \\frac{1}{2} (1 + g(\\mathbf{x}_m)) \\prod_{i=1}^{m-1}x_i` - + :math:`f_{\\text{DTLZ1}2}(\\mathbf{x}) = \\frac{1}{2} (1 + g(\\mathbf{x}_m)) (1-x_{m-1}) \\prod_{i=1}^{m-2}x_i` - + :math:`\\ldots` - + :math:`f_{\\text{DTLZ1}m-1}(\\mathbf{x}) = \\frac{1}{2} (1 + g(\\mathbf{x}_m)) (1 - x_2) x_1` - + :math:`f_{\\text{DTLZ1}m}(\\mathbf{x}) = \\frac{1}{2} (1 - x_1)(1 + g(\\mathbf{x}_m))` - + Where :math:`m` is the number of objectives and :math:`\\mathbf{x}_m` is a vector of the remaining attributes :math:`[x_m~\\ldots~x_n]` of the - individual in :math:`n > m` dimensions. - + attributes in :math:`n > m` dimensions. + """ - g = 100 * (len(individual[obj-1:]) + sum((xi-0.5)**2 - cos(20*pi*(xi-0.5)) for xi in individual[obj-1:])) - f = [0.5 * reduce(mul, individual[:obj-1], 1) * (1 + g)] - f.extend(0.5 * reduce(mul, individual[:m], 1) * (1 - individual[m]) * (1 + g) for m in reversed(xrange(obj-1))) + g = 100 * (len(attributes[obj-1:]) + sum((xi-0.5)**2 - cos(20*pi*(xi-0.5)) for xi in attributes[obj-1:])) + f = [0.5 * reduce(mul, attributes[:obj-1], 1) * (1 + g)] + f.extend(0.5 * reduce(mul, attributes[:m], 1) * (1 - attributes[m]) * (1 + g) for m in reversed(xrange(obj-1))) return f -def dtlz2(individual, obj): - """DTLZ2 multiobjective function. It returns a tuple of *obj* values. - The individual must have at least *obj* elements. - From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective +@evaluation +def dtlz2(attributes, obj): + """DTLZ2 multiobjective function. It returns a tuple of *obj* values. + The attributes must have at least *obj* elements. + From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective Optimization Test Problems. CEC 2002, p. 825 - 830, IEEE Press, 2002. - + :math:`g(\\mathbf{x}_m) = \\sum_{x_i \in \\mathbf{x}_m} (x_i - 0.5)^2` - + :math:`f_{\\text{DTLZ2}1}(\\mathbf{x}) = (1 + g(\\mathbf{x}_m)) \\prod_{i=1}^{m-1} \\cos(0.5x_i\pi)` - + :math:`f_{\\text{DTLZ2}2}(\\mathbf{x}) = (1 + g(\\mathbf{x}_m)) \\sin(0.5x_{m-1}\pi ) \\prod_{i=1}^{m-2} \\cos(0.5x_i\pi)` - + :math:`\\ldots` - + :math:`f_{\\text{DTLZ2}m}(\\mathbf{x}) = (1 + g(\\mathbf{x}_m)) \\sin(0.5x_{1}\pi )` - + Where :math:`m` is the number of objectives and :math:`\\mathbf{x}_m` is a vector of the remaining attributes :math:`[x_m~\\ldots~x_n]` of the - individual in :math:`n > m` dimensions. + attributes in :math:`n > m` dimensions. """ - xc = individual[:obj-1] - xm = individual[obj-1:] + xc = attributes[:obj-1] + xm = attributes[obj-1:] g = sum((xi-0.5)**2 for xi in xm) f = [(1.0+g) * reduce(mul, (cos(0.5*xi*pi) for xi in xc), 1.0)] f.extend((1.0+g) * reduce(mul, (cos(0.5*xi*pi) for xi in xc[:m]), 1) * sin(0.5*xc[m]*pi) for m in range(obj-2, -1, -1)) return f -def dtlz3(individual, obj): - """DTLZ3 multiobjective function. It returns a tuple of *obj* values. - The individual must have at least *obj* elements. - From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective +@evaluation +def dtlz3(attributes, obj): + """DTLZ3 multiobjective function. It returns a tuple of *obj* values. + The attributes must have at least *obj* elements. + From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective Optimization Test Problems. CEC 2002, p. 825 - 830, IEEE Press, 2002. - + :math:`g(\\mathbf{x}_m) = 100\\left(|\\mathbf{x}_m| + \sum_{x_i \in \\mathbf{x}_m}\\left((x_i - 0.5)^2 - \\cos(20\pi(x_i - 0.5))\\right)\\right)` - + :math:`f_{\\text{DTLZ3}1}(\\mathbf{x}) = (1 + g(\\mathbf{x}_m)) \\prod_{i=1}^{m-1} \\cos(0.5x_i\pi)` - + :math:`f_{\\text{DTLZ3}2}(\\mathbf{x}) = (1 + g(\\mathbf{x}_m)) \\sin(0.5x_{m-1}\pi ) \\prod_{i=1}^{m-2} \\cos(0.5x_i\pi)` - + :math:`\\ldots` - + :math:`f_{\\text{DTLZ3}m}(\\mathbf{x}) = (1 + g(\\mathbf{x}_m)) \\sin(0.5x_{1}\pi )` - + Where :math:`m` is the number of objectives and :math:`\\mathbf{x}_m` is a vector of the remaining attributes :math:`[x_m~\\ldots~x_n]` of the - individual in :math:`n > m` dimensions. + attributes in :math:`n > m` dimensions. """ - xc = individual[:obj-1] - xm = individual[obj-1:] + xc = attributes[:obj-1] + xm = attributes[obj-1:] g = 100 * (len(xm) + sum((xi-0.5)**2 - cos(20*pi*(xi-0.5)) for xi in xm)) f = [(1.0+g) * reduce(mul, (cos(0.5*xi*pi) for xi in xc), 1.0)] f.extend((1.0+g) * reduce(mul, (cos(0.5*xi*pi) for xi in xc[:m]), 1) * sin(0.5*xc[m]*pi) for m in range(obj-2, -1, -1)) return f -def dtlz4(individual, obj, alpha): +@evaluation +def dtlz4(attributes, obj, alpha): """DTLZ4 multiobjective function. It returns a tuple of *obj* values. The - individual must have at least *obj* elements. The *alpha* parameter allows + attributes must have at least *obj* elements. The *alpha* parameter allows for a meta-variable mapping in :func:`dtlz2` :math:`x_i \\rightarrow x_i^\\alpha`, the authors suggest :math:`\\alpha = 100`. - From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective + From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective Optimization Test Problems. CEC 2002, p. 825 - 830, IEEE Press, 2002. - + :math:`g(\\mathbf{x}_m) = \\sum_{x_i \in \\mathbf{x}_m} (x_i - 0.5)^2` - + :math:`f_{\\text{DTLZ4}1}(\\mathbf{x}) = (1 + g(\\mathbf{x}_m)) \\prod_{i=1}^{m-1} \\cos(0.5x_i^\\alpha\pi)` - + :math:`f_{\\text{DTLZ4}2}(\\mathbf{x}) = (1 + g(\\mathbf{x}_m)) \\sin(0.5x_{m-1}^\\alpha\pi ) \\prod_{i=1}^{m-2} \\cos(0.5x_i^\\alpha\pi)` - + :math:`\\ldots` - + :math:`f_{\\text{DTLZ4}m}(\\mathbf{x}) = (1 + g(\\mathbf{x}_m)) \\sin(0.5x_{1}^\\alpha\pi )` - + Where :math:`m` is the number of objectives and :math:`\\mathbf{x}_m` is a vector of the remaining attributes :math:`[x_m~\\ldots~x_n]` of the - individual in :math:`n > m` dimensions. + attributes in :math:`n > m` dimensions. """ - xc = individual[:obj-1] - xm = individual[obj-1:] + xc = attributes[:obj-1] + xm = attributes[obj-1:] g = sum((xi-0.5)**2 for xi in xm) f = [(1.0+g) * reduce(mul, (cos(0.5*xi**alpha*pi) for xi in xc), 1.0)] f.extend((1.0+g) * reduce(mul, (cos(0.5*xi**alpha*pi) for xi in xc[:m]), 1) * sin(0.5*xc[m]**alpha*pi) for m in range(obj-2, -1, -1)) return f +@evaluation def dtlz5(ind, n_objs): """DTLZ5 multiobjective function. It returns a tuple of *obj* values. The - individual must have at least *obj* elements. + attributes must have at least *obj* elements. From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective Optimization Test Problems. CEC 2002, p. 825-830, IEEE Press, 2002. """ g = lambda x: sum([(a - 0.5)**2 for a in x]) gval = g(ind[n_objs-1:]) - + theta = lambda x: pi / (4.0 * (1 + gval)) * (1 + 2 * gval * x) fit = [(1 + gval) * cos(pi / 2.0 * ind[0]) * reduce(lambda x,y: x*y, [cos(theta(a)) for a in ind[1:]])] - + for m in reversed(range(1, n_objs)): if m == 1: fit.append((1 + gval) * sin(pi / 2.0 * ind[0])) @@ -596,15 +612,16 @@ def dtlz5(ind, n_objs): reduce(lambda x,y: x*y, [cos(theta(a)) for a in ind[1:m-1]], 1) * sin(theta(ind[m-1]))) return fit +@evaluation def dtlz6(ind, n_objs): """DTLZ6 multiobjective function. It returns a tuple of *obj* values. The - individual must have at least *obj* elements. + attributes must have at least *obj* elements. From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective Optimization Test Problems. CEC 2002, p. 825-830, IEEE Press, 2002. """ gval = sum([a**0.1 for a in ind[n_objs-1:]]) theta = lambda x: pi / (4.0 * (1 + gval)) * (1 + 2 * gval * x) - + fit = [(1 + gval) * cos(pi / 2.0 * ind[0]) * reduce(lambda x,y: x*y, [cos(theta(a)) for a in ind[1:]])] @@ -616,9 +633,10 @@ def dtlz6(ind, n_objs): reduce(lambda x,y: x*y, [cos(theta(a)) for a in ind[1:m-1]], 1) * sin(theta(ind[m-1]))) return fit +@evaluation def dtlz7(ind, n_objs): """DTLZ7 multiobjective function. It returns a tuple of *obj* values. The - individual must have at least *obj* elements. + attributes must have at least *obj* elements. From: K. Deb, L. Thiele, M. Laumanns and E. Zitzler. Scalable Multi-Objective Optimization Test Problems. CEC 2002, p. 825-830, IEEE Press, 2002. """ @@ -627,26 +645,28 @@ def dtlz7(ind, n_objs): fit.append((1 + gval) * (n_objs - sum([a / (1.0 + gval) * (1 + sin(3 * pi * a)) for a in ind[:n_objs-1]]))) return fit -def fonseca(individual): +@evaluation +def fonseca(attributes): """Fonseca and Fleming's multiobjective function. From: C. M. Fonseca and P. J. Fleming, "Multiobjective optimization and multiple constraint handling with evolutionary algorithms -- Part II: Application example", IEEE Transactions on Systems, Man and Cybernetics, 1998. - + :math:`f_{\\text{Fonseca}1}(\\mathbf{x}) = 1 - e^{-\\sum_{i=1}^{3}(x_i - \\frac{1}{\\sqrt{3}})^2}` - + :math:`f_{\\text{Fonseca}2}(\\mathbf{x}) = 1 - e^{-\\sum_{i=1}^{3}(x_i + \\frac{1}{\\sqrt{3}})^2}` """ - f_1 = 1 - exp(-sum((xi - 1/sqrt(3))**2 for xi in individual[:3])) - f_2 = 1 - exp(-sum((xi + 1/sqrt(3))**2 for xi in individual[:3])) + f_1 = 1 - exp(-sum((xi - 1/sqrt(3))**2 for xi in attributes[:3])) + f_2 = 1 - exp(-sum((xi + 1/sqrt(3))**2 for xi in attributes[:3])) return f_1, f_2 -def poloni(individual): - """Poloni's multiobjective function on a two attribute *individual*. From: +@evaluation +def poloni(attributes): + """Poloni's multiobjective function on a two attribute *attributes*. From: C. Poloni, "Hybrid GA for multi objective aerodynamic shape optimization", in Genetic Algorithms in Engineering and Computer Science, 1997. - + :math:`A_1 = 0.5 \\sin (1) - 2 \\cos (1) + \\sin (2) - 1.5 \\cos (2)` :math:`A_2 = 1.5 \\sin (1) - \\cos (1) + 2 \\sin (2) - 0.5 \\cos (2)` @@ -654,21 +674,22 @@ def poloni(individual): :math:`B_1 = 0.5 \\sin (x_1) - 2 \\cos (x_1) + \\sin (x_2) - 1.5 \\cos (x_2)` :math:`B_2 = 1.5 \\sin (x_1) - cos(x_1) + 2 \\sin (x_2) - 0.5 \\cos (x_2)` - + :math:`f_{\\text{Poloni}1}(\\mathbf{x}) = 1 + (A_1 - B_1)^2 + (A_2 - B_2)^2` - + :math:`f_{\\text{Poloni}2}(\\mathbf{x}) = (x_1 + 3)^2 + (x_2 + 1)^2` """ - x_1 = individual[0] - x_2 = individual[1] + x_1 = attributes[0] + x_2 = attributes[1] A_1 = 0.5 * sin(1) - 2 * cos(1) + sin(2) - 1.5 * cos(2) A_2 = 1.5 * sin(1) - cos(1) + 2 * sin(2) - 0.5 * cos(2) B_1 = 0.5 * sin(x_1) - 2 * cos(x_1) + sin(x_2) - 1.5 * cos(x_2) B_2 = 1.5 * sin(x_1) - cos(x_1) + 2 * sin(x_2) - 0.5 * cos(x_2) return 1 + (A_1 - B_1)**2 + (A_2 - B_2)**2, (x_1 + 3)**2 + (x_2 + 1)**2 -def dent(individual, lambda_ = 0.85): - """Test problem Dent. Two-objective problem with a "dent". *individual* has +@evaluation +def dent(attributes, lambda_ = 0.85): + """Test problem Dent. Two-objective problem with a "dent". *attributes* has two attributes that take values in [-1.5, 1.5]. From: Schuetze, O., Laumanns, M., Tantar, E., Coello Coello, C.A., & Talbi, E.-G. (2010). Computing gap free Pareto front approximations with stochastic search algorithms. @@ -677,12 +698,12 @@ def dent(individual, lambda_ = 0.85): Note that in that paper Dent source is stated as: K. Witting and M. Hessel von Molo. Private communication, 2006. """ - d = lambda_ * exp(-(individual[0] - individual[1]) ** 2) - f1 = 0.5 * (sqrt(1 + (individual[0] + individual[1]) ** 2) + \ - sqrt(1 + (individual[0] - individual[1]) ** 2) + \ - individual[0] - individual[1]) + d - f2 = 0.5 * (sqrt(1 + (individual[0] + individual[1]) ** 2) + \ - sqrt(1 + (individual[0] - individual[1]) ** 2) - \ - individual[0] + individual[1]) + d + d = lambda_ * exp(-(attributes[0] - attributes[1]) ** 2) + f1 = 0.5 * (sqrt(1 + (attributes[0] + attributes[1]) ** 2) + \ + sqrt(1 + (attributes[0] - attributes[1]) ** 2) + \ + attributes[0] - attributes[1]) + d + f2 = 0.5 * (sqrt(1 + (attributes[0] + attributes[1]) ** 2) + \ + sqrt(1 + (attributes[0] - attributes[1]) ** 2) - \ + attributes[0] + attributes[1]) + d return f1, f2 diff --git a/deap/cma.py b/deap/cma.py index 76816b087..adc355157 100644 --- a/deap/cma.py +++ b/deap/cma.py @@ -22,11 +22,13 @@ import copy import itertools from math import sqrt, log, exp +import typing as tp import warnings import numpy from . import tools +from .base import Individual, Fitness class BasicStrategy(object): @@ -83,11 +85,13 @@ class BasicStrategy(object): Self-Adaptation in Evolution Strategies. *Evolutionary Computation* """ - def __init__(self, centroid, sigma, **kargs): + def __init__(self, centroid, sigma, attribute_key=None, fitness_key=None, **kargs): self.params = kargs + self.attribute_key = attribute_key + self.fitness_key = fitness_key # Create a centroid as a numpy array - self.centroid = numpy.array(centroid) + self.centroid = numpy.asarray(centroid) self.dim = len(self.centroid) self.sigma = sigma @@ -122,18 +126,19 @@ def generate(self, ind_init): arz = self.centroid + self.sigma * numpy.dot(arz, self.BD.T) return map(ind_init, arz) - def update(self, population): + def update(self, population: tp.Sequence[Individual]): """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) + population.sort(key=lambda ind: ind._getfitness(self.fitness_key), reverse=True) old_centroid = self.centroid - self.centroid = numpy.dot(self.weights, population[0:self.mu]) - + attributes = numpy.array([ind._getattribute(self.attribute_key) + for ind in population[0:self.mu]]) + self.centroid = numpy.dot(self.weights, attributes) c_diff = self.centroid - old_centroid # Cumulation : update evolution path @@ -153,7 +158,7 @@ def update(self, population): * c_diff # Update covariance matrix - artmp = population[0:self.mu] - old_centroid + artmp = attributes - 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) \ diff --git a/deap/tools/__init__.py b/deap/tools/__init__.py index e8b5ef0bd..ac9334593 100644 --- a/deap/tools/__init__.py +++ b/deap/tools/__init__.py @@ -10,15 +10,15 @@ from functools import wraps from typing import Callable, List -# from .constraint import * -# from .crossover import * -# from .emo import * -# from .indicator import * -# from .init import * -# from .migration import * -# from .mutation import * -# from .selection import * -# from .support import * +from .constraint import * +from .crossover import * +from .emo import * +from .indicator import * +from .init import * +from .migration import * +from .mutation import * +from .selection import * +from .support import * from ..base import Individual @@ -55,7 +55,7 @@ def wrapper(*args, **kwargs): if isinstance(argument, Individual): copy_ = deepcopy(argument) individuals.append(copy_) - argument = copy_._getattributes(key) + argument = copy_._getattribute(key) variator_args.append(argument) @@ -64,7 +64,7 @@ def wrapper(*args, **kwargs): if isinstance(argument, Individual): copy_ = deepcopy(argument) individuals.append(copy_) - argument = copy_._getattributes(key) + argument = copy_._getattribute(key) variator_kwargs[keyword] = argument @@ -75,15 +75,44 @@ def wrapper(*args, **kwargs): # individual if len(individuals) > 1: for i, a in zip(individuals, attributes): - i._setattributes(key, a) + i._setattribute(key, a) i.invalidate_fitness() else: individuals = individuals[0] - individuals._setattributes(key, attributes) + individuals._setattribute(key, attributes) individuals.invalidate_fitness() # Return the whole individuals with their fitness invalidated return individuals return wrapper + + +def evaluation(func: Callable): + @wraps(func) + def wrapper(*args, **kwargs): + key = kwargs.pop("key", None) + evaluation_args = list() + evaluation_kwargs = dict() + + # Deepcopy passed individuals + for argument in args: + if isinstance(argument, Individual): + argument = argument._getattribute(key) + + evaluation_args.append(argument) + + # Deepcopy passed individuals + for keyword, argument in kwargs.items(): + if isinstance(argument, Individual): + argument = argument._getattribute(key) + + evaluation_kwargs[keyword] = argument + + # Get variated attributes + fitness = func(*evaluation_args, **evaluation_kwargs) + + return fitness + + return wrapper diff --git a/deap/tools/constraint.py b/deap/tools/constraint.py index 22fd7cb3a..dd62657d1 100644 --- a/deap/tools/constraint.py +++ b/deap/tools/constraint.py @@ -1,7 +1,7 @@ from functools import wraps from itertools import repeat -from collections import Sequence +from collections.abc import Sequence class DeltaPenalty(object): """This decorator returns penalized fitness for invalid individuals and the diff --git a/examples/es/cma_minfct.py b/examples/es/cma_minfct.py index 9a10b6eca..2fe99d830 100644 --- a/examples/es/cma_minfct.py +++ b/examples/es/cma_minfct.py @@ -19,18 +19,23 @@ from deap import base from deap import benchmarks from deap import cma -from deap import creator from deap import tools # Problem size N=30 NGEN=250 -creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) -creator.create("Individual", list, fitness=creator.FitnessMin) +class Individual(base.Individual): + def __init__(self, initval=None): + super().__init__() + self.fitness = base.Fitness(base.Fitness.MINIMIZE) + self.attrs = base.Attribute(initval) + +# creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) +# creator.create("Individual", list, fitness=creator.FitnessMin) toolbox = base.Toolbox() -toolbox.register("evaluate", benchmarks.rastrigin) +toolbox.register("evaluate", benchmarks.rastrigin, key="attrs") def main(verbose=True): # The cma module uses the numpy random number generator @@ -40,11 +45,11 @@ def main(verbose=True): # The centroid is set to a vector of 5.0 see http://www.lri.fr/~hansen/cmaes_inmatlab.html # for more details about the rastrigin and other tests for CMA-ES strategy = cma.BasicStrategy(centroid=[5.0]*N, sigma=5.0, lambda_=20*N) - toolbox.register("generate", strategy.generate, creator.Individual) + toolbox.register("generate", strategy.generate, Individual) toolbox.register("update", strategy.update) hof = tools.HallOfFame(1) - stats = tools.Statistics(lambda ind: ind.fitness.values) + stats = tools.Statistics(lambda ind: ind.fitness.value) stats.register("avg", numpy.mean) stats.register("std", numpy.std) stats.register("min", numpy.min) @@ -64,7 +69,6 @@ def main(verbose=True): if gen >= NGEN: break - # print "Best individual is %s, %s" % (hof[0], hof[0].fitness.values) return hof[0].fitness.values[0] if __name__ == "__main__": diff --git a/tests/base/test_attributes.py b/tests/base/test_attributes.py index 255fd2fba..6d4c1e4b8 100644 --- a/tests/base/test_attributes.py +++ b/tests/base/test_attributes.py @@ -6,22 +6,43 @@ class TestBaseAttribute(unittest.TestCase): - def test_get_value(self): - a = Attribute(3) - self.assertEqual(a._getvalue(), 3) - - def test_set_value(self): + def test_init_value_none(self): a = Attribute() - a._setvalue(3) - self.assertEqual(a.value, 3) - - def test_del_value(self): - a = Attribute(3) - a._delvalue() - self.assertEqual(a.value, None) - - def test_str(self): - value = 3 - a = Attribute(value) - str_a = str(a) - self.assertEqual(str_a, str(value)) + self.assertEqual(a, None) + + def test_init_value(self): + init_val = 3 + a = Attribute(init_val) + self.assertEqual(a, init_val) + + def test_unwrap(self): + init_val = {"a": 3} + a = Attribute(init_val) + expected = a.unwrap() + self.assertIs(expected, init_val) + + +# class TestNumpyAttribute(unittest.TestCase): + # def test_add_value(self): + # a = Attribute(numpy.array([1, 2, 3])) + # a += 3 + # numpy.testing.assert_equal(a, [4, 5, 6]) + + # def test_dot_attributes(self): + # list_ = [1, 2, 3] + # # array = numpy.array(list_) + # attr = Attribute(list_) + + # expected = numpy.dot(list_, list_) + # result = numpy.dot(attr, attr) + + # numpy.testing.assert_equal(expected, result) + + # def test_array_attributes(self): + # list_ = [1, 2, 3] + # attr = Attribute(list_) + + # expected = numpy.array([list_, list_]) + # result = numpy.array([attr, attr]) + + # numpy.testing.assert_equal(expected, result) diff --git a/tests/base/test_individual.py b/tests/base/test_individual.py index 8054cd579..e834373c3 100644 --- a/tests/base/test_individual.py +++ b/tests/base/test_individual.py @@ -168,8 +168,26 @@ def test_register_single_attribute(self): attr = Attribute() ind = Individual() ind.my_attr = attr - self.assertIn("my_attr", ind._attribute) - self.assertIs(ind._attribute["my_attr"], attr) + self.assertIn("my_attr", ind._attributes) + self.assertTrue(hasattr(ind, "my_attr")) + self.assertIs(ind._attributes["my_attr"], None) + + def test_register_single_attribute_multi_individual(self): + attr1 = Attribute() + attr2 = Attribute() + ind1 = Individual() + ind2 = Individual() + ind1.my_attr1 = attr1 + ind2.my_attr2 = attr2 + self.assertIn("my_attr1", ind1._attributes) + self.assertTrue(hasattr(ind1, "my_attr1")) + self.assertNotIn("my_attr2", ind1._attributes) + self.assertFalse(hasattr(ind1, "my_attr2")) + + self.assertNotIn("my_attr1", ind2._attributes) + self.assertFalse(hasattr(ind2, "my_attr1")) + self.assertIn("my_attr2", ind2._attributes) + self.assertTrue(hasattr(ind2, "my_attr2")) def test_register_multi_attributes(self): attr1 = Attribute() @@ -177,28 +195,28 @@ def test_register_multi_attributes(self): ind = Individual() ind.my_attr1 = attr1 ind.my_attr2 = attr2 - self.assertIn("my_attr1", ind._attribute) - self.assertIs(ind._attribute["my_attr1"], attr1) - self.assertIn("my_attr2", ind._attribute) - self.assertIs(ind._attribute["my_attr2"], attr2) + self.assertIn("my_attr1", ind._attributes) + self.assertIs(ind._attributes["my_attr1"], attr1) + self.assertIn("my_attr2", ind._attributes) + self.assertIs(ind._attributes["my_attr2"], attr2) def test_get_single_attribute_no_key(self): attr = Attribute() ind = Individual() ind.my_attr = attr - self.assertIs(ind._getattribute(), attr) + self.assertIsInstance(ind._getattribute(), Attribute) def test_get_single_attribute_with_key(self): attr = Attribute() ind = Individual() ind.my_attr = attr - self.assertIs(ind._getattribute("my_attr"), attr) + self.assertIsInstance(ind._getattribute("my_attr"), Attribute) def test_get_single_attribute_direct(self): attr = Attribute() ind = Individual() ind.my_attr = attr - self.assertIs(ind.my_attr, attr) + self.assertIsInstance(ind.my_attr, Attribute) def test_get_multi_attributes_no_key_raises(self): attr1 = Attribute() @@ -208,47 +226,47 @@ def test_get_multi_attributes_no_key_raises(self): ind.my_attr2 = attr2 self.assertRaises(AttributeError, ind._getattribute) - def test_get_multi_fitnesses_with_key(self): + def test_get_multi_attributes_with_key(self): attr1 = Attribute() attr2 = Attribute() ind = Individual() ind.my_attr1 = attr1 ind.my_attr2 = attr2 - self.assertIs(ind._getattribute("my_attr1"), attr1) - self.assertIs(ind._getattribute("my_attr2"), attr2) + self.assertIsInstance(ind._getattribute("my_attr1"), Attribute) + self.assertIsInstance(ind._getattribute("my_attr2"), Attribute) - def test_get_multi_fitnesses_direct(self): + def test_get_multi_attributes_direct(self): attr1 = Attribute() attr2 = Attribute() ind = Individual() ind.my_attr1 = attr1 ind.my_attr2 = attr2 - self.assertIs(ind.my_attr1, attr1) - self.assertIs(ind.my_attr2, attr2) + self.assertIsInstance(ind.my_attr1, Attribute) + self.assertIsInstance(ind.my_attr2, Attribute) def test_set_single_attribute_no_key(self): attr = Attribute() ind = Individual() ind.my_attr = attr ind._setattribute(None, "abc") - self.assertIs(ind._getattribute("my_attr"), attr) - self.assertEqual(ind.my_attr.value, "abc") + self.assertIsInstance(ind._getattribute("my_attr"), Attribute) + self.assertEqual(ind.my_attr, "abc") def test_set_single_attribute_with_key(self): attr = Attribute() ind = Individual() ind.my_attr = attr ind._setattribute("my_attr", "abc") - self.assertIs(ind._getattribute("my_attr"), attr) - self.assertEqual(ind.my_attr.value, "abc") + self.assertIsInstance(ind._getattribute("my_attr"), Attribute) + self.assertEqual(ind.my_attr, "abc") def test_set_single_attribute_direct(self): attr = Attribute() ind = Individual() ind.my_attr = attr ind.my_attr = "abc" - self.assertIs(ind._getattribute("my_attr"), attr) - self.assertEqual(ind.my_attr.value, "abc") + self.assertIsInstance(ind._getattribute("my_attr"), Attribute) + self.assertEqual(ind.my_attr, "abc") def test_set_multi_attributes_no_key_raises(self): attr1 = Attribute() @@ -265,11 +283,11 @@ def test_set_multi_attributes_with_key(self): ind.my_attr1 = attr1 ind.my_attr2 = attr2 ind._setattribute("my_attr1", "abc") - self.assertIs(ind._getattribute("my_attr1"), attr1) - self.assertEqual(attr1.value, "abc") + self.assertIsInstance(ind._getattribute("my_attr1"), Attribute) + self.assertEqual(attr1, "abc") ind._setattribute("my_attr2", "def") - self.assertIs(ind._getattribute("my_attr2"), attr2) - numpy.testing.assert_array_equal(attr2.value, "def") + self.assertIsInstance(ind._getattribute("my_attr2"), Attribute) + self.assertEqual(attr2, "def") def test_set_multi_attributes_direct(self): attr1 = Attribute() @@ -278,8 +296,74 @@ def test_set_multi_attributes_direct(self): ind.my_attr1 = attr1 ind.my_attr2 = attr2 ind.my_attr1 = "abc" - self.assertIs(ind._getattribute("my_attr1"), attr1) - self.assertEqual(attr1.value, "abc") + self.assertIsInstance(ind._getattribute("my_attr1"), Attribute) + self.assertEqual(attr1, "abc") ind.my_attr2 = "def" - self.assertIs(ind._getattribute("my_attr2"), attr2) - numpy.testing.assert_array_equal(attr2.value, "def") + self.assertIsInstance(ind._getattribute("my_attr2"), Attribute) + self.assertEqual(attr2, "def") + + def test_del_single_attribute(self): + attr = Attribute() + ind = Individual() + ind.my_attr = attr + del ind.my_attr + self.assertNotIn("my_attr", ind._attributes) + self.assertFalse(hasattr(ind, "my_attr")) + + def test_del_multi_attributes(self): + attr1= Attribute() + attr2= Attribute() + ind = Individual() + ind.my_attr1 = attr1 + ind.my_attr2 = attr1 + del ind.my_attr1 + del ind.my_attr2 + self.assertNotIn("my_attr1", ind._attributes) + self.assertFalse(hasattr(ind, "my_attr1")) + self.assertNotIn("my_attr2", ind._attributes) + self.assertFalse(hasattr(ind, "my_attr2")) + + def test_del_single_attribute_multi_individuals(self): + attr = Attribute() + ind1 = Individual() + ind2 = Individual() + ind1.my_attr = attr + ind2.my_attr = attr + del ind1.my_attr + self.assertNotIn("my_attr", ind1._attributes) + self.assertFalse(hasattr(ind1, "my_attr")) + self.assertIn("my_attr", ind2._attributes) + self.assertTrue(hasattr(ind2, "my_attr")) + + +class TestIndividualHeritance(unittest.TestCase): + class TestIndividual(Individual): + def __init__(self, initval=None): + super().__init__() + self.fitness = Fitness(Fitness.MINIMIZE) + self.attr = Attribute(initval) + + class TestIndividualNoSuperInit(Individual): + def __init__(self, initval=None): + self.fitness = Fitness(Fitness.MINIMIZE) + self.attr = Attribute(initval) + + class TestIndividualNoSuperInitAttrFirst(Individual): + def __init__(self, initval=None): + self.attr = Attribute(initval) + self.fitness = Fitness(Fitness.MINIMIZE) + + def test_subclass_has_all_attr(self): + ind = TestIndividualHeritance.TestIndividual() + self.assertTrue(hasattr(ind, "_fitnesses")) + self.assertTrue(hasattr(ind, "_attributes")) + self.assertTrue(hasattr(ind, "fitness")) + self.assertTrue(hasattr(ind, "attr")) + + def test_subclass_no_super_init_raises(self): + with self.assertRaisesRegex(AttributeError, r"cannot assign \w+ before Individual\.__init__\(\) call"): + TestIndividualHeritance.TestIndividualNoSuperInit() + + def test_subclass_no_super_init_raises_attr(self): + with self.assertRaisesRegex(AttributeError, r"cannot assign \w+ before Individual\.__init__\(\) call"): + TestIndividualHeritance.TestIndividualNoSuperInitAttrFirst() diff --git a/tests/test_benchmarks.py b/tests/test_benchmarks.py index e0581831b..b75c513ae 100644 --- a/tests/test_benchmarks.py +++ b/tests/test_benchmarks.py @@ -1,62 +1,45 @@ -"""Test functions from deap/benchmarks.""" import sys import unittest -from deap import base -from deap import benchmarks -from deap import creator from deap.benchmarks import binary -class BenchmarkTest(unittest.TestCase): +class TestBin2Float(unittest.TestCase): """Test object for unittest of deap/benchmarks.""" def setUp(self): - - @binary.bin2float(0, 1023, 10) - def evaluate(individual): - """Simplest evaluation function.""" - return individual - - creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) - creator.create("Individual", list, fitness=creator.FitnessMin) - self.toolbox = base.Toolbox() - self.toolbox.register("evaluate", evaluate) - - def tearDown(self): - del creator.FitnessMin - - def test_bin2float(self): - - # Correct evaluation of bin2float. - zero_individual = creator.Individual([0] * 10) - full_individual = creator.Individual([1] * 10) - two_individiual = creator.Individual(8*[0] + [1, 0]) - population = [zero_individual, full_individual, two_individiual] - fitnesses = map(self.toolbox.evaluate, population) - for ind, fit in zip(population, fitnesses): - ind.fitness.values = fit - assert population[0].fitness.values == (0.0, ) - assert population[1].fitness.values == (1023.0, ) - assert population[2].fitness.values == (2.0, ) - - # Incorrect evaluation of bin2float. - wrong_size_individual = creator.Individual([0, 1, 0, 1, 0, 1, 0, 1, 1]) - wrong_population = [wrong_size_individual] - # It is up the user to make sure that bin2float gets an individual with - # an adequate length; no exceptions are raised. - fitnesses = map(self.toolbox.evaluate, wrong_population) - for ind, fit in zip(wrong_population, fitnesses): - # In python 2.7 operator.mul works in a different way than in - # python3. Thus an error occurs in python2.7 but an assignment is - # correctly executed in python3. - if sys.version_info < (3, ): - with self.assertRaises(TypeError): - ind.fitness.values = fit - else: - assert wrong_population[0].fitness.values == () - - -if __name__ == "__main__": - suite = unittest.TestLoader().loadTestsFromTestCase(BenchmarkTest) - unittest.TextTestRunner(verbosity=2).run(suite) + self.b2f = binary.bin2float(0, 1023, 10)(lambda x: x) + + def test_zero(self): + zero = [0] * 10 + res = self.b2f(zero) + self.assertSequenceEqual(res, [0]) + + def test_full(self): + full = [1] * 10 + res = self.b2f(full) + self.assertSequenceEqual(res, [1023]) + + def test_two(self): + two = [0] * 8 + [1, 0] + res = self.b2f(two) + self.assertSequenceEqual(res, [2]) + + def test_one_two(self): + one = [0] * 9 + [1] + two = [0] * 8 + [1, 0] + one_two = one + two + res = self.b2f(one_two) + self.assertSequenceEqual(res, [1, 2]) + + @unittest.skipUnless(sys.version_info < (3, ), "Python 2") + def test_wrong_size_raises_py2(self): + wrong = [0, 1, 0, 1, 0, 1, 0, 1, 1] + with self.assertRaises(TypeError): + self.b2f(wrong) + + @unittest.skipUnless(sys.version_info >= (3, ), "Python 3+") + def test_wrong_size_no_value_py3(self): + wrong = [0, 1, 0, 1, 0, 1, 0, 1, 1] + res = self.b2f(wrong) + self.assertEqual(len(res), 0) diff --git a/tests/test_convergence.py b/tests/test_convergence.py index 1d1856d58..f744ab958 100644 --- a/tests/test_convergence.py +++ b/tests/test_convergence.py @@ -27,26 +27,22 @@ from deap import benchmarks from deap.benchmarks.tools import hypervolume from deap import cma -from deap import creator from deap import tools -FITCLSNAME = "FIT_TYPE" -INDCLSNAME = "IND_TYPE" - HV_THRESHOLD = 116.0 # 120.777 is Optimal value -class TearDownCreatorTestCase(unittest.TestCase): - def tearDown(self): - # Messy way to remove a class from the creator - del creator.__dict__[FITCLSNAME] - del creator.__dict__[INDCLSNAME] +class Individual(base.Individual): + def __init__(self, initval): + super().__init__() + self.fitness = base.Fitness(base.Fitness.MINIMIZE) + self.attr = base.Attribute(initval) + -class TestSingleObjective(TearDownCreatorTestCase): - def setUp(self): - creator.create(FITCLSNAME, base.Fitness, weights=(-1.0,)) - creator.create(INDCLSNAME, list, fitness=creator.__dict__[FITCLSNAME]) +def eval_sphere(individual: Individual): + return benchmarks.sphere(individual.attr) +class TestSingleObjective(unittest.TestCase): def test_cma(self): NDIM = 5 NGEN = 100 @@ -54,8 +50,8 @@ def test_cma(self): strategy = cma.BasicStrategy(centroid=[0.0]*NDIM, sigma=1.0) toolbox = base.Toolbox() - toolbox.register("evaluate", benchmarks.sphere) - toolbox.register("generate", strategy.generate, creator.__dict__[INDCLSNAME]) + toolbox.register("evaluate", eval_sphere) + toolbox.register("generate", strategy.generate, Individual) toolbox.register("update", strategy.update) # Consume the algorithm until NGEN @@ -64,379 +60,379 @@ def test_cma(self): self.assertLess(best.fitness.values[0], 1e-8) - def test_cma_mixed_integer_1_p_1_no_constraint(self): - N = 3 - NGEN = 15000 - - toolbox = base.Toolbox() - toolbox.register("evaluate", benchmarks.sphere) - - parent = (numpy.random.rand(N) * 2) + 1 - - strategy = cma.ActiveOnePlusLambdaStrategy(parent, 0.5, [0, 0, 0.1], lambda_=1) +# def test_cma_mixed_integer_1_p_1_no_constraint(self): +# N = 3 +# NGEN = 15000 - toolbox.register("generate", strategy.generate, ind_init=creator.__dict__[INDCLSNAME]) - toolbox.register("update", strategy.update) - - best = None - - for gen in range(NGEN): - # Generate a new population - population = toolbox.generate() +# toolbox = base.Toolbox() +# toolbox.register("evaluate", benchmarks.sphere) - # Evaluate the individuals - for individual in population: - individual.fitness.values = toolbox.evaluate(individual) +# parent = (numpy.random.rand(N) * 2) + 1 - if best is None or individual.fitness >= best.fitness: - best = individual +# strategy = cma.ActiveOnePlusLambdaStrategy(parent, 0.5, [0, 0, 0.1], lambda_=1) - # We must stop CMA-ES before the update becomes unstable - if best.fitness.values[0] < 1e-12: - break +# toolbox.register("generate", strategy.generate, ind_init=creator.__dict__[INDCLSNAME]) +# toolbox.register("update", strategy.update) - # Update the strategy with the evaluated individuals - toolbox.update(population) +# best = None - self.assertLess(best.fitness.values[0], 1e-12) - - def test_cma_mixed_integer_1_p_20_no_constraint(self): - N = 3 - NGEN = 15000 - - toolbox = base.Toolbox() - toolbox.register("evaluate", benchmarks.sphere) +# for gen in range(NGEN): +# # Generate a new population +# population = toolbox.generate() - parent = (numpy.random.rand(N) * 2) + 1 +# # Evaluate the individuals +# for individual in population: +# individual.fitness.values = toolbox.evaluate(individual) - strategy = cma.ActiveOnePlusLambdaStrategy(parent, 0.5, [0, 0, 0.1], lambda_=20) +# if best is None or individual.fitness >= best.fitness: +# best = individual - toolbox.register("generate", strategy.generate, ind_init=creator.__dict__[INDCLSNAME]) - toolbox.register("update", strategy.update) +# # We must stop CMA-ES before the update becomes unstable +# if best.fitness.values[0] < 1e-12: +# break - best = None +# # Update the strategy with the evaluated individuals +# toolbox.update(population) - for gen in range(NGEN): - # Generate a new population - population = toolbox.generate() +# self.assertLess(best.fitness.values[0], 1e-12) - # Evaluate the individuals - for individual in population: - individual.fitness.values = toolbox.evaluate(individual) +# def test_cma_mixed_integer_1_p_20_no_constraint(self): +# N = 3 +# NGEN = 15000 - if best is None or individual.fitness >= best.fitness: - best = individual +# toolbox = base.Toolbox() +# toolbox.register("evaluate", benchmarks.sphere) - # Stop when we've reached some kind of optimum - if best.fitness.values[0] < 1e-12: - break +# parent = (numpy.random.rand(N) * 2) + 1 - # Update the strategy with the evaluated individuals - toolbox.update(population) +# strategy = cma.ActiveOnePlusLambdaStrategy(parent, 0.5, [0, 0, 0.1], lambda_=20) - self.assertLess(best.fitness.values[0], 1e-12) +# toolbox.register("generate", strategy.generate, ind_init=creator.__dict__[INDCLSNAME]) +# toolbox.register("update", strategy.update) +# best = None -class TestSingleObjectiveConstrained(TearDownCreatorTestCase): - def setUp(self): - creator.create(FITCLSNAME, base.ConstrainedFitness, weights=(-1.0,)) - creator.create(INDCLSNAME, list, fitness=creator.__dict__[FITCLSNAME]) +# for gen in range(NGEN): +# # Generate a new population +# population = toolbox.generate() +# # Evaluate the individuals +# for individual in population: +# individual.fitness.values = toolbox.evaluate(individual) - def test_cma_mixed_integer_1_p_1_with_constraint(self): - def c1(individual): - if individual[0] + individual[1] < 0.1: - return True - return False +# if best is None or individual.fitness >= best.fitness: +# best = individual - def c2(individual): - if individual[1] < 0.1: - return True - return False +# # Stop when we've reached some kind of optimum +# if best.fitness.values[0] < 1e-12: +# break - N = 5 - NGEN = 15000 - optimum = 0.015 +# # Update the strategy with the evaluated individuals +# toolbox.update(population) - toolbox = base.Toolbox() - toolbox.register("evaluate", benchmarks.sphere) - restarts = 10 +# self.assertLess(best.fitness.values[0], 1e-12) - # Allow a couple of restarts - while restarts > 0: - parent = (numpy.random.rand(N) * 2) + 1 - strategy = cma.ActiveOnePlusLambdaStrategy(parent, 0.5, [0, 0, 0.1, 0, 0], lambda_=1) +# class TestSingleObjectiveConstrained(TearDownCreatorTestCase): +# def setUp(self): +# creator.create(FITCLSNAME, base.ConstrainedFitness, weights=(-1.0,)) +# creator.create(INDCLSNAME, list, fitness=creator.__dict__[FITCLSNAME]) - toolbox.register("generate", strategy.generate, ind_init=creator.__dict__[INDCLSNAME]) - toolbox.register("update", strategy.update) - best = None +# def test_cma_mixed_integer_1_p_1_with_constraint(self): +# def c1(individual): +# if individual[0] + individual[1] < 0.1: +# return True +# return False - for gen in range(NGEN): - # Generate a new population - population = toolbox.generate() +# def c2(individual): +# if individual[1] < 0.1: +# return True +# return False - # Evaluate the individuals - for individual in population: - constraint_violation = c1(individual), c2(individual) - if not any(constraint_violation): - individual.fitness.values = toolbox.evaluate(individual) - individual.fitness.constraint_violation = constraint_violation +# N = 5 +# NGEN = 15000 +# optimum = 0.015 - if best is None or individual.fitness >= best.fitness: - best = individual +# toolbox = base.Toolbox() +# toolbox.register("evaluate", benchmarks.sphere) +# restarts = 10 - # Stop when we've reached some kind of optimum - if best.fitness.values[0] - optimum < 1e-7: - restarts = 0 - break +# # Allow a couple of restarts +# while restarts > 0: +# parent = (numpy.random.rand(N) * 2) + 1 - # Update the strategy with the evaluated individuals - toolbox.update(population) +# strategy = cma.ActiveOnePlusLambdaStrategy(parent, 0.5, [0, 0, 0.1, 0, 0], lambda_=1) - if strategy.condition_number > 10e12: - # We've become unstable - break +# toolbox.register("generate", strategy.generate, ind_init=creator.__dict__[INDCLSNAME]) +# toolbox.register("update", strategy.update) - restarts -= 1 +# best = None - self.assertLess(best.fitness.values[0] - optimum, 1e-7) +# for gen in range(NGEN): +# # Generate a new population +# population = toolbox.generate() - def test_cma_mixed_integer_1_p_20_with_constraint(self): - def c1(individual): - if individual[0] + individual[1] < 0.1: - return True - return False +# # Evaluate the individuals +# for individual in population: +# constraint_violation = c1(individual), c2(individual) +# if not any(constraint_violation): +# individual.fitness.values = toolbox.evaluate(individual) +# individual.fitness.constraint_violation = constraint_violation - def c2(individual): - if individual[3] < 0.1: - return True - return False +# if best is None or individual.fitness >= best.fitness: +# best = individual - N = 5 - NGEN = 15000 - optimum = 0.015 +# # Stop when we've reached some kind of optimum +# if best.fitness.values[0] - optimum < 1e-7: +# restarts = 0 +# break - toolbox = base.Toolbox() - toolbox.register("evaluate", benchmarks.sphere) - restarts = 10 +# # Update the strategy with the evaluated individuals +# toolbox.update(population) - # Allow a couple of restarts - while restarts > 0: - parent = (numpy.random.rand(N) * 2) + 1 +# if strategy.condition_number > 10e12: +# # We've become unstable +# break - strategy = cma.ActiveOnePlusLambdaStrategy(parent, 0.5, [0, 0, 0.1, 0, 0], lambda_=20) +# restarts -= 1 - toolbox.register("generate", strategy.generate, ind_init=creator.__dict__[INDCLSNAME]) - toolbox.register("update", strategy.update) +# self.assertLess(best.fitness.values[0] - optimum, 1e-7) - best = None +# def test_cma_mixed_integer_1_p_20_with_constraint(self): +# def c1(individual): +# if individual[0] + individual[1] < 0.1: +# return True +# return False - for gen in range(NGEN): - # Generate a new population - population = toolbox.generate() +# def c2(individual): +# if individual[3] < 0.1: +# return True +# return False - # Evaluate the individuals - for individual in population: - constraint_violation = c1(individual), c2(individual) - if not any(constraint_violation): - individual.fitness.values = toolbox.evaluate(individual) - individual.fitness.constraint_violation = constraint_violation +# N = 5 +# NGEN = 15000 +# optimum = 0.015 - if best is None or individual.fitness >= best.fitness: - best = individual +# toolbox = base.Toolbox() +# toolbox.register("evaluate", benchmarks.sphere) +# restarts = 10 - if best.fitness.values[0] - optimum < 1e-7: - restarts = 0 - break +# # Allow a couple of restarts +# while restarts > 0: +# parent = (numpy.random.rand(N) * 2) + 1 - # Stop when we've reached some kind of optimum - toolbox.update(population) +# strategy = cma.ActiveOnePlusLambdaStrategy(parent, 0.5, [0, 0, 0.1, 0, 0], lambda_=20) - if strategy.condition_number > 10e12: - # We've become unstable - break +# toolbox.register("generate", strategy.generate, ind_init=creator.__dict__[INDCLSNAME]) +# toolbox.register("update", strategy.update) - restarts -= 1 +# best = None - self.assertLess(best.fitness.values[0] - optimum, 1e-7) +# for gen in range(NGEN): +# # Generate a new population +# population = toolbox.generate() +# # Evaluate the individuals +# for individual in population: +# constraint_violation = c1(individual), c2(individual) +# if not any(constraint_violation): +# individual.fitness.values = toolbox.evaluate(individual) +# individual.fitness.constraint_violation = constraint_violation -class TestMultiObjective(TearDownCreatorTestCase): - def setUp(self): - creator.create(FITCLSNAME, base.Fitness, weights=(-1.0, -1.0)) - creator.create(INDCLSNAME, list, fitness=creator.__dict__[FITCLSNAME]) +# if best is None or individual.fitness >= best.fitness: +# best = individual - def test_nsga2(self): - NDIM = 5 - BOUND_LOW, BOUND_UP = 0.0, 1.0 - MU = 16 - NGEN = 100 +# if best.fitness.values[0] - optimum < 1e-7: +# restarts = 0 +# break - toolbox = base.Toolbox() - toolbox.register("attr_float", random.uniform, BOUND_LOW, BOUND_UP) - toolbox.register("individual", tools.initRepeat, creator.__dict__[INDCLSNAME], toolbox.attr_float, NDIM) - toolbox.register("population", tools.initRepeat, list, toolbox.individual) +# # Stop when we've reached some kind of optimum +# toolbox.update(population) - toolbox.register("evaluate", benchmarks.zdt1) - toolbox.register("mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0) - toolbox.register("mutate", tools.mutPolynomialBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0, indpb=1.0/NDIM) - toolbox.register("select", tools.selNSGA2) +# if strategy.condition_number > 10e12: +# # We've become unstable +# break - pop = toolbox.population(n=MU) - fitnesses = toolbox.map(toolbox.evaluate, pop) - for ind, fit in zip(pop, fitnesses): - ind.fitness.values = fit +# restarts -= 1 - pop = toolbox.select(pop, len(pop)) - for gen in range(1, NGEN): - offspring = tools.selTournamentDCD(pop, len(pop)) - offspring = [toolbox.clone(ind) for ind in offspring] +# self.assertLess(best.fitness.values[0] - optimum, 1e-7) - for ind1, ind2 in zip(offspring[::2], offspring[1::2]): - if random.random() <= 0.9: - toolbox.mate(ind1, ind2) - toolbox.mutate(ind1) - toolbox.mutate(ind2) - del ind1.fitness.values, ind2.fitness.values +# class TestMultiObjective(TearDownCreatorTestCase): +# def setUp(self): +# creator.create(FITCLSNAME, base.Fitness, weights=(-1.0, -1.0)) +# creator.create(INDCLSNAME, list, fitness=creator.__dict__[FITCLSNAME]) - 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 +# def test_nsga2(self): +# NDIM = 5 +# BOUND_LOW, BOUND_UP = 0.0, 1.0 +# MU = 16 +# NGEN = 100 - pop = toolbox.select(pop + offspring, MU) +# toolbox = base.Toolbox() +# toolbox.register("attr_float", random.uniform, BOUND_LOW, BOUND_UP) +# toolbox.register("individual", tools.initRepeat, creator.__dict__[INDCLSNAME], toolbox.attr_float, NDIM) +# toolbox.register("population", tools.initRepeat, list, toolbox.individual) - hv = hypervolume(pop, [11.0, 11.0]) - # hv = 120.777 # Optimal value +# toolbox.register("evaluate", benchmarks.zdt1) +# toolbox.register("mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0) +# toolbox.register("mutate", tools.mutPolynomialBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0, indpb=1.0/NDIM) +# toolbox.register("select", tools.selNSGA2) - self.assertGreater(hv, HV_THRESHOLD) +# pop = toolbox.population(n=MU) +# fitnesses = toolbox.map(toolbox.evaluate, pop) +# for ind, fit in zip(pop, fitnesses): +# ind.fitness.values = fit - for ind in pop: - self.assertTrue(all(numpy.asarray(ind) >= BOUND_LOW)) - self.assertTrue(all(numpy.asarray(ind) <= BOUND_UP)) +# pop = toolbox.select(pop, len(pop)) +# for gen in range(1, NGEN): +# offspring = tools.selTournamentDCD(pop, len(pop)) +# offspring = [toolbox.clone(ind) for ind in offspring] - def test_nsga3(self): - NDIM = 5 - BOUND_LOW, BOUND_UP = 0.0, 1.0 - MU = 16 - NGEN = 100 +# for ind1, ind2 in zip(offspring[::2], offspring[1::2]): +# if random.random() <= 0.9: +# toolbox.mate(ind1, ind2) - ref_points = tools.uniform_reference_points(2, p=12) +# toolbox.mutate(ind1) +# toolbox.mutate(ind2) +# del ind1.fitness.values, ind2.fitness.values - toolbox = base.Toolbox() - toolbox.register("attr_float", random.uniform, BOUND_LOW, BOUND_UP) - toolbox.register("individual", tools.initRepeat, creator.__dict__[INDCLSNAME], toolbox.attr_float, NDIM) - toolbox.register("population", tools.initRepeat, list, toolbox.individual) - - toolbox.register("evaluate", benchmarks.zdt1) - toolbox.register("mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0) - toolbox.register("mutate", tools.mutPolynomialBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0, indpb=1.0/NDIM) - toolbox.register("select", tools.selNSGA3, ref_points=ref_points) - - pop = toolbox.population(n=MU) - fitnesses = toolbox.map(toolbox.evaluate, pop) - for ind, fit in zip(pop, fitnesses): - ind.fitness.values = fit - - pop = toolbox.select(pop, len(pop)) - # Begin the generational process - for gen in range(1, NGEN): - # Vary the individuals - offspring = list(islice(algorithms.and_variation(pop, toolbox, 1.0, 1.0), len(pop))) - - # 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 - - # Select the next generation population - pop = toolbox.select(pop + offspring, MU) - - hv = hypervolume(pop, [11.0, 11.0]) - # hv = 120.777 # Optimal value - - self.assertGreater(hv, HV_THRESHOLD) - - for ind in pop: - self.assertTrue(all(numpy.asarray(ind) >= BOUND_LOW)) - self.assertTrue(all(numpy.asarray(ind) <= BOUND_UP)) - -@unittest.skipUnless(numpy, "requires numpy") -class TestMultiObjectiveNumpy(TearDownCreatorTestCase): - def setUp(self): - creator.create(FITCLSNAME, base.Fitness, weights=(-1.0, -1.0)) - creator.create(INDCLSNAME, numpy.ndarray, fitness=creator.__dict__[FITCLSNAME]) - - def test_mo_cma_es(self): - - def distance(feasible_ind, original_ind): - """A distance function to the feasibility region.""" - return sum((f - o)**2 for f, o in zip(feasible_ind, original_ind)) - - def closest_feasible(individual): - """A function returning a valid individual from an invalid one.""" - feasible_ind = numpy.array(individual) - feasible_ind = numpy.maximum(BOUND_LOW, feasible_ind) - feasible_ind = numpy.minimum(BOUND_UP, feasible_ind) - return feasible_ind - - def valid(individual): - """Determines if the individual is valid or not.""" - if any(individual < BOUND_LOW) or any(individual > BOUND_UP): - return False - return True +# 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 - NDIM = 5 - BOUND_LOW, BOUND_UP = 0.0, 1.0 - MU, LAMBDA = 10, 10 - NGEN = 500 +# pop = toolbox.select(pop + offspring, MU) - numpy.random.seed(128) +# hv = hypervolume(pop, [11.0, 11.0]) +# # hv = 120.777 # Optimal value - # The MO-CMA-ES algorithm takes a full population as argument - population = [creator.__dict__[INDCLSNAME](x) for x in numpy.random.uniform(BOUND_LOW, BOUND_UP, (MU, NDIM))] +# self.assertGreater(hv, HV_THRESHOLD) - toolbox = base.Toolbox() - toolbox.register("evaluate", benchmarks.zdt1) - toolbox.decorate("evaluate", tools.ClosestValidPenalty(valid, closest_feasible, 1.0e+6, distance)) +# for ind in pop: +# self.assertTrue(all(numpy.asarray(ind) >= BOUND_LOW)) +# self.assertTrue(all(numpy.asarray(ind) <= BOUND_UP)) - for ind in population: - ind.fitness.values = toolbox.evaluate(ind) +# def test_nsga3(self): +# NDIM = 5 +# BOUND_LOW, BOUND_UP = 0.0, 1.0 +# MU = 16 +# NGEN = 100 - strategy = cma.MultiObjectiveStrategy(population, sigma=1.0, mu=MU, lambda_=LAMBDA) +# ref_points = tools.uniform_reference_points(2, p=12) - toolbox.register("generate", strategy.generate, creator.__dict__[INDCLSNAME]) - toolbox.register("update", strategy.update) +# toolbox = base.Toolbox() +# toolbox.register("attr_float", random.uniform, BOUND_LOW, BOUND_UP) +# toolbox.register("individual", tools.initRepeat, creator.__dict__[INDCLSNAME], toolbox.attr_float, NDIM) +# toolbox.register("population", tools.initRepeat, list, toolbox.individual) - for gen in range(NGEN): - # Generate a new population - population = toolbox.generate() - - # Evaluate the individuals - fitnesses = toolbox.map(toolbox.evaluate, population) - for ind, fit in zip(population, fitnesses): - ind.fitness.values = fit - - # Update the strategy with the evaluated individuals - toolbox.update(population) - - # Note that we use a penalty to guide the search to feasible solutions, - # but there is no guarantee that individuals are valid. - # We expect the best individuals will be within bounds or very close. - num_valid = 0 - for ind in strategy.parents: - dist = distance(closest_feasible(ind), ind) - if numpy.isclose(dist, 0.0, rtol=1.e-5, atol=1.e-5): - num_valid += 1 - self.assertGreaterEqual(num_valid, len(strategy.parents)) - - # Note that NGEN=500 is enough to get consistent hypervolume > 116, - # but not 119. More generations would help but would slow down testing. - hv = hypervolume(strategy.parents, [11.0, 11.0]) - self.assertGreater(hv, HV_THRESHOLD, msg="Hypervolume is lower than expected") +# toolbox.register("evaluate", benchmarks.zdt1) +# toolbox.register("mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0) +# toolbox.register("mutate", tools.mutPolynomialBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0, indpb=1.0/NDIM) +# toolbox.register("select", tools.selNSGA3, ref_points=ref_points) + +# pop = toolbox.population(n=MU) +# fitnesses = toolbox.map(toolbox.evaluate, pop) +# for ind, fit in zip(pop, fitnesses): +# ind.fitness.values = fit + +# pop = toolbox.select(pop, len(pop)) +# # Begin the generational process +# for gen in range(1, NGEN): +# # Vary the individuals +# offspring = list(islice(algorithms.and_variation(pop, toolbox, 1.0, 1.0), len(pop))) + +# # 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 + +# # Select the next generation population +# pop = toolbox.select(pop + offspring, MU) + +# hv = hypervolume(pop, [11.0, 11.0]) +# # hv = 120.777 # Optimal value + +# self.assertGreater(hv, HV_THRESHOLD) + +# for ind in pop: +# self.assertTrue(all(numpy.asarray(ind) >= BOUND_LOW)) +# self.assertTrue(all(numpy.asarray(ind) <= BOUND_UP)) + +# @unittest.skipUnless(numpy, "requires numpy") +# class TestMultiObjectiveNumpy(TearDownCreatorTestCase): +# def setUp(self): +# creator.create(FITCLSNAME, base.Fitness, weights=(-1.0, -1.0)) +# creator.create(INDCLSNAME, numpy.ndarray, fitness=creator.__dict__[FITCLSNAME]) + +# def test_mo_cma_es(self): + +# def distance(feasible_ind, original_ind): +# """A distance function to the feasibility region.""" +# return sum((f - o)**2 for f, o in zip(feasible_ind, original_ind)) + +# def closest_feasible(individual): +# """A function returning a valid individual from an invalid one.""" +# feasible_ind = numpy.array(individual) +# feasible_ind = numpy.maximum(BOUND_LOW, feasible_ind) +# feasible_ind = numpy.minimum(BOUND_UP, feasible_ind) +# return feasible_ind + +# def valid(individual): +# """Determines if the individual is valid or not.""" +# if any(individual < BOUND_LOW) or any(individual > BOUND_UP): +# return False +# return True + +# NDIM = 5 +# BOUND_LOW, BOUND_UP = 0.0, 1.0 +# MU, LAMBDA = 10, 10 +# NGEN = 500 + +# numpy.random.seed(128) + +# # The MO-CMA-ES algorithm takes a full population as argument +# population = [creator.__dict__[INDCLSNAME](x) for x in numpy.random.uniform(BOUND_LOW, BOUND_UP, (MU, NDIM))] + +# toolbox = base.Toolbox() +# toolbox.register("evaluate", benchmarks.zdt1) +# toolbox.decorate("evaluate", tools.ClosestValidPenalty(valid, closest_feasible, 1.0e+6, distance)) + +# for ind in population: +# ind.fitness.values = toolbox.evaluate(ind) + +# strategy = cma.MultiObjectiveStrategy(population, sigma=1.0, mu=MU, lambda_=LAMBDA) + +# toolbox.register("generate", strategy.generate, creator.__dict__[INDCLSNAME]) +# toolbox.register("update", strategy.update) + +# for gen in range(NGEN): +# # Generate a new population +# population = toolbox.generate() + +# # Evaluate the individuals +# fitnesses = toolbox.map(toolbox.evaluate, population) +# for ind, fit in zip(population, fitnesses): +# ind.fitness.values = fit + +# # Update the strategy with the evaluated individuals +# toolbox.update(population) + +# # Note that we use a penalty to guide the search to feasible solutions, +# # but there is no guarantee that individuals are valid. +# # We expect the best individuals will be within bounds or very close. +# num_valid = 0 +# for ind in strategy.parents: +# dist = distance(closest_feasible(ind), ind) +# if numpy.isclose(dist, 0.0, rtol=1.e-5, atol=1.e-5): +# num_valid += 1 +# self.assertGreaterEqual(num_valid, len(strategy.parents)) + +# # Note that NGEN=500 is enough to get consistent hypervolume > 116, +# # but not 119. More generations would help but would slow down testing. +# hv = hypervolume(strategy.parents, [11.0, 11.0]) +# self.assertGreater(hv, HV_THRESHOLD, msg="Hypervolume is lower than expected") diff --git a/tests/test_creator.py b/tests/test_creator.py deleted file mode 100644 index 09ac54319..000000000 --- a/tests/test_creator.py +++ /dev/null @@ -1,77 +0,0 @@ -# This file is part of DEAP. -# -# DEAP is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# DEAP is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with DEAP. If not, see . - -import unittest - -import array - -try: - import numpy -except ImportError: - numpy = False - -from deap import creator - -CNAME = "CLASS_NAME" - - -class TestCreator(unittest.TestCase): - def tearDown(self): - creator.__dict__.pop(CNAME) - - def test_create(self): - creator.create(CNAME, list) - l = creator.__dict__[CNAME]([1, 2, 3, 4]) - self.assertSequenceEqual(l, [1, 2, 3, 4]) - - def test_attribute(self): - creator.create(CNAME, list, a=1) - l = creator.__dict__[CNAME]([1, 2, 3, 4]) - self.assertEqual(l.a, 1) - - def test_array(self): - creator.create(CNAME, array.array, typecode="i") - a = creator.__dict__[CNAME]([1, 2, 3, 4]) - b = creator.__dict__[CNAME]([5, 6, 7, 8]) - - a[1:3], b[1:3] = b[1:3], a[1:3] - ta = array.array("i", [1, 6, 7, 4]) - tb = array.array("i", [5, 2, 3, 8]) - self.assertSequenceEqual(a, ta) - self.assertSequenceEqual(b, tb) - - @unittest.skipIf(not numpy, "Cannot import Numpy numerical library") - def test_numpy_nocopy(self): - creator.create(CNAME, numpy.ndarray) - a = creator.__dict__[CNAME]([1, 2, 3, 4]) - b = creator.__dict__[CNAME]([5, 6, 7, 8]) - - a[1:3], b[1:3] = b[1:3], a[1:3] - ta = numpy.array([1, 6, 7, 4]) - tb = numpy.array([5, 6, 7, 8]) - numpy.testing.assert_array_equal(a, ta) - numpy.testing.assert_array_equal(b, tb) - - @unittest.skipIf(not numpy, "Cannot import Numpy numerical library") - def test_numpy_copy(self): - creator.create(CNAME, numpy.ndarray) - a = creator.__dict__[CNAME]([1, 2, 3, 4]) - b = creator.__dict__[CNAME]([5, 6, 7, 8]) - - a[1:3], b[1:3] = b[1:3].copy(), a[1:3].copy() - ta = numpy.array([1, 6, 7, 4]) - tb = numpy.array([5, 2, 3, 8]) - numpy.testing.assert_array_equal(a, ta) - numpy.testing.assert_array_equal(b, tb) diff --git a/tests/test_multiproc.py b/tests/test_multiproc.py index 19a025f55..e6cc153c1 100644 --- a/tests/test_multiproc.py +++ b/tests/test_multiproc.py @@ -1,26 +1,26 @@ -import multiprocessing -import unittest +# import multiprocessing +# import unittest -from deap import base -from deap import creator +# from deap import base +# from deap import creator -def _evalOneMax(individual): - return sum(individual), +# def _evalOneMax(individual): +# return sum(individual), -def test_multiproc(): - creator.create("FitnessMax", base.Fitness, weights=(1.0,)) - creator.create("Individual", list, fitness=creator.FitnessMax) +# def test_multiproc(): +# creator.create("FitnessMax", base.Fitness, weights=(1.0,)) +# creator.create("Individual", list, fitness=creator.FitnessMax) - toolbox = base.Toolbox() - toolbox.register("evaluate", _evalOneMax) +# toolbox = base.Toolbox() +# toolbox.register("evaluate", _evalOneMax) - # Process Pool of 4 workers - pool = multiprocessing.Pool(processes=4) - toolbox.register("map", pool.map) +# # Process Pool of 4 workers +# pool = multiprocessing.Pool(processes=4) +# toolbox.register("map", pool.map) - pop = [[1]*20 for _ in range(100)] - fitnesses = toolbox.map(toolbox.evaluate, pop) - for ind, fit in zip(pop, fitnesses): - assert fit == (sum(ind),) \ No newline at end of file +# pop = [[1]*20 for _ in range(100)] +# fitnesses = toolbox.map(toolbox.evaluate, pop) +# for ind, fit in zip(pop, fitnesses): +# assert fit == (sum(ind),) \ No newline at end of file diff --git a/tests/test_pickle.py b/tests/test_pickle.py index ed8c327d1..a4959434a 100644 --- a/tests/test_pickle.py +++ b/tests/test_pickle.py @@ -1,159 +1,159 @@ -import sys -import unittest -import array -import pickle -import operator -import platform - -import numpy - -from deap import creator -from deap import base -from deap import gp -from deap import tools - -def func(): - return "True" - -class Pickling(unittest.TestCase): - - def setUp(self): - creator.create("FitnessMax", base.Fitness, weights=(1.0,)) - creator.create("IndList", list, fitness=creator.FitnessMax) - creator.create("IndArray", array.array, typecode='f', fitness=creator.FitnessMax) - creator.create("IndNDArray", numpy.ndarray, typecode='f', fitness=creator.FitnessMax) - creator.create("IndTree", gp.PrimitiveTree, fitness=creator.FitnessMax) - self.toolbox = base.Toolbox() - self.toolbox.register("func", func) - self.toolbox.register("lambda_func", lambda: "True") - - def tearDown(self): - del creator.FitnessMax - del creator.IndList - del creator.IndArray - del creator.IndNDArray - del creator.IndTree - - def test_pickle_fitness(self): - fitness = creator.FitnessMax() - fitness.values = (1.0,) - fitness_s = pickle.dumps(fitness) - fitness_l = pickle.loads(fitness_s) - self.assertEqual(fitness, fitness_l, "Unpickled fitness != pickled fitness") - - def test_pickle_ind_list(self): - ind = creator.IndList([1.0, 2.0, 3.0]) - ind.fitness.values = (4.0,) - ind_s = pickle.dumps(ind) - ind_l = pickle.loads(ind_s) - self.assertEqual(ind, ind_l, "Unpickled individual list != pickled individual list") - self.assertEqual(ind.fitness, ind_l.fitness, "Unpickled individual fitness != pickled individual fitness") - - def test_pickle_ind_array(self): - ind = creator.IndArray([1.0, 2.0, 3.0]) - ind.fitness.values = (4.0,) - ind_s = pickle.dumps(ind) - ind_l = pickle.loads(ind_s) - self.assertEqual(ind, ind_l, "Unpickled individual array != pickled individual array") - self.assertEqual(ind.fitness, ind_l.fitness, "Unpickled individual fitness != pickled individual fitness") - - # @unittest.skipIf(platform.python_implementation() == "PyPy", "PyPy support for pickling ndarrays is very unstable.") - def test_pickle_ind_ndarray(self): - ind = creator.IndNDArray([1.0, 2.0, 3.0]) - ind.fitness.values = (4.0,) - ind_s = pickle.dumps(ind) - ind_l = pickle.loads(ind_s) - self.assertTrue(all(ind == ind_l), "Unpickled individual numpy.ndarray != pickled individual numpy.ndarray") - self.assertEqual(ind.fitness, ind_l.fitness, "Unpickled individual fitness != pickled individual fitness") - - def test_pickle_tree_input(self): - pset = gp.PrimitiveSetTyped("MAIN", [int], int, "IN") - pset.addPrimitive(operator.add, [int, int], int) - - expr = gp.genFull(pset, min_=1, max_=1) - ind = creator.IndTree(expr) - ind.fitness.values = (1.0,) - ind_s = pickle.dumps(ind, pickle.HIGHEST_PROTOCOL) - ind_l = pickle.loads(ind_s) - msg = "Unpickled individual %s != pickled individual %s" % (str(ind), str(ind_l)) - self.assertEqual(ind, ind_l, msg) - msg = "Unpickled fitness %s != pickled fitness %s" % (str(ind.fitness), str(ind_l.fitness)) - self.assertEqual(ind.fitness, ind_l.fitness, msg) - - def test_pickle_tree_term(self): - pset = gp.PrimitiveSetTyped("MAIN", [], int, "IN") - pset.addPrimitive(operator.add, [int, int], int) - pset.addTerminal(1, int) - - expr = gp.genFull(pset, min_=1, max_=1) - ind = creator.IndTree(expr) - ind.fitness.values = (1.0,) - ind_s = pickle.dumps(ind, pickle.HIGHEST_PROTOCOL) - ind_l = pickle.loads(ind_s) - msg = "Unpickled individual %s != pickled individual %s" % (str(ind), str(ind_l)) - self.assertEqual(ind, ind_l, msg) - msg = "Unpickled fitness %s != pickled fitness %s" % (str(ind.fitness), str(ind_l.fitness)) - self.assertEqual(ind.fitness, ind_l.fitness, msg) - - def test_pickle_tree_ephemeral(self): - pset = gp.PrimitiveSetTyped("MAIN", [], int, "IN") - pset.addPrimitive(operator.add, [int, int], int) - pset.addEphemeralConstant("E1", lambda: 2, int) - - expr = gp.genFull(pset, min_=1, max_=1) - ind = creator.IndTree(expr) - ind.fitness.values = (1.0,) - ind_s = pickle.dumps(ind, pickle.HIGHEST_PROTOCOL) - ind_l = pickle.loads(ind_s) - msg = "Unpickled individual %s != pickled individual %s" % (str(ind), str(ind_l)) - self.assertEqual(ind, ind_l, msg) - msg = "Unpickled fitness %s != pickled fitness %s" % (str(ind.fitness), str(ind_l.fitness)) - self.assertEqual(ind.fitness, ind_l.fitness, msg) - - def test_pickle_population(self): - ind1 = creator.IndList([1.0,2.0,3.0]) - ind1.fitness.values = (1.0,) - ind2 = creator.IndList([4.0,5.0,6.0]) - ind2.fitness.values = (2.0,) - ind3 = creator.IndList([7.0,8.0,9.0]) - ind3.fitness.values = (3.0,) - - pop = [ind1, ind2, ind3] - - pop_s = pickle.dumps(pop) - pop_l = pickle.loads(pop_s) - - self.assertEqual(pop[0], pop_l[0], "Unpickled individual list != pickled individual list") - self.assertEqual(pop[0].fitness, pop_l[0].fitness, "Unpickled individual fitness != pickled individual fitness") - self.assertEqual(pop[1], pop_l[1], "Unpickled individual list != pickled individual list") - self.assertEqual(pop[1].fitness, pop_l[1].fitness, "Unpickled individual fitness != pickled individual fitness") - self.assertEqual(pop[2], pop_l[2], "Unpickled individual list != pickled individual list") - self.assertEqual(pop[2].fitness, pop_l[2].fitness, "Unpickled individual fitness != pickled individual fitness") - - - # @unittest.skipIf(platform.python_implementation() == "PyPy", "PyPy support for pickling ndarrays (thus stats) is very unstable.") - def test_pickle_logbook(self): - stats = tools.Statistics() - logbook = tools.Logbook() - - stats.register("mean", numpy.mean) - record = stats.compile([1,2,3,4,5,6,8,9,10]) - logbook.record(**record) - - logbook_s = pickle.dumps(logbook) - logbook_r = pickle.loads(logbook_s) - - self.assertEqual(logbook, logbook_r, "Unpickled logbook != pickled logbook") - - @unittest.skipIf(sys.version_info < (2, 7), "Skipping test because Python version < 2.7 does not pickle partials.") - def test_pickle_partial(self): - func_s = pickle.dumps(self.toolbox.func) - func_l = pickle.loads(func_s) - - self.assertEqual(self.toolbox.func(), func_l()) - - -if __name__ == "__main__": - suite = unittest.TestLoader().loadTestsFromTestCase(Pickling) - unittest.TextTestRunner(verbosity=2).run(suite) +# import sys +# import unittest +# import array +# import pickle +# import operator +# import platform + +# import numpy + +# from deap import creator +# from deap import base +# from deap import gp +# from deap import tools + +# def func(): +# return "True" + +# class Pickling(unittest.TestCase): + +# def setUp(self): +# creator.create("FitnessMax", base.Fitness, weights=(1.0,)) +# creator.create("IndList", list, fitness=creator.FitnessMax) +# creator.create("IndArray", array.array, typecode='f', fitness=creator.FitnessMax) +# creator.create("IndNDArray", numpy.ndarray, typecode='f', fitness=creator.FitnessMax) +# creator.create("IndTree", gp.PrimitiveTree, fitness=creator.FitnessMax) +# self.toolbox = base.Toolbox() +# self.toolbox.register("func", func) +# self.toolbox.register("lambda_func", lambda: "True") + +# def tearDown(self): +# del creator.FitnessMax +# del creator.IndList +# del creator.IndArray +# del creator.IndNDArray +# del creator.IndTree + +# def test_pickle_fitness(self): +# fitness = creator.FitnessMax() +# fitness.values = (1.0,) +# fitness_s = pickle.dumps(fitness) +# fitness_l = pickle.loads(fitness_s) +# self.assertEqual(fitness, fitness_l, "Unpickled fitness != pickled fitness") + +# def test_pickle_ind_list(self): +# ind = creator.IndList([1.0, 2.0, 3.0]) +# ind.fitness.values = (4.0,) +# ind_s = pickle.dumps(ind) +# ind_l = pickle.loads(ind_s) +# self.assertEqual(ind, ind_l, "Unpickled individual list != pickled individual list") +# self.assertEqual(ind.fitness, ind_l.fitness, "Unpickled individual fitness != pickled individual fitness") + +# def test_pickle_ind_array(self): +# ind = creator.IndArray([1.0, 2.0, 3.0]) +# ind.fitness.values = (4.0,) +# ind_s = pickle.dumps(ind) +# ind_l = pickle.loads(ind_s) +# self.assertEqual(ind, ind_l, "Unpickled individual array != pickled individual array") +# self.assertEqual(ind.fitness, ind_l.fitness, "Unpickled individual fitness != pickled individual fitness") + +# # @unittest.skipIf(platform.python_implementation() == "PyPy", "PyPy support for pickling ndarrays is very unstable.") +# def test_pickle_ind_ndarray(self): +# ind = creator.IndNDArray([1.0, 2.0, 3.0]) +# ind.fitness.values = (4.0,) +# ind_s = pickle.dumps(ind) +# ind_l = pickle.loads(ind_s) +# self.assertTrue(all(ind == ind_l), "Unpickled individual numpy.ndarray != pickled individual numpy.ndarray") +# self.assertEqual(ind.fitness, ind_l.fitness, "Unpickled individual fitness != pickled individual fitness") + +# def test_pickle_tree_input(self): +# pset = gp.PrimitiveSetTyped("MAIN", [int], int, "IN") +# pset.addPrimitive(operator.add, [int, int], int) + +# expr = gp.genFull(pset, min_=1, max_=1) +# ind = creator.IndTree(expr) +# ind.fitness.values = (1.0,) +# ind_s = pickle.dumps(ind, pickle.HIGHEST_PROTOCOL) +# ind_l = pickle.loads(ind_s) +# msg = "Unpickled individual %s != pickled individual %s" % (str(ind), str(ind_l)) +# self.assertEqual(ind, ind_l, msg) +# msg = "Unpickled fitness %s != pickled fitness %s" % (str(ind.fitness), str(ind_l.fitness)) +# self.assertEqual(ind.fitness, ind_l.fitness, msg) + +# def test_pickle_tree_term(self): +# pset = gp.PrimitiveSetTyped("MAIN", [], int, "IN") +# pset.addPrimitive(operator.add, [int, int], int) +# pset.addTerminal(1, int) + +# expr = gp.genFull(pset, min_=1, max_=1) +# ind = creator.IndTree(expr) +# ind.fitness.values = (1.0,) +# ind_s = pickle.dumps(ind, pickle.HIGHEST_PROTOCOL) +# ind_l = pickle.loads(ind_s) +# msg = "Unpickled individual %s != pickled individual %s" % (str(ind), str(ind_l)) +# self.assertEqual(ind, ind_l, msg) +# msg = "Unpickled fitness %s != pickled fitness %s" % (str(ind.fitness), str(ind_l.fitness)) +# self.assertEqual(ind.fitness, ind_l.fitness, msg) + +# def test_pickle_tree_ephemeral(self): +# pset = gp.PrimitiveSetTyped("MAIN", [], int, "IN") +# pset.addPrimitive(operator.add, [int, int], int) +# pset.addEphemeralConstant("E1", lambda: 2, int) + +# expr = gp.genFull(pset, min_=1, max_=1) +# ind = creator.IndTree(expr) +# ind.fitness.values = (1.0,) +# ind_s = pickle.dumps(ind, pickle.HIGHEST_PROTOCOL) +# ind_l = pickle.loads(ind_s) +# msg = "Unpickled individual %s != pickled individual %s" % (str(ind), str(ind_l)) +# self.assertEqual(ind, ind_l, msg) +# msg = "Unpickled fitness %s != pickled fitness %s" % (str(ind.fitness), str(ind_l.fitness)) +# self.assertEqual(ind.fitness, ind_l.fitness, msg) + +# def test_pickle_population(self): +# ind1 = creator.IndList([1.0,2.0,3.0]) +# ind1.fitness.values = (1.0,) +# ind2 = creator.IndList([4.0,5.0,6.0]) +# ind2.fitness.values = (2.0,) +# ind3 = creator.IndList([7.0,8.0,9.0]) +# ind3.fitness.values = (3.0,) + +# pop = [ind1, ind2, ind3] + +# pop_s = pickle.dumps(pop) +# pop_l = pickle.loads(pop_s) + +# self.assertEqual(pop[0], pop_l[0], "Unpickled individual list != pickled individual list") +# self.assertEqual(pop[0].fitness, pop_l[0].fitness, "Unpickled individual fitness != pickled individual fitness") +# self.assertEqual(pop[1], pop_l[1], "Unpickled individual list != pickled individual list") +# self.assertEqual(pop[1].fitness, pop_l[1].fitness, "Unpickled individual fitness != pickled individual fitness") +# self.assertEqual(pop[2], pop_l[2], "Unpickled individual list != pickled individual list") +# self.assertEqual(pop[2].fitness, pop_l[2].fitness, "Unpickled individual fitness != pickled individual fitness") + + +# # @unittest.skipIf(platform.python_implementation() == "PyPy", "PyPy support for pickling ndarrays (thus stats) is very unstable.") +# def test_pickle_logbook(self): +# stats = tools.Statistics() +# logbook = tools.Logbook() + +# stats.register("mean", numpy.mean) +# record = stats.compile([1,2,3,4,5,6,8,9,10]) +# logbook.record(**record) + +# logbook_s = pickle.dumps(logbook) +# logbook_r = pickle.loads(logbook_s) + +# self.assertEqual(logbook, logbook_r, "Unpickled logbook != pickled logbook") + +# @unittest.skipIf(sys.version_info < (2, 7), "Skipping test because Python version < 2.7 does not pickle partials.") +# def test_pickle_partial(self): +# func_s = pickle.dumps(self.toolbox.func) +# func_l = pickle.loads(func_s) + +# self.assertEqual(self.toolbox.func(), func_l()) + + +# if __name__ == "__main__": +# suite = unittest.TestLoader().loadTestsFromTestCase(Pickling) +# unittest.TextTestRunner(verbosity=2).run(suite)