Commit 616db72d authored by Alexander R. Hankin's avatar Alexander R. Hankin
Browse files

added example usages for Manducas.py and Popoulation.py

parent d96402f7
"""
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:
>>> POPULATION_SIZE, NUM_GENERATIONS= 10, 25
>>> 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',
'surviving_mutants',
......@@ -11,28 +28,62 @@ class GenerationInfo(namedtuple('GenerationInfo', ['generation_number', 'fitness
'duplicate_children', 'duplicate_mutants'])):
@property
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)
@property
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
@property
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
@property
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"""
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):
"""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):
@property
def current_fitnesses(self):
"""This function gets the current fitnesses for the current generation.
:returns: float -- the array of fitnesses.
"""
return sorted([m.fitness 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
self.mate()
# mutate: create mutations of random individuals
......@@ -123,9 +200,17 @@ class EvolutionSimulator(object):
@property
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):
self._mutants.append(clone)
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] + \
......@@ -186,6 +280,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 +298,18 @@ class EvolutionSimulator(object):
@property
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([manduca.fitness 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 +320,11 @@ class EvolutionSimulator(object):
[manduca.fitness for manduca in all_manducas]
def compute_fitness(manduca):
return manduca.fitness
"""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.
"""
return manduca.fitness
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