def test_baselogger_works_via_evolution(self, tmpdir, capsys): log_file = tmpdir.join('log.txt') logger = BaseLogger(target=log_file, stdout=True) pop = Population(chromosomes=range(10), eval_function=lambda x: x, logger=logger) 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).log(foo='bar')) _ = pop.evolve(evolution=evo, n=2) # check characteristics of the file with open(log_file, "r") as f: read_file = [item.replace("\n", "") for item in f.readlines()] # size of the log should be appropriate assert len(read_file) == 2 * len(pop) # bar needs to be in every single line assert all(['bar' in row for row in read_file]) # check characteristics of stoud read_stdout = [ line for line in capsys.readouterr().out.split('\n') if line != '' ] assert len(read_stdout) == 2 * len(pop) assert all(['bar' in row for row in read_stdout])
def test_summary_logger_can_accept_kwargs(self, tmpdir, simple_chromosomes, simple_evaluation_function): log_file = tmpdir.join('log.txt') logger = SummaryLogger(target=log_file, stdout=False) pop = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) pop.evaluate() # lets make a first simple log logger.log(pop, foo="bar", buzz="meh") with open(log_file, "r") as f: read_lines = f.readlines() assert len(read_lines) == 1 first_line = read_lines[0] assert "bar" in first_line assert "meh" in first_line last_entry = first_line.split(",") assert len(last_entry) == 6 # lets log another one logger.log(pop, buzz="moh") with open(log_file, "r") as f: read_lines = f.readlines() assert len(read_lines) == 2 first_line = read_lines[-1] assert "moh" in first_line last_entry = first_line.split(",") assert len(last_entry) == 5
def test_evaluate_func(self, simple_chromosomes): def evaluation_function(x): return x * x pop = Population(simple_chromosomes, eval_function=evaluation_function) pop.evaluate() for individual in pop: assert evaluation_function( individual.chromosome) == individual.fitness
def test_current_worst(self, simple_chromosomes): for maximize, worst in ((False, max(simple_chromosomes)), (True, min(simple_chromosomes))): pop = Population(chromosomes=simple_chromosomes, eval_function=float, maximize=maximize) assert pop.current_worst is None pop.evaluate() assert pop.current_worst.chromosome == worst
def test_mutate_func(self): def mutate_func(x): return -x population = Population([1] * 100, eval_function=lambda x: x) population.mutate(mutate_func) for chromosome in population.chromosomes: assert chromosome == -1 assert len(population) == 100
def test_is_stratified(self, shuffled_chromosomes, n_groups): population = Population(shuffled_chromosomes, eval_function=lambda x: x).evaluate() islands = population.group(group_stratified, n_groups=n_groups) # All islands should have the same total fitness assert len( set( sum(map(lambda i: i.fitness, island.individuals)) for island in islands)) == 1
def test_summarylogger_can_write_to_stdout(self, capsys, simple_chromosomes, simple_evaluation_function): logger = SummaryLogger(target=None, stdout=True) pop = Population(chromosomes=range(10), eval_function=lambda x: x) pop.evaluate() logger.log(pop) logger.log(pop) read_stdout = [line for line in capsys.readouterr().out.split('\n') if line != ''] assert len(read_stdout) == 2
def test_unserializable_chromosome(self, tmpdir): directory = tmpdir.mkdir("ckpt") class UnSerializableChromosome: def __init__(self, x): self.x = x pop = Population([UnSerializableChromosome(i) for i in range(10)], lambda x: x.x) with raises(self.exception): pop.checkpoint(target=directory, method=self.method)
def test_baselogger_can_write_to_stdout(self, capsys, simple_chromosomes, simple_evaluation_function): pop = Population(chromosomes=simple_chromosomes, eval_function=lambda x: x, logger=BaseLogger(target=None, stdout=True)) pop.log() read_stdout = [ line for line in capsys.readouterr().out.split('\n') if line != '' ] assert len(read_stdout) == len(pop)
def test_is_nearly_stratified(self, shuffled_chromosomes, n_groups): population = Population(shuffled_chromosomes, eval_function=lambda x: x).evaluate() islands = population.group(group_stratified, n_groups=n_groups) # All islands should have roughly the same total fitness sum_fitnesses = [ sum(map(lambda i: i.fitness, island.individuals)) for island in islands ] assert max(sum_fitnesses) - min(sum_fitnesses) < n_groups * len( islands[0])
def test_simple_counter_works_every(self, simple_chromosomes, simple_evaluation_function): counter = PopCounter() pop = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) evo = (Evolution().mutate(lambda x: x).callback( lambda p: counter.add(p), every=2)) pop.evolve(evolution=evo, n=10) assert counter.count == len(simple_chromosomes) * 5 assert counter.sum == sum(simple_chromosomes) * 5
def test_two_populations_can_use_same_logger(self, tmpdir, capsys): log_file = tmpdir.join('log.txt') logger = SummaryLogger(target=log_file, stdout=True) pop1 = Population(chromosomes=list(range(10)), eval_function=lambda x: x) 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) .evaluate() .callback(logger.log, foo="dino")) pop1.evolve(evolution=evo, n=5) pop2.evolve(evolution=evo, n=5) # two evolutions have now been applied, lets check the output! with open(log_file, "r") as f: read_file = [item.replace("\n", "") for item in f.readlines()] # print(read_file) # size of the log should be appropriate assert len(read_file) == 10 # dino needs to be in every single line assert all(['dino' in row for row in read_file]) # check characteristics of stoud read_stdout = [line for line in capsys.readouterr().out.split('\n') if line != ''] assert len(read_stdout) == 10 assert all(['dino' in row for row in read_stdout])
def test_evaluate_func(self, simple_chromosomes): def evaluation_function(x): sleep(self.latency) return x * x pop = Population(simple_chromosomes, eval_function=evaluation_function) t0 = time() pop.evaluate() t1 = time() single_proc_time = t1 - t0 for individual in pop: assert evaluation_function( individual.chromosome) == individual.fitness # with concurrency pop = Population(simple_chromosomes, eval_function=evaluation_function, concurrent_workers=self.cpus) t0 = time() pop.evaluate() t1 = time() multi_proc_time = t1 - t0 for individual in pop: assert evaluation_function( individual.chromosome) == individual.fitness if self.cpus > 1: assert multi_proc_time < single_proc_time
def test_weights(self, simple_chromosomes, simple_evaluation_function): for maximize in (False, True): pop = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function, maximize=maximize) with raises(RuntimeError): assert min(pop._individual_weights) >= 0 pop.evaluate() assert max(pop._individual_weights) == 1 assert min(pop._individual_weights) == 0 if maximize: assert pop._individual_weights[0] == 0 else: assert pop._individual_weights[0] == 1
def test_survive_n_and_p_works(self, simple_evaluation_function): chromosomes = list(range(200)) pop1 = Population(chromosomes=chromosomes, eval_function=simple_evaluation_function) pop2 = Population(chromosomes=chromosomes, eval_function=simple_evaluation_function) pop3 = Population(chromosomes=chromosomes, eval_function=simple_evaluation_function) assert len(pop1.survive(fraction=0.5, n=200)) == 100 assert len(pop2.survive(fraction=0.9, n=10)) == 10 assert len(pop3.survive(fraction=0.5, n=190, luck=True)) == 100
def test_baselogger_can_accept_kwargs(self, tmpdir, simple_chromosomes, simple_evaluation_function): log_file = tmpdir.join('log.txt') logger = BaseLogger(target=log_file, stdout=False) pop = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) pop.evaluate() # we should see that a file was created with an appropriate number of rows logger.log(pop, foo="bar") with open(log_file, "r") as f: assert len(f.readlines()) == len(simple_chromosomes) assert all(["bar" in line for line in f.readlines()]) # we should see that a file was created with an appropriate number of rows logger.log(pop, foo="meh") with open(log_file, "r") as f: assert len(f.readlines()) == (2 * len(simple_chromosomes)) assert all(['meh' in line for line in f.readlines()[-10:]])
def test_repeat_step_grouped(self, n_groups, grouping_function): calls = [] def callback(pop): calls.append(len(pop)) sub_evo = (Evolution().survive(fraction=0.5).breed( parent_picker=pick_random, combiner=lambda x, y: x + y).callback(callback_function=callback)) pop = Population([0 for _ in range(100)], lambda x: x) evo = (Evolution().evaluate(lazy=True).repeat( sub_evo, grouping_function=grouping_function, n_groups=n_groups)) assert len(pop.evolve(evo, n=2)) == 100 assert len(calls) == 2 * n_groups
def test_baselogger_write_file_no_stdout(self, tmpdir, capsys, simple_chromosomes, simple_evaluation_function): log_file = tmpdir.join('log.txt') logger = BaseLogger(target=log_file, stdout=False) pop = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) # we should see that a file was created with an appropriate number of rows pop.evaluate() logger.log(pop) with open(log_file, "r") as f: assert len(f.readlines()) == len(simple_chromosomes) # we should see that a file was created with an appropriate number of rows logger.log(pop) with open(log_file, "r") as f: assert len(f.readlines()) == (2 * len(simple_chromosomes)) read_stdout = [line for line in capsys.readouterr().out.split('\n') if line != ''] # there should be nothing printed assert len(read_stdout) == 0
def test_mutate_elitist(self): pop = Population([1, 1, 3], eval_function=lambda x: x).evaluate().mutate( lambda x: x + 1, elitist=True) for chromosome in pop.chromosomes: assert chromosome > 1 assert len(pop) == 3
def test_summarylogger_write_file_mo_stdout(self, tmpdir, capsys): log_file = tmpdir.join('log.txt') logger = SummaryLogger(target=log_file, stdout=False) pop = Population(chromosomes=range(10), eval_function=lambda x: x) # we should see that a file was created with an appropriate number of rows pop.evaluate() logger.log(pop) with open(log_file, "r") as f: assert len(f.readlines()) == 1 # we should see that a file was created with an appropriate number of rows logger.log(pop) with open(log_file, "r") as f: assert len(f.readlines()) == 2 read_stdout = [line for line in capsys.readouterr().out.split('\n') if line != ''] # there should be nothing printed assert len(read_stdout) == 0
def test_mutate_func_kwargs(self): def mutate_func(x, y=0): return x + y pop = Population([1] * 100, eval_function=lambda x: x).mutate(mutate_func, y=16) for chromosome in pop.chromosomes: assert chromosome == 17
def test_every_mechanic_in_evolution_log(self, tmpdir, capsys): log_file = tmpdir.join('log.txt') logger = SummaryLogger(target=log_file, stdout=True) 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) .evaluate() .callback(logger.log, every=2)) pop.evolve(evolution=evo, n=100) with open(log_file, "r") as f: read_file = [item.replace("\n", "") for item in f.readlines()] assert len(read_file) == 50 # check characteristics of stoud read_stdout = [line for line in capsys.readouterr().out.split('\n') if line != ''] assert len(read_stdout) == 50
def test_population_generate(self, simple_evaluation_function): def init_func(): return 1 pop = Population.generate(init_function=init_func, eval_function=simple_evaluation_function, size=200) assert len(pop) == 200 assert pop.intended_size == 200 assert pop.individuals[0].chromosome == 1
def test_mutate_probability(self): seed(0) pop = Population([1] * 100, eval_function=lambda x: x).mutate( lambda x: x + 1, probability=0.5).evaluate() assert min(individual.chromosome for individual in pop.individuals) == 1 assert max(individual.chromosome for individual in pop.individuals) == 2 assert pop.current_best.fitness == 2 assert pop.documented_best.fitness == 2 assert len(pop) == 100
def run_travelling_salesman(population_size: int = 100, n_iterations: int = 10, random_seed: int = 0, n_destinations: int = 50, concurrent_workers: Optional[int] = None, n_groups: int = 4, silent: bool = False): seed(random_seed) # Generate some destinations destinations = [(random(), random()) for _ in range(n_destinations)] # Given a list of destination indexes, this is our cost function def evaluate(ordered_destinations: List[int]) -> float: total = 0 for x, y in zip(ordered_destinations, ordered_destinations[1:]): coordinates_x = destinations[x] coordinates_y = destinations[y] total += sqrt((coordinates_x[0] - coordinates_y[1])**2 + (coordinates_x[1] - coordinates_y[1])**2) return total # This generates a random solution def generate_solution() -> List[int]: indexes = list(range(n_destinations)) shuffle(indexes) return indexes def print_function(population: Population): if population.generation % 5000 == 0 and not silent: print( f'{population.generation}: {population.documented_best.fitness:1.2f} / ' f'{population.current_best.fitness:1.2f}') pop = Population.generate(generate_solution, eval_function=evaluate, maximize=False, size=population_size * n_groups, concurrent_workers=concurrent_workers) island_evo = (Evolution().survive(fraction=0.5).breed( parent_picker=pick_random, combiner=cycle_crossover).mutate(swap_elements, elitist=True)) evo = (Evolution().evaluate(lazy=True).callback(print_function).repeat( evolution=island_evo, n=100, grouping_function=group_stratified, n_groups=n_groups)) result = pop.evolve(evolution=evo, n=n_iterations) if not silent: print(f'Shortest route: {result.documented_best.chromosome}') print(f'Route length: {result.documented_best.fitness}')
def run_evolutionary(opt_value=1, population_size=100, n_parents=2, num_iter=200, survival=0.5, noise=0.1, seed=42): random.seed(seed) def init_func(): return (random.random() - 0.5) * 20 + 10 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): return [random.choice(pop) for i in range(n_parents)] def mean_parents(*parents): return sum(parents) / len(parents) def add_noise(chromosome, sigma): return chromosome + (random.random() - 0.5) * sigma pop = Population(chromosomes=[init_func() for _ in range(population_size)], eval_function=eval_func, maximize=True).evaluate() evo = (Evolution().survive(fraction=survival).breed( parent_picker=random_parent_picker, combiner=mean_parents, n_parents=n_parents).mutate(mutate_function=add_noise, sigma=noise).evaluate()) print("will start the evolutionary program, will log progress every step") for i in range(num_iter): pop = pop.evolve(evo).log() print( f"iteration:{i} best: {pop.current_best.fitness} worst: {pop.current_worst.fitness}" )
def test_survive_n_works(self, simple_chromosomes, simple_evaluation_function): pop1 = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) pop2 = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) pop3 = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) assert len(pop1) == len(simple_chromosomes) assert len(pop2.survive(n=50)) == 50 assert len(pop3.survive(n=75, luck=True)) == 75
def test_survive_p_works(self, simple_chromosomes, simple_evaluation_function): pop1 = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) pop2 = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) pop3 = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) assert len(pop1) == len(simple_chromosomes) assert len(pop2.survive(fraction=0.5)) == len(simple_chromosomes) * 0.5 assert len(pop3.survive(fraction=0.1, luck=True)) == len(simple_chromosomes) * 0.1
def test_breed_amount_works(self, simple_chromosomes, simple_evaluation_function): pop1 = Population(chromosomes=simple_chromosomes, eval_function=simple_evaluation_function) pop1.survive(n=50).breed( parent_picker=lambda population: choices(population, k=2), 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=lambda population: choices(population, k=2), combiner=lambda mom, dad: (mom + dad) / 2, population_size=400) assert len(pop2) == 400 assert pop2.intended_size == 400
def test_documented_best(self): pop = Population(chromosomes=[100, 100, 100], eval_function=lambda x: x * 2, maximize=True) assert pop.documented_best is None pop.evaluate() assert pop.documented_best.fitness == pop.current_best.fitness pop.mutate(mutate_function=lambda x: x - 10, probability=1).evaluate() assert pop.documented_best.fitness - 20 == pop.current_best.fitness