I'm currently developing an animation featuring a double pendulum, and I'd like to incorporate sliders to enhance user interaction. These sliders are designed to control the two initial angles of the animation. However, I've encountered an issue: when I update the sliders, the animation restarts with the same initial values, despite the slider values being correctly reflected in the updated plot (verified through a print statement). I suspect there might be an issue with how I'm updating the animation.
To animate the double pendulum, I use this code:
def animate_double_pendulum(i, t, y, L1, L2, lines, points, trace):
theta1, _, theta2, _ = y[:, i]
x1 = L1 * np.sin(theta1)
y1 = -L1 * np.cos(theta1)
x2 = x1 + L2 * np.sin(theta2)
y2 = y1 - L2 * np.cos(theta2)
# Update the positions of the pendulum rods
lines[0].set_data([0, x1], [0, y1])
lines[1].set_data([x1, x2], [y1, y2])
# Update the positions of the pendulum masses (points)
points[0].set_data(x1, y1)
points[1].set_data(x2, y2)
# Update the trace of the endpoint
trace.set_data(np.append(trace.get_xdata(), x2), np.append(trace.get_ydata(), y2))
return lines + points + [trace]
The initial plot is made using this:
L1 = 1.0 # Length of pendulum 1
L2 = 1.0 # Length of pendulum 2
m1 = 1.0 # Mass of pendulum 1
m2 = 1.0 # Mass of pendulum 2
g = 9.81 # Gravitational acceleration
# Initial conditions
theta1_initial = np.radians(60.0)
theta2_initial = np.radians(5.0)
# Time span and number of points for simulation
t_span = (0, 20)
num_points = 1000
t, y = simulate_double_pendulum(theta1_initial, theta2_initial, L1, L2, m1, m2, g, t_span, num_points)
# Set up the initial plot with pendulum rods, masses, and trace
fig7, ax7 = plt.subplots(figsize=(6, 6))
line1, = ax7.plot([], [], lw=2, color='b')
line2, = ax7.plot([], [], lw=2, color='r')
point1, = ax7.plot([], [], 'bo', label='Mass 1')
point2, = ax7.plot([], [], 'ro', label='Mass 2')
trace, = ax7.plot([], [], lw=1, color='gray', label='Trace')
ax7.legend()
ax7.set_xlim(-2, 2)
ax7.set_ylim(-2, 2)
# Create the initial lines, points, and trace
lines = [line1, line2]
points = [point1, point2]
s_theta1, s_theta2 = get_sliders_double_pendulum()
# Animation parameters
interval = t_span[1] / num_points / 2 * 100 # milliseconds per frame
animation = FuncAnimation(
fig7, animate_double_pendulum, frames=num_points, fargs=(t, y, L1, L2, lines, points, trace),
interval=interval, repeat=True
)
plt.show()
And the code that tracks the sliders and the changes regarding those is the following:
def get_sliders_double_pendulum():
s_theta1 = widgets.FloatSlider(
description=r"$\theta_1$",
min=0.0, max=2*np.pi,
step=0.01,
value=theta1_initial,
continuous_update=False)
s_theta2 = widgets.FloatSlider(
description=r"$\theta_2$",
min=0.0, max=2*np.pi,
step=0.01,
value=theta2_initial,
continuous_update=False)
s_theta1.observe(on_theta_slider_change, names='value')
s_theta2.observe(on_theta_slider_change, names='value')
display(s_theta1, s_theta2)
return s_theta1, s_theta2
def on_theta_slider_change(change):
theta1 = s_theta1.value
theta2 = s_theta2.value
update_double_pendulum(theta1, theta2, lines, points, trace, animation)
def clear_double_pendulum(lines, points, trace):
lines[0].set_data([], [])
lines[1].set_data([], [])
points[0].set_data([], [])
points[1].set_data([], [])
trace.set_data([], [])
def update_double_pendulum(theta1, theta2, lines, points, trace, animation):
animation.event_source.stop()
clear_double_pendulum(lines, points, trace)
t, y = simulate_double_pendulum(theta1, theta2, L1, L2, m1, m2, g, t_span, num_points)
ax7.set(title=r'Double pendulum with starting position $\theta_1$'+f'={np.round(theta1, 2)}, '
+r'$\theta_2$='+f'{np.round(theta2, 2)}', xlabel='X', ylabel='Y')
# Update the animation data
animation = FuncAnimation(
fig7, animate_double_pendulum, frames=num_points, fargs=(t, y, L1, L2, lines, points, trace),
interval=interval, repeat=True
)
animation.frame_seq = animation.new_frame_seq()
animation._stop = False
# Restart the animation
animation.event_source.start()
In summary, my goal is for the animation to restart and showcase a new sequence when the sliders are adjusted. I aim to have the animation reflect the updated values of time (t) and position/angular velocity (y). Any help would be greatly appreciated!
Edit: To copy and paste the code to run it:
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
import sympy as sp
from scipy.integrate import solve_ivp
from matplotlib.animation import FuncAnimation
from ipywidgets import HBox, VBox, Layout, widgets
from IPython.display import display
from sympy import symbols, solve, Matrix
from matplotlib import colors
from matplotlib.patches import FancyArrowPatch
import warnings
warnings.filterwarnings("ignore")
def double_pendulum(t, y, L1, L2, m1, m2, g):
theta1, omega1, theta2, omega2 = y
c = np.cos(theta1 - theta2)
s = np.sin(theta1 - theta2)
# Equations of motion for the double pendulum
num1 = -g * (2 * m1 + m2) * np.sin(theta1)
num2 = -m2 * g * np.sin(theta1 - 2 * theta2)
num3 = -2 * s * m2 * (omega2 ** 2 * L2 + omega1 ** 2 * L1 * c)
den = L1 * (2 * m1 + m2 - m2 * np.cos(2 * theta1 - 2 * theta2))
theta1_prime = (num1 + num2 + num3) / den
num1 = 2 * s * (omega1 ** 2 * L1 * (m1 + m2) + g * (m1 + m2) * np.cos(theta1) + omega2 ** 2 * L2 * m2 * c)
den = L2 * (2 * m1 + m2 - m2 * np.cos(2 * theta1 - 2 * theta2))
theta2_prime = num1 / den
return [omega1, theta1_prime, omega2, theta2_prime]
def simulate_double_pendulum(theta1, theta2, L1, L2, m1, m2, g, t_span, num_points, omega1=0, omega2=0):
y0 = [theta1, omega1, theta2, omega2]
sol = solve_ivp(
fun=lambda t, y: double_pendulum(t, y, L1, L2, m1, m2, g),
t_span=t_span,
y0=y0,
method='RK45',
t_eval=np.linspace(t_span[0], t_span[1], num_points)
)
return sol.t, sol.y
def animate_double_pendulum(i, t, y, L1, L2, lines, points, trace):
theta1, _, theta2, _ = y[:, i]
x1 = L1 * np.sin(theta1)
y1 = -L1 * np.cos(theta1)
x2 = x1 + L2 * np.sin(theta2)
y2 = y1 - L2 * np.cos(theta2)
# Update the positions of the pendulum rods
lines[0].set_data([0, x1], [0, y1])
lines[1].set_data([x1, x2], [y1, y2])
# Update the positions of the pendulum masses (points)
points[0].set_data(x1, y1)
points[1].set_data(x2, y2)
# Update the trace of the endpoint
trace.set_data(np.append(trace.get_xdata(), x2), np.append(trace.get_ydata(), y2))
return lines + points + [trace]
def get_sliders_double_pendulum():
s_theta1 = widgets.FloatSlider(
description=r"$\theta_1$",
min=0.0, max=2*np.pi,
step=0.01,
value=theta1,
continuous_update=False)
s_theta2 = widgets.FloatSlider(
description=r"$\theta_2$",
min=0.0, max=2*np.pi,
step=0.01,
value=theta2,
continuous_update=False)
s_theta1.observe(on_theta_slider_change, names='value')
s_theta2.observe(on_theta_slider_change, names='value')
display(s_theta1, s_theta2)
return s_theta1, s_theta2
def on_theta_slider_change(change):
theta1 = s_theta1.value
theta2 = s_theta2.value
update_double_pendulum(theta1, theta2, lines, points, trace, animation)
def clear_double_pendulum(lines, points, trace):
lines[0].set_data([], [])
lines[1].set_data([], [])
points[0].set_data([], [])
points[1].set_data([], [])
trace.set_data([], [])
def update_double_pendulum(theta1, theta2, lines, points, trace, animation):
animation.event_source.stop()
clear_double_pendulum(lines, points, trace)
t, y = simulate_double_pendulum(theta1, theta2, L1, L2, m1, m2, g, t_span, num_points)
ax7.set(title=r'Double pendulum with starting position $\theta_1$'+f'={np.round(theta1, 2)}, '
+r'$\theta_2$='+f'{np.round(theta2, 2)}', xlabel='X', ylabel='Y')
# Update the animation data
animation = FuncAnimation(
fig7, animate_double_pendulum, frames=num_points, fargs=(t, y, L1, L2, lines, points, trace),
interval=interval, repeat=True
)
animation.frame_seq = animation.new_frame_seq()
animation._stop = False
# Restart the animation
animation.event_source.start()
L1 = 1.0 # Length of pendulum 1
L2 = 1.0 # Length of pendulum 2
m1 = 1.0 # Mass of pendulum 1
m2 = 1.0 # Mass of pendulum 2
g = 9.81 # Gravitational acceleration
# Initial conditions
theta1 = np.radians(100.0)
theta2 = np.radians(45.0)
# Time span and number of points for simulation
t_span = (0, 20)
num_points = 1000
t, y = simulate_double_pendulum(theta1, theta2, L1, L2, m1, m2, g, t_span, num_points)
# Set up the initial plot with pendulum rods, masses, and trace
fig7, ax7 = plt.subplots(figsize=(6, 6))
line1, = ax7.plot([], [], lw=2, color='b')
line2, = ax7.plot([], [], lw=2, color='r')
point1, = ax7.plot([], [], 'bo', label='Mass 1')
point2, = ax7.plot([], [], 'ro', label='Mass 2')
trace, = ax7.plot([], [], lw=1, color='gray', label='Trace')
ax7.legend()
ax7.set_xlim(-2, 2)
ax7.set_ylim(-2, 2)
# Create the initial lines, points, and trace
lines = [line1, line2]
points = [point1, point2]
s_theta1, s_theta2 = get_sliders_double_pendulum()
# Animation parameters
interval = t_span[1] / num_points / 2 * 100 # milliseconds per frame
animation = FuncAnimation(
fig7, animate_double_pendulum, frames=num_points, fargs=(t, y, L1, L2, lines, points, trace),
interval=interval, repeat=True
)
plt.show()
This is part of a bigger jupyter notebook, so if not all imports are used in this code, that's why.