I'm working an an animation regarding the double pendulum problem. I want it so that there are 2 sliders, one for the intial value tetha1 and one for theta2. When one of the sliders is changed, I want the animation that is playing to stop, and restart with the new initial values indicated by the sliders. The initial animation works, but as soon as I change one of the sliders, the animation did indeed stop, the ax did clear, but no new animation was displayed.
This is the code I have written to get the trajectory of the double pendulum:
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):
y0 = [theta1, 0, theta2, 0]
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
This code is used to get the animation:
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]
And this code is used to track the two sliders and their changes:
def get_sliders_double_pendulum():
s_theta1 = widgets.FloatSlider(
description=r"$\theta_1$",
min=0.0, max=np.pi,
step=0.01,
value=theta1_initial,
continuous_update=False)
s_theta2 = widgets.FloatSlider(
description=r"$\theta_2$",
min=0.0, max=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
new_animation = update_double_pendulum(theta1, theta2, animation)
plt.show()
def clear_double_pendulum():
ax7.clear()
for txt in ax7.texts:
txt.set_visible(False)
def update_double_pendulum(theta1, theta2, animation):
animation.event_source.stop()
clear_double_pendulum()
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)
lines = [line1, line2]
points = [point1, point2]
t, y = simulate_double_pendulum(theta1, theta2, L1, L2, m1, m2, g, t_span, num_points)
interval = t_span[1] / num_points / 2 * 100 # milliseconds per frame
new_animation = FuncAnimation(
fig7, animate_double_pendulum, frames=num_points, fargs=(t, y, L1, L2, lines, points, trace),
interval=interval, repeat=True
)
return new_animation
For completeness sake, this is the code I use to initialize the animation:
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(100.0)
theta2_initial = 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_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
)
I know this is a lot of code, but I don't seem to get the animation working when the sliders are updated. Any help would be much appreciated!