Team: Avi, Ying

Goals: Our goal is to modify the double pendulum so that when the lower pendulum circles 360 degrees, the top one ticks forward. The intent is to recreate the motion of a clock which combines the second hand (the lower pendulum) and minute hand (the top pendulum) into one moving structure to suggest a direct relationship between the two hands.

Outcomes: It was a bit tricky to get the lower one to spin 360 degrees, but we were able to get it to bounce back and drive the top one to tick forward. Although the movement was not as smooth as we imagined, we were still able to generate the motion and relationship between the two arms as planned.

Process: 

Process/Code: We changed the dblpend_keyframes.py file to allow 240 frames, each 1/4 second apart. The only change required was:

#in dblpend_keyframes.py:
NUM_FRAMES = 240
class PendulumController(rcp.doublependulum.DoublePendulumController):
    def __init__(self):
        super().__init__()

        # for this example, there are four fixed poses commanded a fixed intervals, expressed as a set of (q0, q1) pairs
        self.keyframes = np.zeros((NUM_FRAMES,2))
        self.last_frame = None
        return

We also changed doublependulum.py, editing the PD control parameters to stabilize the first arm of the pendulum:

#in doublependulum.py

class DoublePendulumController(object):
    """Prototype for a double-pendulum controller.  This class is typically subclassed to customize the control strategy and the subclass passed into the common application framework.

    :ivar initial_state: four-element numpy array used to initialize the simulator state: [q1, q2, qd1, qd2]
    :ivar kp: two-element numpy array with default proportional position gains 
    :ivar ki: two-element numpy array with default integral gains
    :ivar kd: two-element numpy array with default velocity damping gain
    """
    def __init__(self):

        # delegate object to which debugging and status messages can be printed; it must implement write()
        self.console = None

        # fixed controller parameters
        self.initial_state = np.array([0.0, 0.0, 0.0, 0.0])
        self.d_state       = np.array((1.0, 0.5*math.pi, 0.0, 0.0))
        self.kp      = np.array((150, 8.0))
        self.ki      = np.array((4.0, 2.0))
        self.kd      = np.array((30, 2.0))
        
        return

and we also changed the time constant for the controller:

#in doublependulum.py

def timer_tick(self, delta_t):
        """Run the simulation for an interval.

        :param delta_t: length of interval in simulated time seconds
        """
        delta_t = .25
        while delta_t > 0:

            # calculate next control outputs
            self.control.compute_control(self.t, self.dt, self.state, self.tau)

            # calculate dynamics model
            qd = self.deriv()

            # Euler integration
            self.state = self.state + self.dt * qd
            delta_t -= self.dt
            self.t += self.dt

To generate 240 different clock states, we wrote a python script to output to a config file that can be loaded by the simulator:

import math
f = open("dblpend_keyframes.config", "w")
f.write("[keyframes]\n")
for i in range(240):
    angle0 = math.pi - (i // 4)* math.pi/30
    string0 = 'keyframe-{}-q0 = {}\n'.format(i,angle0)

    angle1 = (i % 4) * 2 * math.pi/4 - math.pi
    string1 = 'keyframe-{}-q1 = {}\n'.format(i,angle1)
    f.write(string0 + string1)
f.close()