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 1L2 = 1.0 # Length of pendulum 2m1 = 1.0 # Mass of pendulum 1m2 = 1.0 # Mass of pendulum 2g = 9.81 # Gravitational acceleration# Initial conditionstheta1_initial = np.radians(60.0)theta2_initial = np.radians(5.0)# Time span and number of points for simulationt_span = (0, 20)num_points = 1000t, 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 tracefig7, 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 tracelines = [line1, line2]points = [point1, point2]s_theta1, s_theta2 = get_sliders_double_pendulum()# Animation parametersinterval = t_span[1] / num_points / 2 * 100 # milliseconds per frameanimation = 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_theta2def 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 notebookimport numpy as npimport matplotlib.pyplot as pltimport sympy as spfrom scipy.integrate import solve_ivpfrom matplotlib.animation import FuncAnimationfrom ipywidgets import HBox, VBox, Layout, widgetsfrom IPython.display import displayfrom sympy import symbols, solve, Matrixfrom matplotlib import colorsfrom matplotlib.patches import FancyArrowPatchimport warningswarnings.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.ydef 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_theta2def 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 1L2 = 1.0 # Length of pendulum 2m1 = 1.0 # Mass of pendulum 1m2 = 1.0 # Mass of pendulum 2g = 9.81 # Gravitational acceleration# Initial conditionstheta1 = np.radians(100.0)theta2 = np.radians(45.0)# Time span and number of points for simulationt_span = (0, 20)num_points = 1000t, 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 tracefig7, 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 tracelines = [line1, line2]points = [point1, point2]s_theta1, s_theta2 = get_sliders_double_pendulum()# Animation parametersinterval = t_span[1] / num_points / 2 * 100 # milliseconds per frameanimation = 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.