Skip to content

Commit

Permalink
Merge pull request DEAP#175 from fmv1992/master
Browse files Browse the repository at this point in the history
Fixed bin2float behavior and added test suite for benchamarks.py
  • Loading branch information
fmder authored Feb 5, 2017
2 parents 8b8c821 + 8528d1e commit a1412d7
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 60 deletions.
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
sudo: false
language: python
branches:
only:
- master
- develop

matrix:
include:
- python: "2.7"
Expand All @@ -18,6 +23,7 @@ addons:
install:
- python setup.py install
- pip install $NUMPY
- pip install future
- if [[ ${TRAVIS_PYTHON_VERSION%%.*} == '3' ]]; then 2to3 --output-dir=py3k/deap -W -n deap; fi
# command to run tests
script:
Expand Down
24 changes: 13 additions & 11 deletions deap/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ def varAnd(population, toolbox, cxpb, mutpb):
The variation goes as follow. First, the parental population
:math:`P_\mathrm{p}` is duplicated using the :meth:`toolbox.clone` method
and the result is put into the offspring population :math:`P_\mathrm{o}`.
A first loop over :math:`P_\mathrm{o}` is executed to mate pairs of consecutive
individuals. According to the crossover probability *cxpb*, the
and the result is put into the offspring population :math:`P_\mathrm{o}`. A
first loop over :math:`P_\mathrm{o}` is executed to mate pairs of
consecutive individuals. According to the crossover probability *cxpb*, the
individuals :math:`\mathbf{x}_i` and :math:`\mathbf{x}_{i+1}` are mated
using the :meth:`toolbox.mate` method. The resulting children
:math:`\mathbf{y}_i` and :math:`\mathbf{y}_{i+1}` replace their respective
parents in :math:`P_\mathrm{o}`. A second loop over the resulting
:math:`P_\mathrm{o}` is executed to mutate every individual with a
probability *mutpb*. When an individual is mutated it replaces its not
mutated version in :math:`P_\mathrm{o}`. The resulting
:math:`P_\mathrm{o}` is returned.
mutated version in :math:`P_\mathrm{o}`. The resulting :math:`P_\mathrm{o}`
is returned.
This variation is named *And* beceause of its propention to apply both
crossover and mutation on the individuals. Note that both operators are
Expand All @@ -70,7 +70,8 @@ def varAnd(population, toolbox, cxpb, mutpb):
# Apply crossover and mutation on the offspring
for i in range(1, len(offspring), 2):
if random.random() < cxpb:
offspring[i - 1], offspring[i] = toolbox.mate(offspring[i - 1], offspring[i])
offspring[i - 1], offspring[i] = toolbox.mate(offspring[i - 1],
offspring[i])
del offspring[i - 1].fitness.values, offspring[i].fitness.values

for i in range(len(offspring)):
Expand Down Expand Up @@ -223,8 +224,9 @@ def varOr(population, toolbox, lambda_, cxpb, mutpb):
shall be in :math:`[0, 1]`, the reproduction probability is
1 - *cxpb* - *mutpb*.
"""
assert (cxpb + mutpb) <= 1.0, ("The sum of the crossover and mutation "
"probabilities must be smaller or equal to 1.0.")
assert (cxpb + mutpb) <= 1.0, (
"The sum of the crossover and mutation probabilities must be smaller "
"or equal to 1.0.")

offspring = []
for _ in xrange(lambda_):
Expand Down Expand Up @@ -382,9 +384,9 @@ def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen,
.. note::
Care must be taken when the lambda:mu ratio is 1 to 1 as a non-stochastic
selection will result in no selection at all as
the operator selects *lambda* individuals from a pool of *mu*.
Care must be taken when the lambda:mu ratio is 1 to 1 as a
non-stochastic selection will result in no selection at all as the
operator selects *lambda* individuals from a pool of *mu*.
This function expects :meth:`toolbox.mate`, :meth:`toolbox.mutate`,
Expand Down
7 changes: 4 additions & 3 deletions deap/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,10 @@ def setValues(self, values):
_, _, 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
"%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 = ()
Expand Down
55 changes: 32 additions & 23 deletions deap/benchmarks/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,34 @@
# You should have received a copy of the GNU Lesser General Public
# License along with DEAP. If not, see <http://www.gnu.org/licenses/>.

from __future__ import division
from functools import wraps
import math

def bin2float(min_, max_, nbits):
"""Convert a binary array into an array of float where each
float is composed of *nbits* and is between *min_* and *max_*
and return the result of the decorated function.
.. note::
This decorator requires the first argument of
the evaluation function to be named *individual*.
"""
def wrap(function):
@wraps(function)
def wrapped_function(individual, *args, **kargs):
nelem = len(individual)/nbits
# User must take care to make nelem an integer.
nelem = len(individual)//nbits
decoded = [0] * nelem
for i in xrange(nelem):
gene = int("".join(map(str, individual[i*nbits:i*nbits+nbits])), 2)
gene = int("".join(map(str,
individual[i*nbits:i*nbits+nbits])),
2)
div = 2**nbits - 1
temp = float(gene)/float(div)
temp = gene/div
decoded[i] = min_ + (temp * (max_ - min_))
return function(decoded, *args, **kargs)
return wrapped_function
return wrap


def trap(individual):
u = sum(individual)
k = len(individual)
Expand All @@ -45,6 +49,7 @@ def trap(individual):
else:
return k - 1 - u


def inv_trap(individual):
u = sum(individual)
k = len(individual)
Expand All @@ -53,44 +58,47 @@ def inv_trap(individual):
else:
return u - 1


def chuang_f1(individual):
"""Binary deceptive function from : Multivariate Multi-Model Approach for
Globally Multimodal Problems by Chung-Yao Chuang and Wen-Lian Hsu.
The function takes individual of 40+1 dimensions and has two global optima
in [1,1,...,1] and [0,0,...,0].
"""
"""
total = 0
if individual[-1] == 0:
for i in xrange(0,len(individual)-1,4):
for i in xrange(0, len(individual)-1, 4):
total += inv_trap(individual[i:i+4])
else:
for i in xrange(0,len(individual)-1,4):
for i in xrange(0, len(individual)-1, 4):
total += trap(individual[i:i+4])
return total,


def chuang_f2(individual):
"""Binary deceptive function from : Multivariate Multi-Model Approach for
Globally Multimodal Problems by Chung-Yao Chuang and Wen-Lian Hsu.
The function takes individual of 40+1 dimensions and has four global optima
in [1,1,...,0,0], [0,0,...,1,1], [1,1,...,1] and [0,0,...,0].
"""
in [1,1,...,0,0], [0,0,...,1,1], [1,1,...,1] and [0,0,...,0].
"""
total = 0
if individual[-2] == 0 and individual[-1] == 0:
for i in xrange(0,len(individual)-2,8):
for i in xrange(0, len(individual)-2, 8):
total += inv_trap(individual[i:i+4]) + inv_trap(individual[i+4:i+8])
elif individual[-2] == 0 and individual[-1] == 1:
for i in xrange(0,len(individual)-2,8):
for i in xrange(0, len(individual)-2, 8):
total += inv_trap(individual[i:i+4]) + trap(individual[i+4:i+8])
elif individual[-2] == 1 and individual[-1] == 0:
for i in xrange(0,len(individual)-2,8):
for i in xrange(0, len(individual)-2, 8):
total += trap(individual[i:i+4]) + inv_trap(individual[i+4:i+8])
else:
for i in xrange(0,len(individual)-2,8):
for i in xrange(0, len(individual)-2, 8):
total += trap(individual[i:i+4]) + trap(individual[i+4:i+8])
return total,


def chuang_f3(individual):
"""Binary deceptive function from : Multivariate Multi-Model Approach for
Globally Multimodal Problems by Chung-Yao Chuang and Wen-Lian Hsu.
Expand All @@ -100,29 +108,31 @@ def chuang_f3(individual):
"""
total = 0
if individual[-1] == 0:
for i in xrange(0,len(individual)-1,4):
for i in xrange(0, len(individual)-1, 4):
total += inv_trap(individual[i:i+4])
else:
for i in xrange(2,len(individual)-3,4):
for i in xrange(2, len(individual)-3, 4):
total += inv_trap(individual[i:i+4])
total += trap(individual[-2:]+individual[:2])
return total,


# Royal Road Functions
def royal_road1(individual, order):
"""Royal Road Function R1 as presented by Melanie Mitchell in :
"""Royal Road Function R1 as presented by Melanie Mitchell in :
"An introduction to Genetic Algorithms".
"""
nelem = len(individual) / order
nelem = len(individual) // order
max_value = int(2**order - 1)
total = 0
for i in xrange(nelem):
value = int("".join(map(str, individual[i*order:i*order+order])), 2)
total += int(order) * int(value/max_value)
return total,


def royal_road2(individual, order):
"""Royal Road Function R2 as presented by Melanie Mitchell in :
"""Royal Road Function R2 as presented by Melanie Mitchell in :
"An introduction to Genetic Algorithms".
"""
total = 0
Expand All @@ -131,4 +141,3 @@ def royal_road2(individual, order):
total += royal_road1(norder, individual)[0]
norder *= 2
return total,

16 changes: 8 additions & 8 deletions deap/tests/test_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

HV_THRESHOLD = 119.0


def setup_func_single_obj():
creator.create(FITCLSNAME, base.Fitness, weights=(-1.0,))
creator.create(INDCLSNAME, list, fitness=creator.__dict__[FITCLSNAME])
Expand All @@ -57,7 +57,7 @@ def test_cma():
NDIM = 5

strategy = cma.Strategy(centroid=[0.0]*NDIM, sigma=1.0)

toolbox = base.Toolbox()
toolbox.register("evaluate", benchmarks.sphere)
toolbox.register("generate", strategy.generate, creator.__dict__[INDCLSNAME])
Expand Down Expand Up @@ -94,15 +94,15 @@ def test_nsga2():
for gen in range(1, NGEN):
offspring = tools.selTournamentDCD(pop, len(pop))
offspring = [toolbox.clone(ind) for ind in offspring]

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

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):
Expand Down Expand Up @@ -152,7 +152,7 @@ def valid(individual):
ind.fitness.values = toolbox.evaluate(ind)

strategy = cma.StrategyMultiObjective(population, sigma=1.0, mu=MU, lambda_=LAMBDA)

toolbox.register("generate", strategy.generate, creator.__dict__[INDCLSNAME])
toolbox.register("update", strategy.update)

Expand All @@ -164,9 +164,9 @@ def valid(individual):
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)

hv = hypervolume(strategy.parents, [11.0, 11.0])
assert hv > HV_THRESHOLD, "Hypervolume is lower than expected %f < %f" % (hv, HV_THRESHOLD)
64 changes: 64 additions & 0 deletions deap/tests/test_benchmarks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""Test functions from deap/benchmarks."""
import sys
import unittest
from nose import with_setup
from past.builtins import xrange

from deap import base
from deap import benchmarks
from deap import creator
from deap.benchmarks import binary


class BenchmarkTest(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 for x in xrange(10)])
full_individual = creator.Individual([1 for x in xrange(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)
6 changes: 3 additions & 3 deletions deap/tests/test_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def test_array():
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])
Expand All @@ -62,7 +62,7 @@ def test_numpy_nocopy():
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])
Expand All @@ -75,7 +75,7 @@ def test_numpy_copy():
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])
Expand Down
Loading

0 comments on commit a1412d7

Please sign in to comment.