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

Merge branch 'dev'

parents fb8c6f3d db17578d
Example Usage:
Return a random Manduca in a function
>>> num_legs, time_segments, time_step = 2, 4, 10
>>> def random_manduca():
>>> return SimpleManduca.random_individual(num_legs, time_segments, time_step)
import types
import numpy as np
from scipy.integrate import odeint
Example Usage:
Create an evolution simulator and run simulation for 25 generations using a pool of threads:
>>> try:
>>> pool = multiprocessing.Pool(int(multiprocessing.cpu_count()))
>>> simulator = EvolutionSimulator((random_manduca, POPULATION_SIZE), pool=pool)
>>> simulator.run_simulation(NUM_GENERATIONS)
>>> finally:
>>> pool.close()
import numpy as np
import copy
import tqdm
from collections import namedtuple
class GenerationInfo(namedtuple('GenerationInfo', ['generation_number', 'fitness_values',
'surviving_ancestors', 'surviving_children',
......@@ -11,28 +28,62 @@ class GenerationInfo(namedtuple('GenerationInfo', ['generation_number', 'fitness
'duplicate_children', 'duplicate_mutants'])):
def max_fitness(self):
"""This function gets the maximum fitness value in a generation of Manducas
:returns: float -- the max fitness value
return max(self.fitness_values)
def child_survival_rate(self):
if self.num_matings is None:
return None
return float(self.surviving_children) / self.num_matings
"""This function computes the survival rate for Manduca children
:returns: float -- the child survival rate.
if self.num_matings is None:
return None
return float(self.surviving_children) / self.num_matings
def mutant_survival_rate(self):
if self.num_mutants is None:
return None
return float(self.surviving_mutants) / self.num_mutants
"""This function computes the survival rate for Manduca mutants
:returns: float -- the mutant survival rate.
if self.num_mutants is None:
return None
return float(self.surviving_mutants) / self.num_mutants
def parent_survival_rate(self):
if self.num_ancestors is None:
return None
return float(self.surviving_ancestors) / self.num_ancestors
"""This function computes the survival rate for Manduca parents
:returns: float -- the parent survival rate.
if self.num_ancestors is None:
return None
return float(self.surviving_ancestors) / self.num_ancestors
class EvolutionParameters(object):
def __init__(self, max_population, num_matings, num_mutants, max_mutations, **kwargs):
"""This function is the constructor for the EvolutionParameters class
:param max_population: The maximum population size.
:type max_population: int.
:param num_matings: The number of matings.
:type num_matings: int.
:param num_mutants: The number of mutants.
:type num_mutants: int.
:param max_mutations: The maximum number of mutations that can happen.
:type max_mutations: int.
self.max_population = max_population
self.num_matings = num_matings
self.num_mutants = num_mutants
......@@ -41,13 +92,27 @@ class EvolutionParameters(object):
self.num_random = kwargs.pop('num_random', int(self.max_population/20.0))
def update_parameters(self, history):
"""Basic EvolutionParameters doesn't change as the simulation progresses"""
"""This function updates basic EvolutionParameters. Basic EvolutionParameters doesn't change as the simulation progresses"""
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):
"""This function is the constructor for the EvolutionSimulator class
:param population: A list of Manducas or a function that produces a new random Manduca.
:type population: list or function.
:param evolution_parameters: Extra evolution parameters.
:type evolution_parameters: EvolutionParameters.
:param random_number_generator: The type of random number generator.
:type random_number_generator: A random number generator.
:param random_seed: A random seed for the random number generator.
:type random_seed: int.
:param pool: A pool of processes to simulate in parallel using multiprocessing.
:type pool: multiprocessing.Pool.
"""Population can be array of manducas or tuple of (f, num_pop), where each call to f()
produces a member of the population"""
if evolution_parameters is None:
......@@ -82,9 +147,21 @@ class EvolutionSimulator(object):
def current_fitnesses(self):
"""This function gets the current fitnesses for the current generation.
:returns: float -- the array of fitnesses.
return sorted([ for m in self.population], reverse=True)
def next_generation(self, current_generation):
"""This function generates the next generation of Manducas.
:param current_generation: Used for logging purposes.
:type current_generation: int.
:returns: GenerationInfo -- a summary of the generation.
# mate: create children of two random parents
# mutate: create mutations of random individuals
......@@ -123,9 +200,17 @@ class EvolutionSimulator(object):
def population_size(self):
"""This function returns the size of the population
:returns: int -- the population size.
return len(self.population)
def mate(self):
"""This function performs the mating for the generation.
self._children = []
rng = self.random_number_generator
for _ in range(self.evolution_parameters.num_matings):
......@@ -134,6 +219,10 @@ class EvolutionSimulator(object):
self._children.append(p1.mate(p2, rng=rng))
def mutate(self):
"""This function performs the mutations for the generation.
self._mutants = []
for _ in range(self.evolution_parameters.num_mutants):
rng = self.random_number_generator
......@@ -142,6 +231,11 @@ class EvolutionSimulator(object):
def survive(self):
"""This function keeps the best 10 individuals as well as some random individuals weighted by fitness.
:returns: int -- the number of Manducas left
rng = self.random_number_generator
all_manducas = [(m,0) for m in self.population] + \
......@@ -158,6 +252,7 @@ class EvolutionSimulator(object):
idxs = rng.choice(len(unfit_manducas), num_random, replace=False)
randomly_selected_manducas = list(unfit_manducas[idxs])
idxs = []
randomly_selected_manducas = []
# Pick the rest randomly, weighted by fitness
remaining_manducas = self.evolution_parameters.max_population - num_fittest - num_random
......@@ -186,6 +281,16 @@ class EvolutionSimulator(object):
return num_after_survival
def run_simulation(self, num_generations, history_interval=1, progress_bar=True):
"""This function runs the genetic algorithm simulation
:param num_generations: The number of generations.
:type num_generations: int.
:param history_interval: The amount of years to skip between saved histories.
:type history_interval: int.
:param progress_bar: Whether to display the progress.
:type progress_bar: bool.
progress_kwargs = {'disable':not(progress_bar), 'desc':'Genetic Algorithm Simulation', 'unit':'gen'}
for generation in tqdm.tqdm(range(num_generations), **progress_kwargs):
generation_info = self.next_generation(generation)
......@@ -194,10 +299,18 @@ class EvolutionSimulator(object):
def max_fitness(self):
"""This function returns the maximum fitness value for all Manducas in the generation.
:returns: float -- the maximum fitness.
all_manducas = self.population + self._children + self._mutants
return max([ for manduca in all_manducas])
def update_fitness(self):
"""This function sets the fitnesses for a generation of Manducas
all_manducas = self.population + self._children + self._mutants
if self.pool is not None:
# Don't bother sending manducas that already have _fitness saved
......@@ -208,4 +321,11 @@ class EvolutionSimulator(object):
[ for manduca in all_manducas]
def compute_fitness(manduca):
"""This function gets the fitness value of a Manduca
:param manduca: The Manduca who's fitness is returned.
:type manduca: Manduca.
:returns: float -- the fitness value.
from setuptools import setup
description='A package for simulating manduca sexta',
author='David Werner',
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