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.