Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metacreator #1

Merged
merged 6 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 42 additions & 26 deletions deap/creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import array
import copy
import warnings
import copy_reg

class_replacers = {}
"""Some classes in Python's standard library as well as third party library
Expand Down Expand Up @@ -91,6 +92,46 @@ def __reduce__(self):
return (self.__class__, (list(self),), self.__dict__)
class_replacers[array.array] = _array

class MetaCreator(type):
def __new__(meta, name, base, dct):
return super(MetaCreator, meta).__new__(meta, name, (base,), dct)

def __init__(cls, name, base, dct):
# 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.
dict_inst = {}
dict_cls = {}
for obj_name, obj in dct.iteritems():
if isinstance(obj, type):
dict_inst[obj_name] = obj
else:
dict_cls[obj_name] = obj
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)

cls.__init__ = initType
cls.reduce_args = (name, base, dct)
super(MetaCreator, cls).__init__(name, (base,), dict_cls)

def __reduce__(cls):
return (meta_create, cls.reduce_args)

copy_reg.pickle(MetaCreator, MetaCreator.__reduce__)

def meta_create(name, base, dct):
class_ = MetaCreator(name, base, dct)
globals()[name] = class_
return class_

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
Expand Down Expand Up @@ -138,32 +179,7 @@ def __init__(self):
"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
meta_create(name, base, kargs)
84 changes: 50 additions & 34 deletions deap/gp.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@
"""
import copy
import math
import copy_reg
import random
import re
import sys
import types
import warnings

from collections import defaultdict, deque
from functools import partial, wraps
from inspect import isclass
from operator import eq, lt

import tools # Needed by HARM-GP
Expand Down Expand Up @@ -232,20 +233,42 @@ def __eq__(self, other):
else:
return NotImplemented

class Ephemeral(Terminal):
"""Class that encapsulates a terminal which value is set when the
class MetaEphemeral(type):
"""Meta-Class that creates a terminal which value is set when the
object is created. To mutate the value, a new object has to be
generated. This is an abstract base class. When subclassing, a
staticmethod 'func' must be defined.
generated.
"""
def __init__(self):
Terminal.__init__(self, self.func(), symbolic=False, ret=self.ret)
cache = {}
def __new__(meta, name, func, ret=__type__, id_=None):
if id_ in MetaEphemeral.cache:
return MetaEphemeral.cache[id_]

@staticmethod
def func():
"""Return a random value used to define the ephemeral state.
"""
raise NotImplementedError
if isinstance(func, types.LambdaType) and func.__name__ == '<lambda>':
warnings.warn("Ephemeral {name} function cannot be "
"pickled because its generating function "
"is a lambda function. Use functools.partial "
"instead.".format(name=name), RuntimeWarning)

def __init__(self):
self.value = func()

attr = {'__init__' : __init__,
'name' : name,
'func' : func,
'ret' : ret,
'conv_fct' : repr}

cls = super(MetaEphemeral, meta).__new__(meta, name, (Terminal,), attr)
MetaEphemeral.cache[id(cls)] = cls
return cls

def __init__(cls, name, func, ret=__type__, id_=None):
super(MetaEphemeral, cls).__init__(name, (Terminal,), {})

def __reduce__(cls):
return (MetaEphemeral, (cls.name, cls.func, cls.ret, id(cls)))

copy_reg.pickle(MetaEphemeral, MetaEphemeral.__reduce__)

class PrimitiveSetTyped(object):
"""Class that contains the primitives that can be used to solve a
Expand Down Expand Up @@ -379,24 +402,17 @@ def addEphemeralConstant(self, name, ephemeral, ret_type):
:param ephemeral: function with no arguments returning a random value.
:param ret_type: type of the object returned by *ephemeral*.
"""
module_gp = globals()
if not name in module_gp:
class_ = type(name, (Ephemeral,), {'func' : staticmethod(ephemeral),
'ret' : ret_type})
module_gp[name] = class_
if not name in self.mapping:
class_ = MetaEphemeral(name, ephemeral, ret_type)
else:
class_ = module_gp[name]
if issubclass(class_, Ephemeral):
if class_.func is not ephemeral:
raise Exception("Ephemerals with different functions should "
"be named differently, even between psets.")
elif class_.ret is not ret_type:
raise Exception("Ephemerals with the same name and function "
"should have the same type, even between psets.")
else:
raise Exception("Ephemerals should be named differently "
"than classes defined in the gp module.")

class_ = self.mapping[name]
if class_.func is not ephemeral:
raise Exception("Ephemerals with different functions should "
"be named differently, even between psets.")
if class_.ret is not ret_type:
raise Exception("Ephemerals with the same name and function "
"should have the same type, even between psets.")

self._add(class_)
self.terms_count += 1

Expand Down Expand Up @@ -595,7 +611,7 @@ def generate(pset, min_, max_, condition, type_=None):
raise IndexError, "The gp.generate function tried to add "\
"a terminal of type '%s', but there is "\
"none available." % (type_,), traceback
if isclass(term):
if type(term) is MetaEphemeral:
term = term()
expr.append(term)
else:
Expand Down Expand Up @@ -747,7 +763,7 @@ def mutNodeReplacement(individual, pset):

if node.arity == 0: # Terminal
term = random.choice(pset.terminals[node.ret])
if isclass(term):
if type(term) is MetaEphemeral:
term = term()
individual[index] = term
else: # Primitive
Expand All @@ -772,7 +788,7 @@ def mutEphemeral(individual, mode):

ephemerals_idx = [index
for index, node in enumerate(individual)
if isinstance(node, Ephemeral)]
if isinstance(type(node), MetaEphemeral)]

if len(ephemerals_idx) > 0:
if mode == "one":
Expand Down Expand Up @@ -867,8 +883,8 @@ def staticLimit(key, max_value):
depth), because it can ensure that no tree higher than this limit will ever
be accepted in the population, except if it was generated at initialization
time.
:param key: The function to use in order the get the wanted value. For

:param key: The function to use in order the get the wanted value. For
instance, on a GP tree, ``operator.attrgetter('height')`` may
be used to set a depth limit, and ``len`` to set a size limit.
:param max_value: The maximum value allowed for the given measurement.
Expand Down
3 changes: 2 additions & 1 deletion deap/tests/test_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pickle
import operator
import platform
import functools

import numpy

Expand Down Expand Up @@ -99,7 +100,7 @@ def test_pickle_tree_term(self):
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)
pset.addEphemeralConstant("E1", functools.partial(int, 2), int)

expr = gp.genFull(pset, min_=1, max_=1)
ind = creator.IndTree(expr)
Expand Down
36 changes: 19 additions & 17 deletions examples/ga/onemax_mp.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,28 @@
from deap import creator
from deap import tools

def evalOneMax(individual):
return sum(individual),

creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", array.array, typecode='b', fitness=creator.FitnessMax)

toolbox = base.Toolbox()
def main(seed):
random.seed(seed)

# Attribute generator
toolbox.register("attr_bool", random.randint, 0, 1)
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", array.array, typecode='b', fitness=creator.FitnessMax)

# Structure initializers
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, 100)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox = base.Toolbox()

def evalOneMax(individual):
return sum(individual),
# Attribute generator
toolbox.register("attr_bool", random.randint, 0, 1)

toolbox.register("evaluate", evalOneMax)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mutate", tools.mutFlipBit, indpb=0.05)
toolbox.register("select", tools.selTournament, tournsize=3)
# Structure initializers
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, 100)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

if __name__ == "__main__":
random.seed(64)
toolbox.register("evaluate", evalOneMax)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mutate", tools.mutFlipBit, indpb=0.05)
toolbox.register("select", tools.selTournament, tournsize=3)

# Process Pool of 4 workers
pool = multiprocessing.Pool(processes=4)
Expand All @@ -70,3 +69,6 @@ def evalOneMax(individual):
stats=stats, halloffame=hof)

pool.close()

if __name__ == "__main__":
main(64)
4 changes: 3 additions & 1 deletion examples/gp/adf_symbreg.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import numpy

from functools import partial

from deap import base
from deap import creator
from deap import gp
Expand Down Expand Up @@ -69,7 +71,7 @@ def protectedDiv(left, right):
pset.addPrimitive(operator.neg, 1)
pset.addPrimitive(math.cos, 1)
pset.addPrimitive(math.sin, 1)
pset.addEphemeralConstant("rand101", lambda: random.randint(-1, 1))
pset.addEphemeralConstant("rand101", partial(random.randint, -1, 1))
pset.addADF(adfset0)
pset.addADF(adfset1)
pset.addADF(adfset2)
Expand Down
4 changes: 3 additions & 1 deletion examples/gp/spambase.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import numpy

from functools import partial

from deap import algorithms
from deap import base
from deap import creator
Expand Down Expand Up @@ -64,7 +66,7 @@ def if_then_else(input, output1, output2):
pset.addPrimitive(if_then_else, [bool, float, float], float)

# terminals
pset.addEphemeralConstant("rand100", lambda: random.random() * 100, float)
pset.addEphemeralConstant("rand100", partial(random.uniform, 0, 100), float)
pset.addTerminal(False, bool)
pset.addTerminal(True, bool)

Expand Down
4 changes: 3 additions & 1 deletion examples/gp/symbreg.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import numpy

from functools import partial

from deap import algorithms
from deap import base
from deap import creator
Expand All @@ -40,7 +42,7 @@ def protectedDiv(left, right):
pset.addPrimitive(operator.neg, 1)
pset.addPrimitive(math.cos, 1)
pset.addPrimitive(math.sin, 1)
pset.addEphemeralConstant("rand101", lambda: random.randint(-1,1))
pset.addEphemeralConstant("rand101", partial(random.randint, -1, 1))
pset.renameArguments(ARG0='x')

creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
Expand Down
4 changes: 3 additions & 1 deletion examples/gp/symbreg_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import numpy

from functools import partial

from deap import algorithms
from deap import base
from deap import creator
Expand All @@ -44,7 +46,7 @@ def protectedDiv(left, right):
pset.addPrimitive(numpy.negative, 1, name="vneg")
pset.addPrimitive(numpy.cos, 1, name="vcos")
pset.addPrimitive(numpy.sin, 1, name="vsin")
pset.addEphemeralConstant("rand101", lambda: random.randint(-1,1))
pset.addEphemeralConstant("rand101", partial(random.randint, -1, 1))
pset.renameArguments(ARG0='x')

creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
Expand Down