Skip to content

Commit

Permalink
Introduced different Individual Types
Browse files Browse the repository at this point in the history
  • Loading branch information
alcides committed Nov 1, 2024
1 parent 4f0aa5a commit ade0b66
Show file tree
Hide file tree
Showing 27 changed files with 275 additions and 131 deletions.
115 changes: 115 additions & 0 deletions geneticengine/algorithms/enumerative.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from __future__ import annotations
from itertools import count, takewhile
from typing import Any

from geneticengine.algorithms.api import SynthesisAlgorithm

from geneticengine.evaluation.budget import SearchBudget
from geneticengine.evaluation.tracker import ProgressTracker
from geneticengine.exceptions import GeneticEngineError
from geneticengine.grammar.grammar import Grammar
from geneticengine.grammar.metahandlers.base import MetaHandlerGenerator
from geneticengine.grammar.utils import (
get_arguments,
get_generic_parameter,
get_generic_parameters,
is_generic_list,
is_generic_tuple,
is_metahandler,
is_union,
)
from geneticengine.problems import Problem
from geneticengine.representations.tree.initializations import apply_constructor
from geneticengine.solutions.individual import (
ConcreteIndividual,
Individual,
)


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


def iterate_grammar(grammar: Grammar, starting_symbol: type):

def combine_list(ts: list[type], acc: list[Any]):
match ts:
case []:
yield acc
case _:
head = ts[0]
tail = ts[1:]
for x in iterate_grammar(grammar, head):
yield from combine_list(tail, acc + [x])

if starting_symbol is int:
yield from range(-100000000, 100000000)
elif starting_symbol is float:
yield from frange(-100000.0, 100000.0, 0.00001)
elif starting_symbol is bool:
yield True
yield False
elif is_generic_tuple(starting_symbol):
types = get_generic_parameters(starting_symbol)
for li in combine_list(types, []):
yield tuple(li)
elif is_generic_list(starting_symbol):
inner_type = get_generic_parameter(starting_symbol)
for length in range(0, 1024):
generator_list = [iterate_grammar(grammar, inner_type) for _ in range(length)]
for concrete_list in combine_list(generator_list, []):
print(concrete_list)
yield concrete_list

elif is_metahandler(starting_symbol):
metahandler: MetaHandlerGenerator = starting_symbol.__metadata__[0] # type: ignore
base_type = get_generic_parameter(starting_symbol)
for ins in iterate_grammar(grammar, base_type):
if metahandler.validate(ins):
yield ins
elif is_union(starting_symbol):
for alt in get_generic_parameters(starting_symbol):
yield from iterate_grammar(grammar, alt)
else:
if starting_symbol not in grammar.all_nodes:
raise GeneticEngineError(
f"Symbol {starting_symbol} not in grammar rules.",
)
elif starting_symbol in grammar.alternatives:
compatible_productions = grammar.alternatives[starting_symbol]
for prod in compatible_productions:
yield from iterate_grammar(grammar, prod)
else:
# Normal production
args = []
# dependent_values = {}
args = [argt for _, argt in get_arguments(starting_symbol)]
for li in combine_list(args, []):
assert isinstance(li, list)
yield apply_constructor(starting_symbol, li)


def iterate_individuals(grammar: Grammar, starting_symbol: type):
for p in iterate_grammar(grammar, starting_symbol):
yield ConcreteIndividual(instance=p)


class EnumerativeSearch(SynthesisAlgorithm):
"""Iterates through all possible representations and selects the best."""

def __init__(
self,
problem: Problem,
budget: SearchBudget,
grammar: Grammar,
tracker: ProgressTracker | None = None,
):
super().__init__(problem, budget, tracker)
self.grammar = grammar

def perform_search(self) -> list[Individual]:
for individual in iterate_individuals(self.grammar, self.grammar.starting_symbol):
self.tracker.evaluate_single(individual)
if self.budget.is_done(self.tracker):
break
return self.tracker.get_best_individuals()
20 changes: 10 additions & 10 deletions geneticengine/algorithms/gp/adaptive.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from geneticengine.problems import Fitness, Problem
from geneticengine.random.sources import RandomSource
from geneticengine.representations.api import Representation
from geneticengine.solutions.individual import Individual
from geneticengine.solutions.individual import PhenotypicIndividual


def generate_random_population_size(random: RandomSource) -> int:
Expand Down Expand Up @@ -47,9 +47,9 @@ def initialize(
random: RandomSource,
target_size: int,
**kwargs,
) -> Iterator[Individual]:
) -> Iterator[PhenotypicIndividual]:
while not self.budget.is_done(self.tracker):
yield Individual(
yield PhenotypicIndividual(
representation.create_genotype(
random,
**kwargs,
Expand All @@ -73,7 +73,7 @@ def post_iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> None:
Expand Down Expand Up @@ -108,11 +108,11 @@ def iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> Iterator[Individual]:
npopulation: list[Individual] = [i for i in population]
) -> Iterator[PhenotypicIndividual]:
npopulation: list[PhenotypicIndividual] = [i for i in population]
ranges = self.compute_ranges(npopulation, target_size)
assert len(ranges) == len(self.steps)

Expand Down Expand Up @@ -141,7 +141,7 @@ def iterate(
yield from new_individuals


def best_of_population(population: list[Individual], problem: Problem) -> Individual:
def best_of_population(population: list[PhenotypicIndividual], problem: Problem) -> PhenotypicIndividual:
return reduce(
lambda x, s: x if problem.is_better(x.get_fitness(problem), s.get_fitness(problem)) else s,
population,
Expand All @@ -162,7 +162,7 @@ def post_iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> None:
Expand Down Expand Up @@ -193,7 +193,7 @@ def post_iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> None:
Expand Down
20 changes: 10 additions & 10 deletions geneticengine/algorithms/gp/operators/combinators.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations
from typing import Iterator

from geneticengine.solutions.individual import Individual
from geneticengine.solutions.individual import Individual, PhenotypicIndividual
from geneticengine.algorithms.gp.structure import GeneticStep
from geneticengine.problems import Problem
from geneticengine.random.sources import RandomSource
Expand All @@ -18,10 +18,10 @@ def iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> Iterator[Individual]:
) -> Iterator[PhenotypicIndividual]:
for _, p in zip(range(target_size), population):
yield p

Expand All @@ -39,10 +39,10 @@ def iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> Iterator[Individual]:
) -> Iterator[PhenotypicIndividual]:
npopulation = population
for step in self.steps:
npopulation = step.apply(
Expand Down Expand Up @@ -101,10 +101,10 @@ def iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> Iterator[Individual]:
) -> Iterator[PhenotypicIndividual]:
npopulation: list[Individual] = [i for i in population]
ranges = self.compute_ranges(npopulation, target_size)
assert len(ranges) == len(self.steps)
Expand Down Expand Up @@ -149,11 +149,11 @@ def iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> Iterator[Individual]:
npopulation: list[Individual] = list(population)
) -> Iterator[PhenotypicIndividual]:
npopulation: list[PhenotypicIndividual] = list(population)

Check warning on line 156 in geneticengine/algorithms/gp/operators/combinators.py

View check run for this annotation

Codecov / codecov/patch

geneticengine/algorithms/gp/operators/combinators.py#L156

Added line #L156 was not covered by tests
total = sum(self.weights)
indices = [0] + self.cumsum(
[int(round(w * len(npopulation) / total, 0)) for w in self.weights],
Expand Down
14 changes: 7 additions & 7 deletions geneticengine/algorithms/gp/operators/crossover.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
from typing import Iterator

from geneticengine.solutions.individual import Individual
from geneticengine.solutions.individual import Individual, PhenotypicIndividual
from geneticengine.algorithms.gp.structure import GeneticStep
from geneticengine.problems import Problem
from geneticengine.random.sources import RandomSource
Expand All @@ -28,10 +28,10 @@ def iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> Iterator[Individual]:
) -> Iterator[PhenotypicIndividual]:
assert isinstance(representation, RepresentationWithCrossover)
npopulation = list(population)
for i in range(target_size // 2):
Expand All @@ -53,8 +53,8 @@ def iterate(
def crossover(
self,
random: RandomSource,
individual1: Individual,
individual2: Individual,
individual1: PhenotypicIndividual,
individual2: PhenotypicIndividual,
representation: Representation,
):
assert isinstance(representation, RepresentationWithCrossover)
Expand All @@ -65,6 +65,6 @@ def crossover(
individual2.genotype,
)
return (
Individual(g1, representation=representation),
Individual(g2, representation=representation),
PhenotypicIndividual(g1, representation=representation),
PhenotypicIndividual(g2, representation=representation),
)
6 changes: 3 additions & 3 deletions geneticengine/algorithms/gp/operators/elitism.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from __future__ import annotations
from typing import Iterator

from geneticengine.solutions.individual import Individual
from geneticengine.algorithms.gp.structure import GeneticStep
from geneticengine.problems.helpers import sort_population
from geneticengine.problems import Problem
from geneticengine.random.sources import RandomSource
from geneticengine.representations.api import Representation
from geneticengine.evaluation import Evaluator
from geneticengine.solutions.individual import PhenotypicIndividual


class ElitismStep(GeneticStep):
Expand All @@ -19,10 +19,10 @@ def iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> Iterator[Individual]:
) -> Iterator[PhenotypicIndividual]:
evaluator.evaluate(problem, population)
# TODO: We do not need to sort here.
new_population = sort_population(list(population), problem)
Expand Down
6 changes: 3 additions & 3 deletions geneticengine/algorithms/gp/operators/evaluation.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from typing import Iterator
from geneticengine.solutions.individual import Individual
from geneticengine.algorithms.gp.structure import GeneticStep
from geneticengine.problems import Problem
from geneticengine.random.sources import RandomSource
from geneticengine.representations.api import Representation
from geneticengine.evaluation import Evaluator
from geneticengine.solutions.individual import PhenotypicIndividual


class EvaluateStep(GeneticStep):
Expand All @@ -16,9 +16,9 @@ def iterate(
evaluator: Evaluator,
representation: Representation,
random: RandomSource,
population: Iterator[Individual],
population: Iterator[PhenotypicIndividual],
target_size: int,
generation: int,
) -> Iterator[Individual]:
) -> Iterator[PhenotypicIndividual]:
evaluator.evaluate(problem, population)
yield from population
8 changes: 4 additions & 4 deletions geneticengine/algorithms/gp/operators/initializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations
from typing import Iterator

from geneticengine.solutions.individual import Individual
from geneticengine.solutions.individual import PhenotypicIndividual
from geneticengine.algorithms.gp.structure import PopulationInitializer
from geneticengine.problems import Problem
from geneticengine.random.sources import RandomSource
Expand All @@ -22,7 +22,7 @@ def initialize(
random: RandomSource,
target_size: int,
**kwargs,
) -> Iterator[Individual]:
) -> Iterator[PhenotypicIndividual]:
mid = target_size // 2
yield from self.initializer1(problem, representation, random, mid)
yield from self.initializer2(
Expand All @@ -44,9 +44,9 @@ def initialize(
random: RandomSource,
target_size: int,
**kwargs,
) -> Iterator[Individual]:
) -> Iterator[PhenotypicIndividual]:
for i in range(target_size):
yield Individual(
yield PhenotypicIndividual(
representation.create_genotype(
random,
**kwargs,
Expand Down
Loading

0 comments on commit ade0b66

Please sign in to comment.