Commit a08dc059 authored by David A.. Werner's avatar David A.. Werner
Browse files

Initial version of Population.py

parent b81d5baa
import numpy as np
import copy
import tqdm
from collections import namedtuple
class GenerationInfo(namedtuple('GenerationInfo', ['generation_number', 'incoming_fitness', 'outgoing_fitness',
'surviving_ancestors', 'surviving_children',
'surviving_mutants',
'num_ancestors', 'num_matings', 'num_mutants',
'duplicate_population',
'duplicate_children', 'duplicate_mutants'])):
@property
def max_fitness(self):
return max(self.outgoing_fitness)
class EvolutionParameters(object):
def __init__(self, max_population, num_matings, num_mutants, max_mutations, **kwargs):
self.max_population = max_population
self.num_matings = num_matings
self.num_mutants = num_mutants
self.max_mutations = max_mutations
self.num_fittest = kwargs.pop('num_fittest', int(self.max_population/2))
self.num_random = kwargs.pop('num_random', int(self.max_population/5))
def update_parameters(self, history):
"""Basic EvolutionParameters doesn't change as the simulation progresses"""
pass
class EvolutionSimulator(object):
default_evolution_parameters = EvolutionParameters(20, 10, 10, 10)
def __init__(self, population, evolution_parameters=None, random_number_generator=None, random_seed=None, pool=None):
"""Population can be array of manducas or tuple of (f, num_pop), where each call to f()
produces a member of the population"""
if isinstance(population, tuple):
self.population = [population[0]() for _ in range(population[1])]
else:
self.population = population
self._children = []
self._mutants = []
self.history = []
if evolution_parameters is None:
self.evolution_parameters = copy.copy(self.__class__.default_evolution_parameters)
else:
self.evolution_parameters = copy.copy(evolution_parameters)
if random_number_generator is None:
self.random_number_generator = np.random.RandomState()
else:
self.random_number_generator = random_number_generator
if random_seed:
self.random_number_generator.seed(random_seed)
self.pool = pool
def next_generation(self, current_generation):
incoming_fitness = sorted([m.fitness for m in self.population], reverse=True)
# mate: create children of two random parents
self.mate()
# mutate: create mutations of random individuals
self.mutate()
# remove duplicates
num_before_dups = [len(self.population), len(self._children), len(self._mutants)]
# individuals in current population are unique
self.population = list(set(self.population))
# remove children who are clones of others in the population
self._children = [c for c in self._children if c not in self.population]
# remove mutants who are clones of others in the population or of a child
self._mutants = [m for m in self._mutants if m not in self.population+self._children]
num_after_dups = [len(self.population), len(self._children), len(self._mutants)]
duplicate_population = num_before_dups[0] - num_after_dups[0]
duplicate_children = num_before_dups[1] - num_after_dups[1]
duplicate_mutants = num_before_dups[2] - num_after_dups[2]
self.update_fitness()
num_after_survival = self.survive()
surviving_ancestors, surviving_children, surviving_mutants = num_after_survival
outgoing_fitness = sorted([m.fitness for m in self.population], reverse=True)
num_ancestors = self.evolution_parameters.max_population
num_matings = self.evolution_parameters.num_matings
num_mutants = self.evolution_parameters.num_mutants
self.evolution_parameters.update_parameters(self.history)
return GenerationInfo(current_generation, incoming_fitness, outgoing_fitness,
surviving_ancestors, surviving_children,
surviving_mutants, num_ancestors,
num_matings, num_mutants,
duplicate_population,
duplicate_children, duplicate_mutants)
@property
def population_size(self):
return len(self.population)
def mate(self):
self._children = []
rng = self.random_number_generator
for _ in range(self.evolution_parameters.num_matings):
p1 = self.random_number_generator.choice(self.population)
p2 = self.random_number_generator.choice(self.population)
self._children.append(p1.mate(p2, rng=rng))
def mutate(self):
self._mutants = []
for _ in range(self.evolution_parameters.num_mutants):
rng = self.random_number_generator
clone = rng.choice(self.population).clone()
clone.mutate(max_mutations=self.evolution_parameters.max_mutations,rng=rng)
self._mutants.append(clone)
def survive(self):
rng = self.random_number_generator
all_manducas = [(m,0) for m in self.population] + \
[(m,1) for m in self._children] + \
[(m,2) for m in self._mutants]
all_manducas.sort(key=lambda val: val[0].fitness, reverse=True)
# Always keep the best 10 individuals
num_fittest = self.evolution_parameters.num_fittest
fittest_manducas, unfit_manducas = all_manducas[:num_fittest], all_manducas[num_fittest:]
unfit_manducas = np.array(unfit_manducas)
# Pick more completely randomly
num_random = self.evolution_parameters.num_random
if num_random > 0:
idxs = rng.choice(len(unfit_manducas), num_random, replace=False)
randomly_selected_manducas = list(unfit_manducas[idxs])
else:
randomly_selected_manducas = []
# Pick the rest randomly, weighted by fitness
remaining_manducas = self.evolution_parameters.max_population - num_fittest - num_random
if remaining_manducas > 0:
fitnesses = [val[0].fitness for val in unfit_manducas]
fitnesses -= min(fitnesses)
for idx in idxs:
fitnesses[idx] = 0 # Make sure not to duplicate already selected individuals
probs = fitnesses / sum(fitnesses)
idxs = rng.choice(len(unfit_manducas), remaining_manducas, p=probs, replace=False)
remaining_selected_manducas = list(unfit_manducas[idxs])
else:
remaining_selected_manducas = []
selected_manducas = fittest_manducas + randomly_selected_manducas + remaining_selected_manducas
self.population = [val[0] for val in selected_manducas if val[1]==0]
self._children = [val[0] for val in selected_manducas if val[1]==1]
self._mutants = [val[0] for val in selected_manducas if val[1]==2]
num_after_survival = [len(self.population), len(self._children), len(self._mutants)]
self.population = self.population + self._children + self._mutants
self._children = self._mutants = []
return num_after_survival
def run_simulation(self, num_generations, history_interval=1, progress_bar=True):
for generation in tqdm.tqdm(range(num_generations), disable=not(progress_bar)):
generation_info = self.next_generation(generation)
if generation%history_interval == 0 or generation==0 or generation==num_generations-1:
self.history.append(generation_info)
@property
def max_fitness(self):
all_manducas = self.population + self._children + self._mutants
return max([manduca.fitness for manduca in all_manducas])
def update_fitness(self):
all_manducas = self.population + self._children + self._mutants
[manduca.fitness for manduca in all_manducas]
from .Manducas import Manduca, SimpleManduca, ManducaDeformityError
from .Manducas import Manduca, SimpleManduca, ManducaDeformityError, ManducaBodyProperties
from .Population import EvolutionParameters, EvolutionSimulator, GenerationInfo
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment