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
41b732e7
Commit
41b732e7
authored
May 09, 2018
by
Alexander R. Hankin
Browse files
Merge branch 'dev' of
https://github.cs.tufts.edu/dwerne01/EE194_Manduca_Simulator
into dev
parents
616db72d
2617ff41
Changes
5
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
41b732e7
...
...
@@ -29,13 +29,15 @@
*
[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
*
[x] saving animations as gifs
*
[x] saving animations as mp4s!
*
[x] making animations less ineffecient
*
[x] auto documentation using sphinx
*
[x]
`make docs`
*
[x]
`make docs_html`
*
[x]
`make docs_pdf`
*
[ ] Actually document everything..
*
[ ] ~~Actually document everything..~~
*
[x] Actually document some things!
*
[ ] ~~pep8 compliance~~
*
[x] Custom Exceptions
*
[x] Test code using py.test
...
...
manduca/Manducas.py
View file @
41b732e7
...
...
@@ -100,7 +100,7 @@ class Manduca(object):
def
legs
(
self
):
"""This function gets the legs for a specified Manduca.
:returns:
int
-- the legs of the Manduca.
:returns:
numpy.array
-- the legs of the Manduca.
"""
return
self
.
_legs
...
...
@@ -126,7 +126,7 @@ class Manduca(object):
def
muscles
(
self
):
"""This function gets the muscles for a specified Manduca.
:returns:
int
-- the muscles of the Manduca.
:returns:
numpy.array
-- the muscles of the Manduca.
"""
return
self
.
_muscles
...
...
@@ -150,9 +150,9 @@ class Manduca(object):
@
property
def
time_step
(
self
):
"""This function gets the
current
time step for a specified Manduca.
"""This function gets the
simulation
time step for a specified Manduca.
:returns:
in
t -- the
current
time step for the Manduca.
:returns:
floa
t -- the
simulation
time step for the Manduca.
"""
return
self
.
_time_step
...
...
@@ -203,10 +203,10 @@ class Manduca(object):
@
staticmethod
def
distance_traveled
(
manduca
):
"""This function gets the current time step for a specified Manduca.
:param manduca: The Manduca to evaluated.
:type manduca: Manduca.
:returns:
in
t -- the distance the Manduca has traveled.
:returns:
floa
t -- the distance the Manduca has traveled.
"""
positions
,
velocites
,
times
=
manduca
.
simulate
(
record_level
=
0
)
...
...
@@ -237,7 +237,9 @@ class Manduca(object):
:returns: numpy array -- the initial position of the Manduca.
"""
return
np
.
arange
(
self
.
num_legs
)
*
self
.
body_properties
.
L0
pos
=
np
.
arange
(
self
.
num_legs
)
*
self
.
body_properties
.
L0
pos
=
pos
-
np
.
mean
(
pos
)
return
pos
def
check_consistancy
(
self
):
"""This function checks to make sure the Manduca's body is consistent, i.e., same number of legs and muscles.
...
...
@@ -252,7 +254,7 @@ class Manduca(object):
def
change_muscle_value
(
self
,
current_value
,
rng
=
np
.
random
):
"""This function changes a muscle value for a specified Manduca.
:param current_value: The current muscle value.
:type current_value: float.
:param rng=np.random: The random number generator.
...
...
@@ -263,12 +265,12 @@ class Manduca(object):
return
rng
.
rand
()
*
self
.
body_properties
.
muscle_strength
# First randomly choose the number of mutations (1 up to MAX_MUTATIONS)
#
- flip a random leg-locked
#
- flip a random muscle-on
#
- do the secret-sauce mutation if desired.
#- flip a random leg-locked
#- flip a random muscle-on
#- do the secret-sauce mutation if desired.
def
mutate
(
self
,
rng
=
np
.
random
,
max_mutations
=
20
):
"""This function mutates a specified Manduca by either flipping a random leg locked or flipping a random muscle on.
:param rng=np.random: The random number generator.
:type rng=np.random: A random number generator.
:param max_mutations = 20: The maximum possible number of mutations that can happen.
...
...
@@ -299,7 +301,7 @@ class Manduca(object):
# This is the final function that you write yourself.
def
mate
(
self
,
parent2
,
rng
=
np
.
random
):
"""This function mates two lucky Manducas. Picks entire rows from one parent or the other to form child.
:param parent2: The other parent involved in the mating.
:type parent2: Manduca.
:param rng=np.random: The random number generator.
...
...
@@ -320,7 +322,7 @@ class Manduca(object):
def
__repr__
(
self
):
"""This function prints the Manduca object.
:returns: string -- containing information about the Manduca which includes legs, muscles, and body properties.
"""
...
...
@@ -337,7 +339,7 @@ class Manduca(object):
def
__eq__
(
self
,
other
):
"""This function checks to see if two Manducas are equivalent.
:param other: The other Manduca involved in the comparison.
:type other: Manduca.
:returns: bool -- whether the two Manducas are equivalent.
...
...
@@ -351,13 +353,13 @@ class Manduca(object):
def
__ne__
(
self
,
other
):
"""This function checks to see if two Manducas are different.
:param other: The other Manduca involved in the comparison.
:type other: Manduca.
:returns: bool -- whether the two Manducas are different.
"""
return
(
self
.
time_step
!=
other
.
time_step
or
not
(
np
.
array_equal
(
self
.
legs
,
other
.
legs
))
or
not
(
np
.
array_equal
(
self
.
muscles
,
other
.
muscles
)
or
...
...
@@ -365,7 +367,7 @@ class Manduca(object):
def
__hash__
(
self
):
"""This functions returns a hash of the summary of the Manduca's properties
:param parent2: The other parent involved in the mating.
:type parent2: Manduca.
:returns: Manduca -- the new child Manduca.
...
...
@@ -376,14 +378,14 @@ class Manduca(object):
manduca_str
=
leg_str
+
m_str
+
t_str
return
hash
(
manduca_str
)
def
simulate
(
self
,
record_level
=
0
,
sub_intervals
=
10
):
def
simulate
(
self
,
record_level
=
0
,
sub_intervals
=
10
,
record_legs_and_muscles
=
False
):
"""This functions returns a hash of the summary of the Manduca's properties
:param record_level: Which intervals to record the position/velocity/time
0 -> initial/final only
1 -> once for each interval
2 -> every sub-interval
* -> default to 0
\
* -> default to 0
:param sub_intervals: the number of subintervals simulated in the physics for each time interval/
:type sub_intervals: int.
...
...
@@ -403,10 +405,10 @@ class Manduca(object):
Front Leg: fn' = -k(xn-x{n-1}-L0) + c(v{n-1}-vn) + M{n-1}n
:param x: position of legs and velocites of legs
in the form [x0, x1, ..., xn, v0, v1, ..., vn]
in the form [x0, x1, ..., xn, v0, v1, ..., vn]
:param t: is an array of time values over which to integrate
in the form [t0, t1, ..., tN]
in the form [t0, t1, ..., tN]
:return: numpy array -- the derivatives of x
...
...
@@ -466,6 +468,7 @@ class Manduca(object):
# Record the initial position and velocities
recorded_positions
,
recorded_velocities
=
[
position
],
[
velocities
]
recorded_times
=
[
0.0
]
recorded_legs
,
recorded_muscles
=
[
self
.
legs
[
0
]],
[
self
.
muscles
[
0
]]
# For each time-step, advance the simulation
for
time
,
current_legs
,
current_muscles
in
zip
(
times
,
self
.
legs
,
self
.
muscles
):
...
...
@@ -492,21 +495,31 @@ class Manduca(object):
recorded_positions
.
append
(
position
)
recorded_velocities
.
append
(
velocities
)
recorded_times
.
append
(
end_time
)
recorded_legs
.
append
(
current_legs
)
recorded_muscles
.
append
(
current_muscles
)
elif
record_level
==
2
:
recorded_positions
.
extend
(
new_positions
)
recorded_velocities
.
extend
(
new_velocities
)
recorded_times
.
extend
(
new_times
)
for
_
in
range
(
sub_intervals
):
recorded_legs
.
append
(
current_legs
)
recorded_muscles
.
append
(
current_muscles
)
# If the record_level is 0, save the final interval as wel
if
record_level
not
in
[
1
,
2
]:
recorded_positions
.
append
(
position
)
recorded_velocities
.
append
(
velocities
)
recorded_times
.
append
(
end_time
)
recorded_legs
.
append
(
current_legs
)
recorded_muscles
.
append
(
current_muscles
)
if
record_legs_and_muscles
==
True
:
return
recorded_positions
,
recorded_velocities
,
recorded_times
,
recorded_legs
,
\
recorded_muscles
return
recorded_positions
,
recorded_velocities
,
recorded_times
def
save
(
self
,
file_name
,
compressed
=
True
):
"""This function saves information about a Manduca to a file
:param file_name: The name of the file to be written to.
:type file_name: string.
:param compressed: Whether or not the file should be compressed.
...
...
@@ -524,13 +537,13 @@ class Manduca(object):
@
classmethod
def
load
(
cls
,
file_name
,
*
other_params
,
**
kwargs
):
"""This functions loads information about a Manduca from a file
:param cls: The other parent
:type cls: Manduca.
:param file_name: The name of the file to be loaded from
:type file_name: string.
:param *other_params: Additional p
arameters.
:
type *other_params: optional args.
:param
\
*other_params: Additional p
roperties to load from the file and pass as \*args
:
param \**kwargs: kwargs passed to cls.__init__
"""
kwargs_copy
=
dict
(
kwargs
)
...
...
@@ -538,10 +551,11 @@ class Manduca(object):
results
=
np
.
load
(
file_name
)
legs
=
results
[
'legs'
]
muscles
=
results
[
'muscles'
]
time_step
=
results
[
'time_step'
]
time_step
=
np
.
asscalar
(
results
[
'time_step'
]
)
args
=
[]
for
param
in
other_params
:
args
.
append
(
results
[
param
])
# TODO: This should be removed (body_properties are saved with the manduca now)
if
'body_properties'
not
in
kwargs_copy
or
kwargs_copy
[
'body_properties'
]
is
None
:
kwargs_copy
[
'body_properties'
]
=
ManducaBodyProperties
(
*
results
[
'body_properties'
])
new_manduca
=
cls
(
legs
,
muscles
,
time_step
,
*
args
,
**
kwargs_copy
)
...
...
@@ -555,7 +569,7 @@ class Manduca(object):
def
clone
(
self
):
"""This functions clones a Manduca
:returns: Manduca -- the Manduca clone.
"""
...
...
@@ -563,20 +577,44 @@ class Manduca(object):
time_step
=
self
.
time_step
,
body_properties
=
self
.
body_properties
)
@
staticmethod
def
random_individual
(
num_legs
,
time_segments
,
time_step
,
**
kwargs
):
"""This functions returns a hash of the summary of the Manduca's properties
:param num_legs: The number of the legs the Manduca has.
:type num_legs: int.
:param time_step: .
:type time_step: int.
:param time_step: the time of one interval.
:type time_step: int.
:param \**kwargs: keyword args passed to super.
:returns: Manduca -- the new random Manduca.
"""
body_properties
=
kwargs
.
pop
(
'body_properties'
,
None
)
if
body_properties
==
None
:
body_properties
=
Manduca
.
default_body_properties
kwargs
[
'body_properties'
]
=
body_properties
muscle_strength
=
body_properties
.
muscle_strength
rng
=
kwargs
.
pop
(
'rng'
,
np
.
random
)
legs
=
rng
.
choice
([
0
,
1
],
size
=
(
time_segments
,
num_legs
))
muscles
=
rng
.
uniform
(
size
=
(
time_segments
,
num_legs
-
1
))
*
muscle_strength
return
Manduca
(
legs
,
muscles
,
time_step
,
**
kwargs
)
class
SimpleManduca
(
Manduca
):
"""A Manduca that has legs and muscles that are either ON/OFF"""
def
__init__
(
self
,
legs
,
muscles
,
time_step
,
**
kwargs
):
"""This function is the constructor for the Simple Manduca class
:param legs: 2D array representing Manduca's legs.
:type legs: int.
:param muscles: 2D array representing Manduca's muscles.
:type muscles: int.
:param time_step: the time of one interval.
:type time_step: int.
:param **kwargs: keyword args passed to super.
:type **kwargs: keyword args.
:param \**kwargs: keyword args passed to super.
"""
super
(
SimpleManduca
,
self
).
__init__
(
legs
,
muscles
,
time_step
,
**
kwargs
)
...
...
@@ -589,16 +627,15 @@ class SimpleManduca(Manduca):
@
staticmethod
def
random_individual
(
num_legs
,
time_segments
,
time_step
,
**
kwargs
):
"""This functions returns a hash of the summary of the Manduca's properties
:param num_legs: The number of the legs the Manduca has.
:type num_legs: int.
:param time_step: .
:type time_step: int.
:param time_step: the time of one interval.
:type time_step: int.
:param **kwargs: keyword args passed to super.
:type **kwargs: keyword args.
:param \**kwargs: keyword args passed to super.
:returns: Manduca -- the new random Manduca.
"""
...
...
@@ -615,7 +652,7 @@ class SimpleManduca(Manduca):
def
change_muscle_value
(
self
,
current_value
,
rng
=
np
.
random
):
"""This functions determines a new muscle value based on the previous
:param current_value: the current muscle value.
:type current_value: float.
:param rng=np.random: A random number generator
...
...
manduca/Visualization.py
0 → 100644
View file @
41b732e7
"""
Makes animations of manducas moving
"""
import
numpy
as
np
import
matplotlib.pyplot
as
plt
import
matplotlib.animation
as
animation
import
matplotlib.patches
as
patches
import
os
def
animate
(
*
manducas
,
**
kwargs
):
anim
=
_get_animation
(
*
manducas
,
**
kwargs
)
plt
.
show
()
plt
.
close
()
def
animate_as_gif
(
file_name
,
*
manducas
,
**
kwargs
):
anim
=
_get_animation
(
*
manducas
,
**
kwargs
)
_save_as_gif
(
file_name
,
anim
)
plt
.
close
()
def
animate_as_mp4
(
file_name
,
*
manducas
,
**
kwargs
):
anim
=
_get_animation
(
*
manducas
,
**
kwargs
)
_save_as_gif
(
file_name
,
anim
)
plt
.
close
()
def
_get_animation
(
*
manducas
,
**
kwargs
):
""" Produces an animation of all \*manducas racing
NOTE: Requires all manducas to have same time_step and num_time_steps!
"""
show_winners
=
kwargs
.
pop
(
'show_winners'
,
False
)
show_fitnesses
=
kwargs
.
pop
(
'show_fitnesses'
,
False
)
manducas_with_labels
=
[]
for
idx
,
val
in
enumerate
(
manducas
):
if
isinstance
(
val
,
tuple
):
manducas_with_labels
.
append
(
val
)
else
:
manducas_with_labels
.
append
((
val
,
str
(
idx
)))
if
show_fitnesses
or
show_winners
:
fitnesses
=
[
man
.
fitness
for
man
,
_
in
manducas_with_labels
]
if
show_winners
:
max_f
=
max
(
fitnesses
)
winners
=
np
.
array
(
fitnesses
)
==
max_f
else
:
winners
=
None
if
not
show_fitnesses
:
fitnesses
=
None
for
idx
in
range
(
len
(
manducas_with_labels
)):
manduca
,
label
=
manducas_with_labels
[
idx
]
if
fitnesses
is
not
None
:
new_label
=
"{}[fitness={}]"
.
format
(
label
,
fitnesses
[
idx
])
else
:
new_label
=
str
(
label
)
if
winners
is
not
None
:
if
winners
[
idx
]:
new_label
=
"{}[WINNER]"
.
format
(
new_label
)
manducas_with_labels
[
idx
]
=
(
manduca
,
new_label
)
# Make sure the manducas are compatible!
time_steps
=
list
(
set
([
manduca
.
time_step
for
manduca
,
_
in
manducas_with_labels
]))
assert
len
(
time_steps
)
==
1
,
\
"All manducas must have same time_step to be animated together"
num_time_steps
=
list
(
set
([
manduca
.
num_time_steps
for
manduca
,
_
in
manducas_with_labels
]))
assert
len
(
time_steps
)
==
1
,
\
"All manducas must have same number of time steps to be animated together"
dx
=
time_steps
[
0
]
positions
,
legs
,
muscles
=
[],
[],
[]
# Read the file(s) and build points[n_frames][15 items][n_worms]
for
manduca
,
labels
in
manducas_with_labels
:
sim_kwargs
=
{
key
:
kwargs
[
key
]
for
key
in
[
'sub_intervals'
]
if
key
in
kwargs
}
x
,
v
,
t
,
l
,
m
=
manduca
.
simulate
(
record_level
=
2
,
record_legs_and_muscles
=
True
,
**
sim_kwargs
)
positions
.
append
(
x
)
legs
.
append
(
l
)
muscle_strength
=
manduca
.
body_properties
.
muscle_strength
muscles
.
append
([
force
/
muscle_strength
for
force
in
m
])
labels
=
[
label
for
manduca
,
label
in
manducas_with_labels
]
return
_animate
(
labels
,
positions
,
legs
,
muscles
,
dx
,
fitnesses
=
fitnesses
,
winners
=
winners
,
**
kwargs
)
#TODO: Also do still-frames
def
get_stills
(
manduca
,
sub_intervals
=
None
):
pass
def
_save_as_gif
(
output_file
,
animation
,
dpi
=
80
,
**
kwargs
):
animation
.
save
(
output_file
,
dpi
=
dpi
,
writer
=
'imagemagick'
,
**
kwargs
)
def
_save_as_mp4
(
output_file
,
animation
,
dpi
=
80
,
**
kwargs
):
animation
.
save
(
output_file
,
dpi
=
dpi
,
writer
=
'mencoder'
,
**
kwargs
)
def
_animate
(
labels
,
positions
,
legs
,
muscles
,
dx
,
**
kwargs
):
leg_width
=
kwargs
.
pop
(
'leg_width'
,
30
)
show_result_percent
=
kwargs
.
pop
(
'show_result_percent'
,
0.90
)
fitnesses
=
kwargs
.
pop
(
'fitnesses'
,
None
)
winners
=
kwargs
.
pop
(
'winners'
,
None
)
# The per-frame animation function.
# Inputs: 'points' is a full array with[n_timepoints][data][n_worms] (where
# n_timepoints is the number of frames to be displayed).
# Remember that our display axes are:
# * x ranges over the min/max x values from the simulation.
# * y ranges from 0 to n_worms; i.e., each worm is allocated a vertical
# space of 1.
def
per_frame
(
frame_number
):
test_patches
=
[]
for
manduca_number
in
range
(
len
(
positions
)):
mx
=
positions
[
manduca_number
][
frame_number
]
ml
=
legs
[
manduca_number
][
frame_number
]
mm
=
muscles
[
manduca_number
][
frame_number
]
lrs
=
all_leg_rects
[
manduca_number
]
brs
=
all_body_rects
[
manduca_number
]
mrs
=
all_muscle_rects
[
manduca_number
]
for
rect
,
leg
,
pos
in
zip
(
lrs
,
ml
,
mx
):
x
,
w
=
pos
,
leg_width
if
leg
==
0
:
y
=
manduca_number
+
0.2
else
:
y
=
manduca_number
+
0.1
h
=
0.75
-
(
y
-
manduca_number
)
rect
.
set_bounds
(
x
,
y
,
w
,
h
)
for
br
,
mr
,
musc
,
pos
,
next_pos
in
zip
(
brs
,
mrs
,
mm
,
mx
,
mx
[
1
:]):
br
.
set_x
(
pos
+
leg_width
)
br
.
set_width
(
next_pos
-
pos
-
leg_width
)
mr
.
set_x
(
pos
+
leg_width
)
mr
.
set_width
(
next_pos
-
pos
-
leg_width
)
mr
.
set_alpha
(
musc
)
return
all_patches
xmin
=
min
([
min
(
min
(
val
)
for
val
in
l
)
for
l
in
positions
])
xmax
=
max
([
max
(
max
(
val
)
for
val
in
l
)
for
l
in
positions
])
xmax
+=
leg_width
subplot_kwargs
=
kwargs
.
pop
(
'subplot_kwargs'
,{})
fig
,
axes
=
plt
.
subplots
(
**
subplot_kwargs
)
axes
.
axis
([
xmin
,
xmax
,
0
,
len
(
positions
)
+
0.1
])
axes
.
set_autoscale_on
(
False
)
axes
.
get_yaxis
().
set_visible
(
False
)
for
i
,
label
in
enumerate
(
labels
):
axes
.
text
(
xmin
,
i
+
0.95
,
label
,
va
=
'top'
)
axes
.
plot
([
xmin
,
xmax
],[
i
+
0.1
]
*
2
,
'brown'
)
all_patches
=
[]
all_leg_rects
,
all_body_rects
,
all_muscle_rects
=
[],
[],
[]
for
idx
,
(
x
,
l
,
m
)
in
enumerate
(
zip
(
positions
,
legs
,
muscles
)):
x0
,
l0
,
m0
=
x
[
0
],
l
[
0
],
m
[
0
]
leg_rects
=
[
patches
.
Rectangle
((
0
,
0
),
0
,
0
,
facecolor
=
'r'
)
for
_
in
l0
]
body_rects
=
[
patches
.
Rectangle
((
0
,
idx
+
0.25
),
0
,
0.5
,
facecolor
=
'g'
)
for
_
in
m0
]
muscle_rects
=
[
patches
.
Rectangle
((
0
,
idx
+
0.375
),
0
,
0.25
,
facecolor
=
'k'
)
for
_
in
m0
]
all_leg_rects
.
append
(
leg_rects
)
all_body_rects
.
append
(
body_rects
)
all_muscle_rects
.
append
(
muscle_rects
)
all_patches
.
extend
(
leg_rects
+
body_rects
+
muscle_rects
)
[
axes
.
add_patch
(
p
)
for
p
in
all_patches
]
msec_per_frame
=
dx
*
1
num_frames
=
len
(
positions
[
0
])
ani
=
animation
.
FuncAnimation
(
fig
,
per_frame
,
frames
=
num_frames
,
interval
=
msec_per_frame
,
blit
=
True
,
repeat
=
False
)
return
ani
manduca/__init__.py
View file @
41b732e7
from
.Manducas
import
Manduca
,
SimpleManduca
,
ManducaDeformityError
,
ManducaBodyProperties
from
.Population
import
EvolutionParameters
,
EvolutionSimulator
,
GenerationInfo
from
.Visualization
import
animate
,
animate_as_gif
,
animate_as_mp4
setup.py
View file @
41b732e7
from
setuptools
import
setup
setup
(
name
=
'manduca'
,
version
=
'0.0.
2
'
,
version
=
'0.0.
3
'
,
description
=
'A package for simulating manduca sexta'
,
author
=
'David Werner'
,
author_email
=
'david.werner@tufts.edu'
,
...
...
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