© Copyright, 2025 G. Schaer.

SPDX-License-Identifier: GPL-3.0-only

Project Example 3: The Double Pendulum

Set up the Visualizer, Animator, and Physics Environment

Import necessary condynsate modules

[1]:
from condynsate import Project
from condynsate import __assets__ as assets

Create a instance of condynsate.Project that uses the visualizer and animator. Also, set simulator_dt to 0.003 seconds so that the simulation fidelty is higher. Note, this will result in increased run time.

[2]:
proj = Project(visualizer = True, animator = True, simulator_dt = 0.003)
You can open the visualizer by visiting the following URL:
http://127.0.0.1:7000/static/

Load the double pendulum into the simulator. Set fixed = True so that the base link of the pendulum has 0 degrees of freedom. Also, set the initial angular velocity of one of the joints to some non-zero value.

[3]:
# Load and orient a double pendulum.
dp = proj.load_urdf(assets['double_pendulum.urdf'], fixed=True)

# Set the initial state
dp.joints['mass_1_to_arm_2'].set_initial_state(omega=0.1) # This returns 0 on success
[3]:
0

Now we will adjust the visualizer scene. First we turn off the axes visualization.

[4]:
# Turn off the axes and grid visualization.
proj.visualizer.set_axes(False) # This returns 0 on success
[4]:
0

Then we will move the camera’s position up just a slight amount from its default position and then tell it to look directly at the center of mass of the pendulum.

[5]:
# Set the camera's position
proj.visualizer.set_cam_position((0, -4, 2.5)) # This returns 0 on success

# Focus the camera on the gyro
proj.visualizer.set_cam_target(dp.center_of_mass) # This returns 0 on success
[5]:
0

Now we will remove all friction from the double pendulum. To do so, we need to set the damping of each joint to 0 and the air resistance of each link to 0

[6]:
# Set the damping of all joints to 0
for joint in dp.joints.values():
    joint.set_dynamics(damping=0.0,) # This returns 0 on success

# Set the air resistance of all links to 0
for link in dp.links.values():
    link.set_dynamics(linear_air_resistance=0.0, angular_air_resistance=0.0)

Add a line plot to the animator to plot the double pendulum’s state in phase space.

[7]:
# The return value of add_lineplot is either a list of or single
# hex value that is used to reference each line on the lineplot
# In this case, n_lines=2, so the return value is a tuple
# of hex codes used to reference each line in the plot
(arm_1, arm_2) = proj.animator.add_lineplot(n_lines=2,
                                            color=('r', 'b'),
                                            label=('Arm 1', 'Arm 2'),
                                            line_width=1.0,
                                            title='Pendulum State',
                                            x_label='Angle [rad]',
                                            y_label='Angular Rate [rad / sec]',
                                            x_lim=(-25, 25),
                                            y_lim=(-25, 25),
                                            h_zero_line=True,
                                            v_zero_line=True,)

Note the animator does not appear yet. This is because the GUI is not started yet. There are two ways to start the GUI:

  1. proj.reset

  2. proj.animator.start

Either is valid. In this case, we will just use the reset function.

Running the Simulation

Before running the simulation, we reset the project. This ensure that everything is started, updated, and in the desired initial state. proj.reset should be called before a simulation every time you run one.

[8]:
# Reset the project to its initial state. This is required to
# reset the simulation, reset the visualizer, and reset/start the
# animator.
proj.reset() # This returns 0 on success
[8]:
0

Important Note: After reset is called, the animator GUI will open. It will be unresponsive until it is refreshed by proj. This refreshes occur automatically at every proj.reset, proj.step, proj.await_anykeys, or proj.await_keypress call. Therefore, while the simulation loop is running, or while waiting for user keyboard input, the GUI will be responsive. You can also manually refresh it using the proj.refresh_animator function.

Next we create and run a simulation loop. In this loop, on every step we

  1. Plot the pendulum’s state in phase space

  2. Take a simulation step in real time

[9]:
# Run a 20 second simulation loop
while proj.simtime <= 20.:
    # Get the state of each joint
    state_1 = dp.joints['base_to_arm_1'].state
    angle_1 = state_1.angle
    omega_1 = state_1.omega
    state_2 = dp.joints['mass_1_to_arm_2'].state
    angle_2 = state_2.angle
    omega_2 = state_2.omega

    # Plot the state
    proj.animator.lineplot_append_point(arm_1, angle_1, omega_1) # This returns 0 on success
    proj.animator.lineplot_append_point(arm_2, angle_2, omega_2) # This returns 0 on success

    # Attempt to run in real time. The small step size may result in
    # slower than real time simulation
    proj.step(real_time=True, stable_step=False) # This returns 0 on success

Finally, we terminate the project. This is required to save any videos that are recorded and gracefully exit all the children threads (including the animator window). proj.terminate() should be called when done with any member of the condynsate.Project class.

[10]:
proj.terminate() # This returns 0 on success
[10]:
0
[ ]: