Commit 41b732e7 authored by Alexander R. Hankin's avatar Alexander R. Hankin
Browse files
parents 616db72d 2617ff41
......@@ -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
......
......@@ -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: int -- the current time step for the Manduca.
:returns: float -- 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: int -- the distance the Manduca has traveled.
:returns: float -- 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 parameters.
:type *other_params: optional args.
:param \*other_params: Additional properties 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
......
"""
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
from .Manducas import Manduca, SimpleManduca, ManducaDeformityError, ManducaBodyProperties
from .Population import EvolutionParameters, EvolutionSimulator, GenerationInfo
from .Visualization import animate, animate_as_gif, animate_as_mp4
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',
......
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