Skip to content

Commit

Permalink
Merge pull request #258 from Su2001/Enumerative
Browse files Browse the repository at this point in the history
implements dependent type in enumerative
  • Loading branch information
alcides authored Nov 12, 2024
2 parents 5112e91 + 08cd951 commit 3cf3ca2
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 33 deletions.
17 changes: 16 additions & 1 deletion examples/benchmarks/benchmark.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from abc import ABC

from geml.common import PopulationRecorder
from geneticengine.algorithms.enumerative import EnumerativeSearch
from geneticengine.algorithms.gp.gp import GeneticProgramming
from geneticengine.evaluation.budget import TimeBudget
from geneticengine.evaluation.budget import EvaluationBudget, TimeBudget
from geneticengine.evaluation.tracker import ProgressTracker
from geneticengine.grammar.grammar import Grammar
from geneticengine.problems import Problem
Expand Down Expand Up @@ -35,3 +36,17 @@ def example_run(b: Benchmark):
print(
f"Fitness of {best.get_fitness(problem)} by genotype: {best.genotype} with phenotype: {best.get_phenotype()}",
)

def example_run_Enumerative(b: Benchmark):
problem = b.get_problem()
grammar = b.get_grammar()
alg = EnumerativeSearch(
problem=problem,
budget=EvaluationBudget(1000000),
grammar= grammar,
tracker=ProgressTracker(problem, recorders=[PopulationRecorder()]),
)
best = alg.search()[0]
print(
f"Fitness of {best.get_fitness(problem)} by phenotype: {best.get_phenotype()}",
)
36 changes: 22 additions & 14 deletions geneticengine/algorithms/enumerative.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,34 @@
Individual,
)


def frange(start, stop, step):
return takewhile(lambda x: x < stop, count(start, step))

def foundDict(d, target, result):
for key,value in d.items():
if target == value[0]:
d[key][1] = result
return


def combine_list_types(ts: list[type], acc: list[Any], gen):
def combine_list_types(ts: list[type], acc: list[Any], gen, dependent_values: Optional[dict[str,Any]]={}):
match ts:
case []:
yield acc
case _:
head = ts[0]
tail = ts[1:]
for x in gen(head):
yield from combine_list_types(tail, acc + [x], gen)
for x in gen(head,dependent_values):
foundDict(dependent_values, head, x)
yield from combine_list_types(tail, acc + [x], gen,dependent_values)


def iterate_grammar(grammar: Grammar, starting_symbol: type, generator_for_recursive: Optional[Any] = None):
def iterate_grammar(grammar: Grammar, starting_symbol: type, generator_for_recursive: Optional[Any] = None, dependent_values: Optional[dict[str,Any]] = {}):

if generator_for_recursive is None:

def generator_for_recursive(symbol: type):
return iterate_grammar(grammar, symbol, generator_for_recursive)
def generator_for_recursive(symbol: type, dependent_values: Optional[dict[str,Any]] = {}):
return iterate_grammar(grammar, symbol, generator_for_recursive, dependent_values)

if starting_symbol is int:
yield from range(-100000000, 100000000)
Expand All @@ -57,15 +63,15 @@ def generator_for_recursive(symbol: type):
yield False
elif is_generic_tuple(starting_symbol):
types = get_generic_parameters(starting_symbol)
for li in combine_list_types(types, [], generator_for_recursive):
for li in combine_list_types(types, [], generator_for_recursive,{}):
yield tuple(li)
elif is_generic_list(starting_symbol):
inner_type = get_generic_parameter(starting_symbol)

length = 0
while True:
generator_list = [inner_type for _ in range(length)]
for concrete_list in combine_list_types(generator_list, [], generator_for_recursive):
for concrete_list in combine_list_types(generator_list, [], generator_for_recursive,{}):
yield concrete_list
length += 1

Expand All @@ -74,7 +80,7 @@ def generator_for_recursive(symbol: type):
base_type = get_generic_parameter(starting_symbol)

if hasattr(metahandler, "iterate"):
yield from metahandler.iterate(base_type, lambda xs: combine_list_types(xs, [], generator_for_recursive))
yield from metahandler.iterate(base_type, lambda xs: combine_list_types(xs, [], generator_for_recursive,dependent_values), generator_for_recursive, dependent_values)
else:
base_type = get_generic_parameter(starting_symbol)
for ins in generator_for_recursive(base_type):
Expand Down Expand Up @@ -110,7 +116,7 @@ def generator_for_recursive(symbol: type):

# This reader will replace the generator with reading from the cache of previous levels
# If the type is different, it generates it as it was previously done.
def rgenerator(t: type) -> Generator[Any, Any, Any]:
def rgenerator(t: type, dependent_values: dict[str,Any]) -> Generator[Any, Any, Any]:
if t is starting_symbol:
yield from cache
else:
Expand All @@ -127,9 +133,11 @@ def rgenerator(t: type) -> Generator[Any, Any, Any]:
# Normal production
args = []
# TODO: Add dependent types to enumerative
# dependent_values = {}
args = [argt for _, argt in get_arguments(starting_symbol)]
for li in combine_list_types(args, [], generator_for_recursive):
dependent_values = {}
for argn, argt in get_arguments(starting_symbol):
dependent_values[argn] = [argt,0]
args.append(argt)
for li in combine_list_types(args, [], generator_for_recursive, dependent_values):
yield apply_constructor(starting_symbol, li)


Expand Down
13 changes: 8 additions & 5 deletions geneticengine/grammar/metahandlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@ def generate(
criterion.
:param Source r: Random source for generation
:param Grammar g: Grammar to follow in the generation
:param Callable[[int, Type], Any] rec: The method to generate a new instance of type and maximum depth d
:param Grammar g: Grammar to follow in the generation :param
Callable[[int, Type], Any] rec: The method to generate a new
instance of type and maximum depth d
:param int depth: the current depth budget
:param Type base_type: The inner type being annotated
:param str argname: The name of the field of the parent object which is being generated
:param Dict[str, Type] context: The names and types of all fields in the parent object
:param Dict[str, Type] dependent_values: The names and values of all previous fields in the parent object
:param str argname: The name of the field of the parent object
which is being generated :param Dict[str, Type] context: The
names and types of all fields in the parent object :param
Dict[str, Type] dependent_values: The names and values of
all previous fields in the parent object
"""
...

Expand Down
16 changes: 14 additions & 2 deletions geneticengine/grammar/metahandlers/dependent.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Annotated, Any, Callable
from typing import Annotated, Any, Callable, Generator


from geneticengine.grammar.grammar import Grammar
Expand All @@ -13,7 +13,7 @@ class Dependent(MetaHandlerGenerator):
callable: Callable[[Any], type]

def validate(self, v) -> bool:
raise NotImplementedError() # TODO: Dependent Types
return True

def generate(
self,
Expand All @@ -28,6 +28,18 @@ def generate(
v = rec(Annotated[base_type, t])
return v

def iterate(
self,
base_type: type,
combine_lists: Callable[[list[type]], Generator[Any, Any, Any]],
rec: Any,
dependent_values: dict[str, Any],
):
values = [dependent_values[name][1] for name in self.get_dependencies()]
t: Any = self.callable(*values)
v = rec(Annotated[base_type, t],dependent_values)
return v

def __hash__(self):
return hash(self.__class__) + hash(self.name) + hash(id(self.callable))

Expand Down
2 changes: 2 additions & 0 deletions geneticengine/grammar/metahandlers/floats.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def iterate(
self,
base_type: type,
combine_lists: Callable[[list[type]], Generator[Any, Any, Any]],
rec: Any,
dependent_values: dict[str, Any],
):
v = self.min
while v <= self.max:
Expand Down
5 changes: 4 additions & 1 deletion geneticengine/grammar/metahandlers/ints.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class IntRange(MetaHandlerGenerator):
"""IntRange(a,b) restricts ints to be between a and b.
The range can be dynamically altered before the grammar extraction
Int.__init__.__annotations__["value"] = Annotated[int, IntRange(c,d)]
Int.__init__.__annotations__["value"] = Annotated[int,
IntRange(c,d)]
"""

def __init__(self, min, max):
Expand All @@ -40,6 +41,8 @@ def iterate(
self,
base_type: type,
combine_lists: Callable[[list[type]], Generator[Any, Any, Any]],
rec: Any,
dependent_values: dict[str, Any],
):
for i in range(self.min, self.max + 1):
yield i
Expand Down
2 changes: 2 additions & 0 deletions geneticengine/grammar/metahandlers/lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ def iterate(
self,
base_type: type,
combine_lists: Callable[[list[type]], Generator[Any, Any, Any]],
rec: Any,
dependent_values: dict[str, Any],
):
assert is_generic_list(base_type)
inner_type = get_generic_parameter(base_type)
Expand Down
14 changes: 8 additions & 6 deletions geneticengine/grammar/metahandlers/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ def iterate(
self,
base_type: type,
combine_lists: Callable[[list[type]], Generator[Any, Any, Any]],
rec: Any,
dependent_values: dict[str, Any],
):
def generate_letter():
yield from self.options
Expand All @@ -110,13 +112,13 @@ class WeightedStringHandler(MetaHandlerGenerator):
output complies with a given alphabet and a matrix of probabilities for
each position.
Each row on the matrix should reflect the probability of
each character in that position. Thus, the number of cols
in the input matrix should be the same as the number of
characters in the alphabet.
Each row on the matrix should reflect the probability of each
character in that position. Thus, the number of cols in the input
matrix should be the same as the number of characters in the
alphabet.
This refinement will return a string with a
size == nrows in the matrix
This refinement will return a string with a size == nrows in the
matrix
"""

def __init__(self, matrix, alphabet):
Expand Down
9 changes: 6 additions & 3 deletions geneticengine/grammar/metahandlers/vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
class VarRange(MetaHandlerGenerator):
"""VarRange([a, b, c]) represents the alternative between a, b, and c.
The list of options can be dynamically altered before the grammar extraction
with something like Var.__init__.__annotations__["name"] = Annotated[str, VarRange([d, e, f])].
The option list must not be empty.
The list of options can be dynamically altered before the grammar
extraction with something like Var.__init__.__annotations__["name"]
= Annotated[str, VarRange([d, e, f])]. The option list must not be
empty.
"""

def __init__(self, options: list[T]):
Expand Down Expand Up @@ -47,5 +48,7 @@ def iterate(
self,
base_type: type,
combine_lists: Callable[[list[type]], Generator[Any, Any, Any]],
rec: Any,
dependent_values: dict[str, Any],
):
yield from self.options
2 changes: 1 addition & 1 deletion tests/representations/representations_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def fitness_function(x: Root) -> float:
gp.search()

def test_enumerative_combine(self) -> None:
def gen_bool(t):
def gen_bool(t, dependent_values):
assert t is bool
yield True
yield False
Expand Down

0 comments on commit 3cf3ca2

Please sign in to comment.