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

Merge branch 'dev'. Refactored interfaces/behaviour. Multithreaded fitness calculation.

parents 609461eb d999c7c5
......@@ -25,15 +25,22 @@
#Project Description
## Goals
* [ ] Simplified interfaces and classes
* [ ] Population, Manduca, SimpleManduca, History
* [ ] Customizable plotting of historic values
* [x] Simplified interfaces and classes
* [x] Simulation: Population, Manduca, SimpleManduca
* [x] Logged values for plotting: History
* [x] properties
* [ ] saving animations as gifs
* [ ] making animations less ineffecient
* [ ] auto documentation using sphinx
* [ ] conf.py
* [ ] pep8 compliance
* [ ] properties
* [ ] Custom Exceptions
* [ ] Test code using py.test
* [ ] wrappers
* [ ] multi-threading using multithreading.Pool
* [ ] Hist
* [x] `make docs`
* [x] `make docs_html`
* [x] `make docs_pdf`
* [ ] Actually document everything..
* [ ] ~~pep8 compliance~~
* [x] Custom Exceptions
* [x] Test code using py.test
* [x] `make test`
* [x] wrappers: see [demo code](https://github.cs.tufts.edu/dwerne01/EE194_Manduca_Simulator_Demo)
* [x] multi-threading using multithreading.Pool
* [x] Population.update_fitnesses() runs in parallel
* [ ] ~~Population.mate() and Population.mutate()~~ (would result in non-deterministic simulations)
......@@ -31,7 +31,7 @@ class Manduca(object):
self._fitness = None
self.check_consistancy()
if fitness_function == None:
self._fitness_function = Manduca.distance_traveled
self._fitness_function = 'distance_traveled'
else:
self._fitness_function = fitness_function
if body_properties == None:
......@@ -114,8 +114,14 @@ class Manduca(object):
@property
def fitness(self):
""" To use an alternate fitness function, add the function to the class and set
self.fitness_function to be a string containing the function name"""
if (self._fitness is None):
self._fitness = self._fitness_function(self)
fn = getattr(self, self._fitness_function)
if isinstance(fn, types.FunctionType):
self._fitness = getattr(self, self._fitness_function)(self)
else:
self._fitness = getattr(self, self._fitness_function)()
return (self._fitness)
@property
......
......@@ -3,7 +3,7 @@ import copy
import tqdm
from collections import namedtuple
class GenerationInfo(namedtuple('GenerationInfo', ['generation_number', 'incoming_fitness', 'outgoing_fitness',
class GenerationInfo(namedtuple('GenerationInfo', ['generation_number', 'fitness_values',
'surviving_ancestors', 'surviving_children',
'surviving_mutants',
'num_ancestors', 'num_matings', 'num_mutants',
......@@ -11,7 +11,25 @@ class GenerationInfo(namedtuple('GenerationInfo', ['generation_number', 'incomin
'duplicate_children', 'duplicate_mutants'])):
@property
def max_fitness(self):
return max(self.outgoing_fitness)
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
@property
def mutant_survival_rate(self):
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
class EvolutionParameters(object):
def __init__(self, max_population, num_matings, num_mutants, max_mutations, **kwargs):
......@@ -19,8 +37,8 @@ class EvolutionParameters(object):
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))
self.num_fittest = kwargs.pop('num_fittest', int(self.max_population/2.0))
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"""
......@@ -32,17 +50,21 @@ class EvolutionSimulator(object):
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])]
if evolution_parameters is None:
self.evolution_parameters = copy.copy(self.__class__.default_evolution_parameters)
else:
self.evolution_parameters = copy.copy(evolution_parameters)
if isinstance(population, list):
self.population = population
else:
self.population = [population() for _ in range(self.evolution_parameters.max_population)]
n_before = len(self.population)
self.population = list(set(self.population))
n_after = len(self.population)
dups_in_pop = n_before - n_after
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:
......@@ -50,9 +72,19 @@ class EvolutionSimulator(object):
if random_seed:
self.random_number_generator.seed(random_seed)
self.pool = pool
self.update_fitness()
initial_generation_info = GenerationInfo(0, self.current_fitnesses,
None, None, None,
None, None, None,
dups_in_pop,None,None)
self.history.append(initial_generation_info)
@property
def current_fitnesses(self):
return sorted([m.fitness for m in self.population], reverse=True)
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
......@@ -75,14 +107,14 @@ class EvolutionSimulator(object):
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)
fitness_values = self.current_fitnesses
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,
return GenerationInfo(current_generation+1, fitness_values,
surviving_ancestors, surviving_children,
surviving_mutants, num_ancestors,
num_matings, num_mutants,
......@@ -150,6 +182,7 @@ class EvolutionSimulator(object):
num_after_survival = [len(self.population), len(self._children), len(self._mutants)]
self.population = self.population + self._children + self._mutants
self._children = self._mutants = []
self.population.sort(key=lambda val: val.fitness, reverse=True)
return num_after_survival
def run_simulation(self, num_generations, history_interval=1, progress_bar=True):
......@@ -166,4 +199,13 @@ class EvolutionSimulator(object):
def update_fitness(self):
all_manducas = self.population + self._children + self._mutants
[manduca.fitness for manduca in all_manducas]
if self.pool is not None:
# Don't bother sending manducas that already have _fitness saved
all_manducas = filter(lambda m: m._fitness is None, all_manducas)
for idx, fitness in enumerate(self.pool.map(compute_fitness, all_manducas)):
all_manducas[idx]._fitness = fitness
else:
[manduca.fitness for manduca in all_manducas]
def compute_fitness(manduca):
return manduca.fitness
from setuptools import setup
setup(name='manduca',
version='0.0.1',
version='0.0.2',
description='A package for simulating manduca sexta',
author='David Werner',
author_email='david.werner@tufts.edu',
......
......@@ -2,7 +2,8 @@ from manduca import Manduca, ManducaBodyProperties
all_body_properties = [None, Manduca.default_body_properties, Manduca.damped_body_properties,
ManducaBodyProperties(10,4,6,2,10)]
all_fitness_functions = [Manduca.distance_traveled, lambda m: 1/sum(m.muscles+0.001)]
#TODO (fn_name, fn_def or None if already a function)]
all_fitness_functions = ['distance_traveled']
all_num_time_steps = [2, 3, 5, 10]
all_num_legs = [2, 3, 4, 5, 10]
all_time_steps = [0.01, 1.0, 10.0, 1]
......
......@@ -140,8 +140,11 @@ def test_fitness_reset():
# 3) Make sure it was reset
# 4) Make sure fitness evaluates to the correct value
# 5) Make sure _fitness is updated as well
def neg_one(man):
return -1
SimpleManduca.neg_one = neg_one
man._fitness = 1
man.fitness_function = lambda m: -1
man.fitness_function = 'neg_one'
assert man._fitness == None
assert man.fitness == -1
assert man._fitness == -1
......
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