def run_rock_paper_scissors(population_size: int = 100, n_iterations: int = 200, random_seed: int = 42, survive_fraction: float = 0.8, arbitrariness: float = 0.2, concurrent_workers: int = 1, lizard_spock: bool = False, grouped: bool = False, silent: bool = False): seed(random_seed) RockPaperScissorsPlayer.arbitrariness = arbitrariness player_class = RockPaperScissorsLizardSpockPlayer if lizard_spock else RockPaperScissorsPlayer pop = ContestPopulation(chromosomes=[player_class() for _ in range(population_size)], eval_function=evaluate, maximize=True, concurrent_workers=concurrent_workers).evaluate() history = History() evo = Evolution().repeat( evolution=(Evolution() .survive(fraction=survive_fraction) .breed(parent_picker=pick_random, combiner=lambda x, y: x.combine(y), n_parents=2) .mutate(lambda x: x.mutate()) .evaluate() .callback(history.log)), n=n_iterations // 4, grouping_function=group_duplicate if grouped else None ) pop.evolve(evo, n=4) if not silent: history.plot() return history
def test_repr(self): assert repr(Evolution()) == 'Evolution()' assert repr( Evolution().evaluate()) == 'Evolution(\n EvaluationStep())' r = 'Evolution(\n RepeatStep() with evolution (10x):\n Evolution(\n' \ ' EvaluationStep()\n SurviveStep()))' assert repr(Evolution().repeat(Evolution().survive(fraction=0.9), n=10)) == r
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 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_every(self, tmpdir, simple_population): directory = tmpdir.mkdir('ckpt') evo = Evolution().mutate(lambda x: x+1).checkpoint(target=directory, method=self.method, every=10) simple_population.evolve(evolution=evo, n=9) assert len(listdir(directory)) == 0 simple_population.evolve(evolution=evo, n=11) assert len(listdir(directory)) == 2
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_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, logger=logger) pop2 = Population(chromosomes=list(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) + 1, n_parents=2).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_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_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 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 run_rock_paper_scissors(population_size=100, n_iterations=200, random_seed=42, survive_fraction=0.90, arbitrariness=0.0): seed(random_seed) RockPaperScissorsPlayer.arbitrariness = arbitrariness pop = ContestPopulation(chromosomes=[ RockPaperScissorsPlayer() for _ in range(population_size) ], eval_function=evaluation_func, maximize=True).evaluate() evo = (Evolution().survive(fraction=survive_fraction).breed( parent_picker=pick_random, combiner=lambda x, y: x.combine(y), n_parents=2).mutate(lambda x: x.mutate()).evaluate()) preferences_over_time = [] for _ in range(n_iterations): preferences = Counter() for individual in pop: preferences.update([individual.chromosome.preference]) pop = pop.evolve(evo) preferences_over_time.append(preferences) try: import matplotlib.pylab as plt import pandas as pd except ImportError: print( "If you install matplotlib and pandas you will get a pretty plot.") return ax = pd.DataFrame(preferences_over_time).fillna(0).plot(figsize=(10, 4)) ax.set_ylim([0, population_size]) ax.set_xlabel('iteration') ax.set_ylabel('# individuals with preference') plt.show()
self.i += 1 best = max([i.fitness for i in pop.evaluate()]) print(f"the best score i={self.i} => {best}") if __name__ == "__main__": logger = MyLogger() random.seed(42) pop = Population(chromosomes=[random_start() for _ in range(200)], eval_function=func_to_optimise, maximize=True, concurrent_workers=2) evo1 = (Evolution() .survive(fraction=0.1) .breed(parent_picker=pick_random_parents, combiner=make_child) .mutate(mutate_function=add_noise, sigma=0.2) .evaluate() .callback(logger.log)) evo2 = (Evolution() .survive(n=10) .breed(parent_picker=pick_random_parents, combiner=make_child) .mutate(mutate_function=add_noise, sigma=0.1) .evaluate() .callback(logger.log)) evo3 = (Evolution() .repeat(evo1, n=20) .repeat(evo2, n=20)) pop = pop.evolve(evo3, n=3)
def run_evolutionary(num_towns=42, population_size=100, num_iter=200, seed=42): """ Runs a simple evolutionary algorithm against a simple TSP problem. The goal is to explain the `evol` library, this is not an algorithm that should perform well. """ # First we generate random towns as candidates with a seed random.seed(seed) coordinates = [(random.random(), random.random()) for i in range(num_towns)] # Next we define a few functions that we will need in order to create an algorithm. # Think of these functions as if they are lego blocks. def init_func(num_towns): """ This function generates an individual """ order = list(range(num_towns)) random.shuffle(order) return order def dist(t1, t2): """ Calculates the distance between two towns. """ return math.sqrt((t1[0] - t2[0])**2 + (t1[1] - t2[1])**2) def eval_func(order): """ Evaluates a candidate chromosome, which is a list that represents town orders. """ return sum([ dist(coordinates[order[i]], coordinates[order[i - 1]]) for i, t in enumerate(order) ]) def pick_random(parents): """ This function selects two parents """ return random.choice(parents), random.choice(parents) def partition(lst, n_crossover): division = len(lst) / n_crossover return [ lst[round(division * i):round(division * (i + 1))] for i in range(n_crossover) ] def crossover_ox(mom_order, dad_order, n_crossover): idx_split = partition(range(len(mom_order)), n_crossover=n_crossover) dad_idx = sum([list(d) for i, d in enumerate(idx_split) if i % 2 == 0], []) path = [-1 for _ in range(len(mom_order))] for idx in dad_idx: path[idx] = dad_order[idx] cities_visited = {p for p in path if p != -1} for i, d in enumerate(path): if d == -1: city = [p for p in mom_order if p not in cities_visited][0] path[i] = city cities_visited.add(city) return path def random_flip(chromosome): result = chromosome[:] idx1, idx2 = random.choices(list(range(len(chromosome))), k=2) result[idx1], result[idx2] = result[idx2], result[idx1] return result pop = Population( chromosomes=[init_func(num_towns) for _ in range(population_size)], eval_function=eval_func, maximize=False).evaluate() evo = (Evolution().survive(fraction=0.1).breed( parent_picker=pick_random, combiner=crossover_ox, n_crossover=2).mutate(random_flip).evaluate()) print("will start the evolutionary program") scores = [] iterations = [] for i in range(num_iter): print( f"iteration: {i} best score: {min([individual.fitness for individual in pop])}" ) for indiviual in pop: scores.append(indiviual.fitness) iterations.append(i) pop = pop.evolve(evo) try: import matplotlib.pylab as plt plt.scatter(iterations, scores, s=1, alpha=0.3) plt.title("population fitness vs. iteration") plt.show() except ImportError: print("If you install matplotlib you will get a pretty plot.")
def add_noise(chromosome, sigma): new = chromosome + (np.random.rand(100) - 0.5) * sigma return new # We start by defining a population with candidates. pop = Population(chromosomes=[random_start() for _ in range(5)], eval_function=func_to_optimise, maximize=False) # We define a sequence of steps to change these candidates evo1 = ( Evolution().survive(fraction=0.2).breed(parent_picker=pick_random_parents, combiner=make_child) # .mutate(mutate_function=add_noise, sigma=1)) .mutate(mutate_function=add_noise, sigma=1)) # We define another sequence of steps to change these candidates evo2 = ( Evolution().survive(n=1).breed(parent_picker=pick_random_parents, combiner=make_child) # .mutate(mutate_function=add_noise, sigma=0.2)) .mutate(mutate_function=add_noise, sigma=0.1)) # We are combining two evolutions into a third one. You don't have to # but this approach demonstrates the flexibility of the library. evo3 = (Evolution().repeat(evo1, n=10).evaluate()) # In this step we are telling evol to apply the evolutions # to the population of candidates.
maximize=False, concurrent_workers=4) # Code to load a pickled/stored version, each 50 generation the population is written to disk # stored_pop = Population.load('./output/20200207-223736.187164.pkl', eval_function=score, maximize=False) # # Create new population from stored one, trick to get multiprocessing working after # pop = Population(chromosomes=[deepcopy(a) for a in stored_pop.chromosomes], # eval_function=score, maximize=False, concurrent_workers=4, generation=4550) print(f"Staring with {pop.concurrent_workers} workers") genome_duplication = (Evolution().survive(fraction=0.025).breed( parent_picker=pick_best_and_random, combiner=merge, population_size=population_size).mutate( mutate_function=mutate_painting, rate=0.05, sigma=0.5).evaluate(lazy=False).callback( print_summary, img_template=image_template, checkpoint_path=checkpoint_path)) evo_step_1 = (Evolution().survive(fraction=0.025).breed( parent_picker=pick_best_and_random, combiner=mate, population_size=population_size).mutate( mutate_function=mutate_painting, rate=0.05, sigma=0.5).evaluate(lazy=False).callback( print_summary, img_template=image_template, checkpoint_path=checkpoint_path))
def simple_evolution(): return (Evolution().survive(fraction=0.5).breed( parent_picker=pick_random, n_parents=2, combiner=lambda x, y: x + y).mutate(lambda x: x + 1, probability=0.1))
if __name__ == "__main__": checkpoint_path = "./geneticArt/" image_template = os.path.join(checkpoint_path, "drawing_%05d.png") targetImage = Image.open("./img/unicorn.png").convert('RGBA') #targetImage.show() #size = targetImage.size numPolygons = 150 popSize: int = 200 pop = Population(chromosomes=[ DrawImage(numPolygons, targetImage, background_color=(255, 255, 255)) for _ in range(popSize) ], eval_function=score, maximize=False, concurrent_workers=6) evolution = (Evolution().survive(fraction=0.05).breed( parent_picker=selection, combiner=crossover, population_size=popSize).mutate(mutate_function=mutation, rate=0.05).evaluate(lazy=False).apply( final, img_template=image_template, checkpoint_path=checkpoint_path)) pop = pop.evolve(evolution, n=2000)
def test_add_step(self): evo = Evolution() assert len(evo.chain) == 0 evo_step = evo._add_step('step') assert len(evo.chain) == 0 # original unchanged assert evo_step.chain == ['step'] # copy with extra step
def test_repeat_step(self): pop = Population([0 for i in range(100)], lambda x: x) evo = Evolution().repeat(Evolution().survive(fraction=0.9), n=10) # Check whether an Evolution inside another Evolution is actually applied assert len(pop.evolve(evo, n=2)) < 50