Tutorial 03: The Animator
Tutorial Description
This tutorial covers use the animator interface to create a real time plot of data during a simulation. We will demonstrate this by creating a phase-space plot of a forced pendulum.
Imports
To begin, we import the same modules for the same reasons as tutorial 00.
[1]:
from condynsate import Simulator as con_sim
from condynsate import __assets__ as assets
Building the Project Class
We now create a Project
class with __init__
and run
functions. In __init__
a pendulum is loaded using the same technique as tutorial 02. Additionally, the animator is set up the plot the phase diagram of the pendulum while the simulation is running. In run
, we cover how send state data to the animator.
[2]:
class Project():
def __init__(self):
'''
##################################################################
This time we want to use the animator, so we set animation to
True.
##################################################################
'''
# Create a instance of the simulator
self.s = con_sim(animation = True,
animation_fr = 24.0,
keyboard = False)
# Load the pendulum in the orientation we want
self.pendulum = self.s.load_urdf(urdf_path = assets['pendulum'],
position = [0., 0., 0.05],
yaw = 1.571,
wxyz_quaternion = [1., 0., 0., 0],
fixed = True,
update_vis = True)
# Set the initial angle of the pendulum joint
self.s.set_joint_position(urdf_obj = self.pendulum,
joint_name = 'chassis_to_arm',
position = 0.698,
initial_cond = True,
physics = False)
'''
##################################################################
Once our URDF is loaded and initial conditions are set, we move
on to creating the animator window.
condynsate.simulator.add_plot is how we tell the animator that
we want to add a plot to our animation GUI. We may call this
function as many times as we like and each time a new plot
will be added to the animation GUI. The arguments to this
function are as follows:
n_artists : int, optional
The number of artists that draw on the plot
The default is 1.
plot_type: either 'line' or 'bar', optional
The type of plot. May either be 'line' or 'bar'. The default
is 'line'.
title : string, optional
The title of the plot. Will be written above the plot when
rendered. The default is None.
x_label : string, optional
The label to apply to the x axis. Will be written under the plot
when rendered. The default is None.
y_label : string, optional
The label to apply to the y axis. Will be written to the left of
the plot when rendered. The default is None.
x_lim : [float, float], optional
The limits to apply to the x axis of the plot. A value of None
will apply automatically updating limits to the corresponding
bound of the axis. For example [None, 10.] will fix the upper
bound to exactly 10, but the lower bound will freely change to
show all data.The default is [None, None].
y_lim : [float, float], optional
The limits to apply to the y axis of the plot. A value of None
will apply automatically updating limits to the corresponding
bound of the axis. For example [None, 10.] will fix the upper
bound to exactly 10, but the lower bound will freely change to
show all data.The default is [None, None].
h_zero_line : boolean, optional
A boolean flag that indicates whether a horizontal line will be
drawn on the y=0 line. The default is false
v_zero_line : boolean, optional
A boolean flag that indicates whether a vertical line will be
drawn on the x=0 line. The default is false
color : matplotlib color string or tuple of color strings, optional
The color each artist draws in. When tuple, must have length
n_artists. The default is 'black'.
label : string or tuple of strings, optional
The label applied to each artist. For line charts,
the labels are shown in a legend in the top right of the plot. For
bar charts, the labels are shown on the y axis next to their
corresponging bars. When tuple, must have length n_artists.
When None, no labels are made. The default is None.
line_width : float or tuple of floats, optional
The line weigth each artist uses. For line plots, this is the
width of the plotted line, for bar charts, this is the width of
the border around each bar. When tuple, must have length n_artists.
The default is 1.5.
line_style : line style string or tuple of ls strings, optional
The line style each artist uses. For line plots, this is the
style of the plotted line, for bar charts, this argument is not
used and therefore ignored. When tuple, must have length n_artists.
The default is 'solid'.
tail : int or tuple of ints optional
Specifies how many data points are used to draw a line. Only the
most recently added data points are kept. Any data points added
more than tail data points ago are discarded and not plotted. Only
valid for line plots. When tuple, must have length n_artists. A
value less than or equal to 0 means that no data is ever discarded
and all data points added to the animator will be drawn.
The default is -1.
The function returns:
plot_id : int
A integer identifier that is unique to the plot created.
This allows future interaction with this plot (adding data
points, etc.).
artist_ids : tuple of ints, optional
A tuple of integer identifiers that are unique to the artist
created. This allows future interaction with these artists (adding
data points, etc.). Is only returned when n_artists > 1.
plot_id can be considered a pointer to a specific
plot. If you want to make edits to a plot, you must first
identify which plot you are modifying. This is done with
plot_id. Each subplot can have multiple artists. You can
think of an artist as a pen. If you want to draw two lines on a
plot at the same time, you will need two pens, i.e. two
artists. Each time you add a plot with more than 1 artists, you
will receive a list of artist_ids. You can again think of this as
a list of pointers to each artists in that plot. Note that if a
plot is defined to have only 1 artist, no artist_ids are returned.
This is because there is no need to disambiguate artists.
##################################################################
'''
# Make plot for phase space
self.p = self.s.add_plot(n_artists = 1, # Only 1 artist means no artist_ids are returned
plot_type = 'line',
title = "Phase Space",
x_label = "Angle [Deg]",
y_label = "Angle Rate [Deg / Sec]",
color = "k",
line_width = 2.5,
line_style = "-",
x_lim = [-40.,40],
y_lim = [-275.,275],
h_zero_line = True,
v_zero_line = True)
'''
##################################################################
Once we are done adding subplots to the animator, we open the
animator GUI.
##################################################################
'''
# Open the animator GUI
self.s.start_animator()
def run(self, max_time=None):
'''
##################################################################
This run function does all the same basic functions as in
tutorial 02 but with the added functionality of real time
animation of the phase of the pendulum.
##################################################################
'''
# Reset the simulator.
self.s.reset()
# Run the simulation loop until done
while(not self.s.is_done):
# Get the pendulum's joint state
state = self.s.get_joint_state(urdf_obj = self.pendulum,
joint_name = 'chassis_to_arm')
# Get the angle and angular velocity of the pendulum
angle = 180. * state['position'] / 3.142
angle_vel = 180. * state['velocity'] / 3.142
# Apply a proportional torque
torque = -angle - 0.01*angle_vel
self.s.set_joint_torque(urdf_obj = self.pendulum,
joint_name = 'chassis_to_arm',
torque = torque,
show_arrow = True,
arrow_scale = 0.02,
arrow_offset = 0.05)
'''
##############################################################
This is how we modify a plot in real time. Essentially, we
identify which artist of which plot we would like to draw
a data point, and then we specify that data point.
Because our plot only has a single artist, there is no need
to reference the artist_id
##############################################################
'''
# Add (angle, angle_vel) point to plot self.p
self.s.add_line_datum(plot_id = self.p,
x = angle,
y = angle_vel)
'''
##############################################################
As usual, at the bottom of the run function we step the
simulation.
##############################################################
'''
ret_code = self.s.step(max_time=max_time)
'''
##############################################################
Finally, when the simulation loop is over and we are done with
the animator, we terminate it.
##############################################################
'''
self.s.terminate_animator()
Running the Project Class
Now that we have made the Project
class, we can test it by initializing it and then calling the run
function. Note: %%capture
is required to supress the animator drawing the first frame to this notebook. Though it is not required, it is reccomended. It must be the first line in the cell in which it’s called.
[3]:
%%capture
# Create an instance of the Project class.
proj = Project()
# Run the simulation
proj.run(max_time = 5.0)
Challenge
This tutorial is now complete. For an additional challenge, think of how you might add another plot to the animator that draws both the angle and angular velocity as a function of time.