Commit 6b72a679 authored by Alexander R. Hankin's avatar Alexander R. Hankin
Browse files

Adding documentation

parent 609461eb
......@@ -5,13 +5,26 @@ from collections import namedtuple
import StringIO
import functools
class ManducaDeformityError(Exception):
def __init__(self, message):
"""This function is the constructor for the ManducaDeformityError class
:param message: an informative message about what kind of deformity the Manduca has.
:type message: string.
"""
self.message = message
def __str__(self):
"""This function prints information about the ManducaDeformityError object
:returns: the repr function which prints the information.
"""
return repr(self.message)
# L0: Body segment resting spring lenght
# k: Elastic spring constant
# m: Leg mass
......@@ -24,6 +37,19 @@ class Manduca(object):
default_body_properties = ManducaBodyProperties(500, 1, 2, 1, 100)
damped_body_properties = ManducaBodyProperties(500, 1, 3, 1, 100)
def __init__(self, legs, muscles, time_step, body_properties=None, fitness_function=None):
"""This function is the constructor for the Manduca class
:param legs: A 2D array indicating the state of Manduca's legs at each time step.
:type legs: int.
:param muscles: A 2D array indicating the state of Manduca's muscles at each time step.
:type muscles: int.
:param time_step: Current time step
:type time_step: int.
:param body_properties: Additional body properties of the Manduca
:type body_properties: ManducaBodyProperties.
:param fitness_function: The name of the method of the Manduca class that returns the fitness value.
:type state: string.
"""
self._legs = self._muscles = None
self.legs = np.copy(legs)
self.muscles = np.copy(muscles)
......@@ -41,19 +67,41 @@ class Manduca(object):
@property
def fitness_function(self):
"""This function gets the fitness function for a specified Manduca.
:returns: string -- the fitness function.
"""
return self._fitness_function
@fitness_function.setter
def fitness_function(self, fn):
"""This function sets the fitness function for a specified Manduca.
:param fn: The fitness function to use.
:type fn: str.
"""
self._fitness = None
self._fitness_function = fn
@property
def legs(self):
"""This function gets the legs for a specified Manduca.
:returns: int -- the legs of the Manduca.
"""
return self._legs
@legs.setter
def legs(self, legs):
"""This function sets the legs for a specified Manduca. It also resets the fitness.
:param legs: The legs to use.
:type legs: int.
"""
new_legs = np.array(legs)
if self._legs is not None:
if new_legs.shape != self._legs.shape:
......@@ -65,10 +113,21 @@ class Manduca(object):
@property
def muscles(self):
"""This function gets the muscles for a specified Manduca.
:returns: int -- the muscles of the Manduca.
"""
return self._muscles
@muscles.setter
def muscles(self, muscles):
"""This function sets the muscles for a specified Manduca. It also resets the fitness.
:param muscles: The muscles to use.
:type name: int.
"""
new_muscles = np.array(muscles)
if self._muscles is not None:
if new_muscles.shape != self._muscles.shape:
......@@ -80,23 +139,49 @@ class Manduca(object):
@property
def time_step(self):
"""This function gets the current time step for a specified Manduca.
:returns: int -- the current time step for the Manduca.
"""
return self._time_step
@time_step.setter
def time_step(self, time_step):
"""This function sets the time step for a specified Manduca. It also resets the fitness.
:param time_step: The time step to use.
:type name: int.
"""
self._time_step = time_step
self._fitness = None
@property
def num_legs(self):
"""This function gets the number of legs of a specified Manduca.
:returns: int -- the number of legs of the Manduca.
"""
return self.legs.shape[1]
@property
def num_muscles(self):
"""This function gets the number of muscles of a specified Manduca.
:returns: int -- the number of muscles of the Manduca.
"""
return self.muscles.shape[1]
@property
def num_time_steps(self):
"""This function gets the number of time steps for a specified Manduca.
:returns: int -- the number of time steps for the Manduca.
"""
leg_ts, muscle_ts = self.legs.shape[0], self.muscles.shape[0]
if leg_ts != muscle_ts:
msg = 'Arbitrary number of time-steps. {} in legs, {} in muscles'
......@@ -106,6 +191,13 @@ 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.
"""
positions, velocites, times = manduca.simulate(record_level=0)
mean_starting_loc = np.mean(positions[0])
mean_end_loc = np.mean(positions[-1])
......@@ -114,15 +206,28 @@ class Manduca(object):
@property
def fitness(self):
"""This function gets the fitness function for a specified Manduca.
:returns: numeric -- the fitness value for the Manduca.
"""
if (self._fitness is None):
self._fitness = self._fitness_function(self)
return (self._fitness)
@property
def initial_position(self):
"""This function gets the initial position of a specified Manduca.
:returns: numpy array -- the initial position of the Manduca.
"""
return np.arange(self.num_legs)*self.body_properties.L0
def check_consistancy(self):
"""This function checks to make sure the Manduca's body is consistent, i.e., same number of legs and muscles.
"""
if self.num_muscles != self.num_legs - 1:
msg = 'Ill-defined manduca has {} legs and {} muscles'
e = msg.format(self.num_legs, self.num_muscles)
......@@ -131,6 +236,15 @@ class Manduca(object):
self.num_time_steps
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.
:type rng=np.random: A random number generator.
:returns: float -- the new muscle value.
"""
return rng.rand()*self.body_properties.muscle_strength
# First randomly choose the number of mutations (1 up to MAX_MUTATIONS)
......@@ -138,6 +252,14 @@ class Manduca(object):
# - 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.
:type max_mutations = 20: int.
"""
num_mutations = rng.randint(1, max_mutations)
for _ in range(num_mutations):
mutation_type = rng.randint(2)
......@@ -161,6 +283,15 @@ class Manduca(object):
# from the first parent or the second (with equal likelihood).
# 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.
:type rng=np.random: A random number generator.
:returns: Manduca -- the new child Manduca.
"""
child = self.clone()
num_time_steps = child.num_time_steps
for time_step in range(num_time_steps):
......@@ -173,6 +304,11 @@ class Manduca(object):
return child
def __repr__(self):
"""This function prints the Manduca object.
:returns: string -- containing information about the Manduca which includes legs, muscles, and body properties.
"""
output = StringIO.StringIO()
output.write("Body Properties: {}\n".format(self.body_properties))
output.write(" legs | muscles")
......@@ -185,31 +321,59 @@ class Manduca(object):
return val
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.
"""
return (self.time_step == other.time_step and
np.array_equal(self.legs, other.legs) and
np.array_equal(self.muscles, other.muscles) and
self.body_properties == other.body_properties)
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
self.body_properties != other.body_properties))
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.
"""
leg_str, m_str = self.legs.tostring(), self.muscles.tostring()
t_str = str(self.time_step)
manduca_str = leg_str + m_str + t_str
return hash(manduca_str)
def simulate(self, record_level=0, verbose_record=True, sub_intervals = 10):
""" record_level: Which intervals to record the position/velocity/time
def simulate(self, record_level=0, sub_intervals = 10):
"""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
:param sub_intervals: the number of subintervals simulated in the physics for each time interval/
:type sub_intervals: int.
:returns: -- recored positions, velocities, and times
returns recored positions, velocities, and times
"""
L0, k, c, m, muscle_strength = self.body_properties
......@@ -217,18 +381,21 @@ class Manduca(object):
def calculate_slopes(x, t):
"""This function computes the derivatives of x
x is position of legs and velocites of legs
in the form [x0, x1, ..., xn, v0, v1, ..., vn]
t is an array of time values over which to integrate
in the form [t0, t1, ..., tN]
The forces are as follows:
Back leg: f0' = k(x1-x0-L0) + c(v1-v0) + M01
Middle legs: fi' = k(x{i+1}-xi-L0) - k(xi-x{i-1}-L0) +
c(v{i+1}-vi) + c(v{i-1}-vi) + Mn{n+1} - M{n-1}n
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]
:param t: is an array of time values over which to integrate
in the form [t0, t1, ..., tN]
:return: numpy array -- the derivatives of x
"""
# Pull out the position and velocity vectors
pos, vel = x[0:self.num_legs], x[self.num_legs:2*self.num_legs]
......@@ -323,6 +490,14 @@ class Manduca(object):
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.
:type compressed: bool.
"""
if compressed:
save_fn = np.savez_compressed
else:
......@@ -333,6 +508,16 @@ 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.
"""
kwargs_copy = dict(kwargs)
try:
results = np.load(file_name)
......@@ -354,6 +539,11 @@ class Manduca(object):
raise(e)
def clone(self):
"""This functions clones a Manduca
:returns: Manduca -- the Manduca clone.
"""
return self.__class__(np.copy(self.legs), np.copy(self.muscles),
time_step=self.time_step,
body_properties=self.body_properties)
......@@ -362,6 +552,18 @@ class Manduca(object):
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.
"""
super(SimpleManduca, self).__init__(legs, muscles, time_step, **kwargs)
locked_legs, unlocked_legs = self.legs == 1, self.legs == 0
assert (locked_legs | unlocked_legs).all(), "Legs must be 0 or 1"
......@@ -371,6 +573,20 @@ 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.
:returns: Manduca -- the new random Manduca.
"""
body_properties = kwargs.pop('body_properties', None)
if body_properties == None:
body_properties = Manduca.default_body_properties
......@@ -383,4 +599,13 @@ class SimpleManduca(Manduca):
return SimpleManduca(legs, muscles, time_step, **kwargs)
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
:type rng=np.random: A random number generator
:returns: float -- the new muscle value.
"""
return current_value^self.body_properties.muscle_strength
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