Slider changes for an animation of the double pendulum problem doesn't work

245 Views Asked by At

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!

0

There are 0 best solutions below