Skip to content
New issue

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

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

Already on GitHub? # to your account

WIP: Change the signature of pick_random #155

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Change the signature of pick_random
Parent pickers are no longer passed any kwargs. The pick_random must
now be initialised before use, the number of parents passed to it
upon initialization. In addition, pickers must always return a sequence
of picked parents - even if it is only one.

These changes make it much easier to implement more complex picking
algorithms, and in addition they remove the requirement for the
select_arguments() decorator, which hurts my eyes.
  • Loading branch information
rogiervandergeer committed May 15, 2020
commit 1e40981b27f6c8a3384d19cb0a8a338d5063a70c
19 changes: 12 additions & 7 deletions evol/helpers/pickers.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from typing import Sequence, Tuple

from random import choice
from typing import Callable, Sequence, Tuple

from evol import Individual


def pick_random(parents: Sequence[Individual], n_parents: int = 2) -> Tuple:
"""Randomly selects parents with replacement
def pick_random(n_parents: int = 2) -> Callable[[Sequence[Individual]], Tuple[Individual, ...]]:
"""Returns a parent-picker that randomly samples parents with replacement.

Typical usage:
Evolution().breed(parent_picker=pick_random(n_parents=2), combiner=some_combiner)

Accepted arguments:
n_parents: Number of parents to select. Defaults to 2.
:param n_parents: The number of parents the picker should return.
:return: Callable
"""
return tuple(choice(parents) for _ in range(n_parents))
def picker(parents: Sequence[Individual]) -> Tuple[Individual, ...]:
return tuple(choice(parents) for _ in range(n_parents))

return picker
6 changes: 3 additions & 3 deletions evol/population.py
Original file line number Diff line number Diff line change
@@ -18,8 +18,8 @@
from evol.conditions import Condition
from evol.exceptions import StopEvolution
from evol.helpers.groups import group_random
from evol.utils import offspring_generator, select_arguments
from evol.serialization import SimpleSerializer
from evol.utils import offspring_generator, select_arguments

if TYPE_CHECKING:
from .evolution import Evolution
@@ -160,7 +160,7 @@ def evaluate(self, lazy: bool = False) -> 'BasePopulation':
pass

def breed(self,
parent_picker: Callable[..., Sequence[Individual]],
parent_picker: Callable[[Sequence[Individual]], Sequence[Individual]],
combiner: Callable,
population_size: Optional[int] = None,
**kwargs) -> 'BasePopulation':
@@ -182,7 +182,7 @@ def breed(self,
if population_size:
self.intended_size = population_size
offspring = offspring_generator(parents=self.individuals,
parent_picker=select_arguments(parent_picker),
parent_picker=parent_picker,
combiner=select_arguments(combiner),
**kwargs)
self.individuals += list(islice(offspring, self.intended_size - len(self.individuals)))
11 changes: 4 additions & 7 deletions evol/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from inspect import signature
from typing import List, Callable, Union, Sequence, Any, Generator
from typing import List, Callable, Sequence, Any, Generator

from evol import Individual


def offspring_generator(parents: List[Individual],
parent_picker: Callable[..., Union[Individual, Sequence]],
parent_picker: Callable[[Sequence[Individual]], Sequence[Individual]],
combiner: Callable[..., Any],
**kwargs) -> Generator[Individual, None, None]:
"""Generator for offspring.
@@ -25,11 +25,8 @@ def offspring_generator(parents: List[Individual],
"""
while True:
# Obtain parent chromosomes
selected_parents = parent_picker(parents, **kwargs)
if isinstance(selected_parents, Individual):
chromosomes = (selected_parents.chromosome,)
else:
chromosomes = tuple(individual.chromosome for individual in selected_parents)
selected_parents = parent_picker(parents)
chromosomes = tuple(individual.chromosome for individual in selected_parents)
# Create children
combined = combiner(*chromosomes, **kwargs)
if isinstance(combined, Generator):
4 changes: 2 additions & 2 deletions examples/number_of_parents.py
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ def init_func():
def eval_func(x, opt_value=opt_value):
return -((x - opt_value) ** 2) + math.cos(x - opt_value)

def random_parent_picker(pop, n_parents):
def random_parent_picker(pop):
return [random.choice(pop) for i in range(n_parents)]

def mean_parents(*parents):
@@ -36,7 +36,7 @@ def add_noise(chromosome, sigma):

evo = (Evolution()
.survive(fraction=survival)
.breed(parent_picker=random_parent_picker, combiner=mean_parents, n_parents=n_parents)
.breed(parent_picker=random_parent_picker, combiner=mean_parents)
.mutate(mutate_function=add_noise, sigma=noise)
.evaluate())

2 changes: 1 addition & 1 deletion examples/population_demo.py
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ def func_to_optimise(x):


def pick_random_parents(pop):
return random.choice(pop)
return random.choice(pop),


random.seed(42)
2 changes: 1 addition & 1 deletion examples/rock_paper_scissors.py
Original file line number Diff line number Diff line change
@@ -120,7 +120,7 @@ def run_rock_paper_scissors(population_size: int = 100,
evo = Evolution().repeat(
evolution=(Evolution()
.survive(fraction=survive_fraction)
.breed(parent_picker=pick_random, combiner=lambda x, y: x.combine(y), n_parents=2)
.breed(parent_picker=pick_random(n_parents=2), combiner=lambda x, y: x.combine(y))
.mutate(lambda x: x.mutate())
.evaluate()
.callback(history.log)),
2 changes: 1 addition & 1 deletion examples/travelling_salesman.py
Original file line number Diff line number Diff line change
@@ -47,7 +47,7 @@ def print_function(population: Population):

island_evo = (Evolution()
.survive(fraction=0.5)
.breed(parent_picker=pick_random, combiner=cycle_crossover)
.breed(parent_picker=pick_random(n_parents=2), combiner=cycle_crossover)
.mutate(swap_elements))

evo = (Evolution()
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -54,7 +54,7 @@ def simple_evolution():
return (
Evolution()
.survive(fraction=0.5)
.breed(parent_picker=pick_random, n_parents=2, combiner=lambda x, y: x + y)
.breed(parent_picker=pick_random(n_parents=2), combiner=lambda x, y: x + y)
.mutate(lambda x: x + 1, probability=0.1)
)

2 changes: 1 addition & 1 deletion tests/test_evolution.py
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ def callback(pop):
sub_evo = (
Evolution()
.survive(fraction=0.5)
.breed(parent_picker=pick_random,
.breed(parent_picker=pick_random(n_parents=2),
combiner=lambda x, y: x + y)
.callback(callback_function=callback)
)
20 changes: 8 additions & 12 deletions tests/test_logging.py
Original file line number Diff line number Diff line change
@@ -56,9 +56,8 @@ def test_baselogger_works_via_evolution_callback(self, tmpdir, capsys):
pop = Population(chromosomes=range(10), eval_function=lambda x: x)
evo = (Evolution()
.survive(fraction=0.5)
.breed(parent_picker=pick_random,
combiner=lambda mom, dad: (mom + dad) / 2 + (random.random() - 0.5),
n_parents=2)
.breed(parent_picker=pick_random(n_parents=2),
combiner=lambda mom, dad: (mom + dad) / 2 + (random.random() - 0.5))
.callback(logger.log, foo='bar'))
pop.evolve(evolution=evo, n=2)
# check characteristics of the file
@@ -132,9 +131,8 @@ def test_summarylogger_works_via_evolution(self, tmpdir, capsys):
pop = Population(chromosomes=list(range(10)), eval_function=lambda x: x)
evo = (Evolution()
.survive(fraction=0.5)
.breed(parent_picker=pick_random,
combiner=lambda mom, dad: (mom + dad) / 2 + (random.random() - 0.5),
n_parents=2)
.breed(parent_picker=pick_random(n_parents=2),
combiner=lambda mom, dad: (mom + dad) / 2 + (random.random() - 0.5))
.evaluate()
.callback(logger.log, foo='bar'))
pop.evolve(evolution=evo, n=5)
@@ -157,9 +155,8 @@ def test_two_populations_can_use_same_logger(self, tmpdir, capsys):
pop2 = Population(chromosomes=list(range(10)), eval_function=lambda x: x)
evo = (Evolution()
.survive(fraction=0.5)
.breed(parent_picker=pick_random,
combiner=lambda mom, dad: (mom + dad) + 1,
n_parents=2)
.breed(parent_picker=pick_random(n_parents=2),
combiner=lambda mom, dad: (mom + dad) + 1)
.evaluate()
.callback(logger.log, foo="dino"))
pop1.evolve(evolution=evo, n=5)
@@ -183,9 +180,8 @@ def test_every_mechanic_in_evolution_log(self, tmpdir, capsys):
pop = Population(chromosomes=list(range(10)), eval_function=lambda x: x)
evo = (Evolution()
.survive(fraction=0.5)
.breed(parent_picker=pick_random,
combiner=lambda mom, dad: (mom + dad) + 1,
n_parents=2)
.breed(parent_picker=pick_random(n_parents=2),
combiner=lambda mom, dad: (mom + dad) + 1)
.evaluate()
.callback(logger.log, every=2))
pop.evolve(evolution=evo, n=100)
13 changes: 6 additions & 7 deletions tests/test_population.py
Original file line number Diff line number Diff line change
@@ -172,28 +172,27 @@ def test_breed_amount_works(self, simple_chromosomes, simple_evaluation_function

def test_breed_works_with_kwargs(self, simple_chromosomes, simple_evaluation_function):
pop1 = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function)
pop1.survive(n=50).breed(parent_picker=pick_random,
combiner=lambda mom, dad: (mom + dad) / 2,
n_parents=2)
pop1.survive(n=50).breed(parent_picker=pick_random(),
combiner=lambda mom, dad: (mom + dad) / 2)
assert len(pop1) == len(simple_chromosomes)
pop2 = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function)
pop2.survive(n=50).breed(parent_picker=pick_random,
pop2.survive(n=50).breed(parent_picker=pick_random(n_parents=3),
combiner=lambda *parents: sum(parents)/len(parents),
population_size=400, n_parents=3)
population_size=400)
assert len(pop2) == 400
assert pop2.intended_size == 400

def test_breed_raises_with_multiple_values_for_kwarg(self, simple_population):

(simple_population
.survive(fraction=0.5)
.breed(parent_picker=pick_random,
.breed(parent_picker=pick_random(n_parents=2),
combiner=lambda x, y: x + y))

with raises(TypeError):
(simple_population
.survive(fraction=0.5)
.breed(parent_picker=pick_random,
.breed(parent_picker=pick_random(n_parents=2),
combiner=lambda x, y: x + y, y=2))


12 changes: 6 additions & 6 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -12,18 +12,18 @@ def combiner(x, y):
return 1

result = offspring_generator(parents=simple_population.individuals,
parent_picker=pick_random, combiner=combiner)
parent_picker=pick_random(), combiner=combiner)
assert isinstance(next(result), Individual)
assert next(result).chromosome == 1

@mark.parametrize('n_parents', [1, 2, 3, 4])
def test_args(self, n_parents: int, simple_population: Population):
def combiner(*parents, n_parents):
def combiner(*parents):
assert len(parents) == n_parents
return 1

result = offspring_generator(parents=simple_population.individuals, n_parents=n_parents,
parent_picker=pick_random, combiner=combiner)
result = offspring_generator(parents=simple_population.individuals,
parent_picker=pick_random(n_parents=n_parents), combiner=combiner)
assert isinstance(next(result), Individual)
assert next(result).chromosome == 1

@@ -32,7 +32,7 @@ def combiner(x):
return 1

def picker(parents):
return parents[0]
return parents[0],

result = offspring_generator(parents=simple_population.individuals, parent_picker=picker, combiner=combiner)
assert isinstance(next(result), Individual)
@@ -44,7 +44,7 @@ def combiner(x, y):
yield 2

result = offspring_generator(parents=simple_population.individuals,
parent_picker=pick_random, combiner=combiner)
parent_picker=pick_random(), combiner=combiner)
for _ in range(10):
assert next(result).chromosome == 1
assert next(result).chromosome == 2