Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Alexander R. Hankin
EE194_Manduca_Simulator
Commits
8a92281d
Commit
8a92281d
authored
May 08, 2018
by
David A.. Werner
Browse files
Merge branch 'dev'. Refactored interfaces/behaviour. Multithreaded fitness calculation.
parents
609461eb
d999c7c5
Changes
6
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
8a92281d
...
...
@@ -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)
manduca/Manducas.py
View file @
8a92281d
...
...
@@ -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
...
...
manduca/Population.py
View file @
8a92281d
...
...
@@ -3,7 +3,7 @@ import copy
import
tqdm
from
collections
import
namedtuple
class
GenerationInfo
(
namedtuple
(
'GenerationInfo'
,
[
'generation_number'
,
'
incoming_fitness'
,
'outgoing_fitnes
s'
,
class
GenerationInfo
(
namedtuple
(
'GenerationInfo'
,
[
'generation_number'
,
'
fitness_value
s'
,
'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_fitnes
s
,
return
GenerationInfo
(
current_generation
+
1
,
fitness_value
s
,
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
setup.py
View file @
8a92281d
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'
,
...
...
tests/conftest.py
View file @
8a92281d
...
...
@@ -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
]
...
...
tests/test_Manducas.py
View file @
8a92281d
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment