使用FuncAnimation在同一个窗口中叠加两个动画。

huangapple go评论74阅读模式
英文:

Overlay two animations within the same window with FuncAnimation

问题

I am writing a program which simulates the motion of the double pendulum. In order to make the impact of different initial values visible, I would like to overlay the motion of two double pendulums onto each other in a single window/graph.

However I have difficulties with putting the two animations into a single window and overlying each other. The furthest I have come is opening them in two windows in parallel time, this is done by the provided code.

Furthermore for some reason when I run the code it generates three windows instead of two, one of which is empty.

My code is as follows:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

loc1 = "DoublePendRes1.txt"
loc2 = "DoublePendRes2.txt"

def calculate_coordinates(L1, L2, angle1, angle2):
    x1 = L1 * np.sin(angle1)
    y1 = -L1 * np.cos(angle1)
    x2 = x1 + L2 * np.sin(angle2)
    y2 = y1 - L2 * np.cos(angle2)
    return x1, y1, x2, y2

def pendulumAnimation(timeLst, angle1Lst, angle2Lst, L1, L2, ax, color):
    fig = plt.figure()
    ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, xlim=-(L1 + L2), L1 + L2, ylim=-(L1 + L2), L1 + L2)
    ax.grid()
    line, = ax.plot([], [], 'o-', lw=2, color=color)
    trail, = ax.plot([], [], 'r-', alpha=0.6, linewidth=0.8, color=color)

    trail_x = []
    trail_y = []

    def init():
        line.set_data([], [])
        trail.set_data([], [])
        return line, trail

    def update(frame):
        angle1 = angle1Lst[frame]
        angle2 = angle2Lst[frame]
        x1, y1, x2, y2 = calculate_coordinates(L1, L2, angle1, angle2)
        line.set_data([0, x1, x2], [0, y1, y2])

        trail_x.append(x2)
        trail_y.append(y2)
        trail.set_data(trail_x, trail_y)

        return line, trail

    animation = FuncAnimation(fig, update, frames=len(timeLst), interval=10, init_func=init, blit=True)
    return animation

def extract_data(targetFile):
    dataSet = np.loadtxt(targetFile)

    timeData = dataSet[:, 0]
    theta1Set = dataSet[:, 1]
    theta2Set = dataSet[:, 2]

    return timeData, theta1Set, theta2Set

def animationCall():
    fig, ax = plt.subplots(figsize=(6, 6))
    ax.set_aspect('equal')
    ax.grid()

    time_evo1, theta1_1, theta2_1 = extract_data(loc1)
    animation1 = pendulumAnimation(time_evo1, theta1_1, theta2_1, 1, 1, ax, color='blue')

    time_evo2, theta1_2, theta2_2 = extract_data(loc2)
    animation2 = pendulumAnimation(time_evo2, theta1_2, theta2_2, 1, 1, ax, color='red')

    plt.show()

animationCall()

The .txt files are generated by a different program (which later will be written in C++, thus the programs are independent). Note that I still haven't double checked these double pendulum equations but for the questions sake that's irrelevant. You can generate these double pendulum data for your own with the following code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# File locations for: read/write
loc1 = "DoublePendRes2.txt"

def double_pendulum(t, y, L1, L2, m1, m2):
    theta1, theta1_dot, theta2, theta2_dot = y
    g = 9.8

    c = np.cos(theta1 - theta2)
    s = np.sin(theta1 - theta2)

    # Compute the equations of motion
    theta1_ddot = -(g * (2 * m1 + m2) * np.sin(theta1) + m2 * g * np.sin(theta1 - 2 * theta2) + 2 * s * m2 * (theta2_dot ** 2 * L2 + theta1_dot ** 2 * L1 * c)) / (L1 * (2 * m1 + m2 - m2 * c ** 2))
    theta2_ddot = (2 * s * (theta1_dot ** 2 * L1 * (m1 + m2) + g * (m1 + m2) * np.cos(theta1) + theta2_dot ** 2 * L2 * m2 * c)) / (L2 * (2 * m1 + m2 - m2 * c ** 2))

    dydt = [theta1_dot, theta1_ddot, theta2_dot, theta2_ddot]
    return dydt

# Define the Runge-Kutta-4 method
def runge_kutta_step(t, y, h, L1, L2, m1, m2):
    k1 = h * np.array(double_pendulum(t, y, L1, L2, m1, m2))
    k2 = h * np.array(double_pendulum(t + h/2, y + k1/2, L1, L2, m1, m2))
    k3 = h * np.array(double_pendulum(t + h/2, y + k2/2, L1, L2, m1, m2))
    k4 = h * np.array(double_pendulum(t + h, y + k3, L1, L2, m1, m2))

    return y + (k1 + 2*k2 + 2*k3 + k4) / 6

def solve_double_pendulum(t_span, y0, h, L1, L2, m1, m2):
    num_steps = int((t_span[1] - t_span[0]) / h)
    t_values = np.linspace(t_span[0], t_span[1], num_steps + 1)
    y_values = np.zeros((num_steps + 1, 4))
    y_values[0] = y0

    for i in range(num_steps):
        t = t_values[i]
        y = y_values[i]
        y_values[i + 1] = runge_kutta_step(t, y, h, L1, L2, m1, m2)

    return t_values, y_values

def doublePendulumCall(initLst, L1, L2, m1, m2):
    # Initial Conditions: [theta1, theta1_dot, theta2, theta2_dot]
    y0 = initL

<details>
<summary>英文:</summary>

I am writing a program which simulates the motion of the double pendulum. In order to make the impact of different initial values visible, I would like to overlay the motion of two double pendulums onto each other in a single window/graph. 

However I have **difficulties with putting the two animations into a single window and overlying each other.** The furthest I have come is opening them in two windows in parallel time, this is done by the provided code.

Furthermore for some reason when I run the code **it generates three windows instead of two, one of which is empty.**


My code is as follows:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

loc1 = "DoublePendRes1.txt"
loc2 = "DoublePendRes2.txt"

def calculate_coordinates(L1, L2, angle1, angle2):
x1 = L1 * np.sin(angle1)
y1 = -L1 * np.cos(angle1)
x2 = x1 + L2 * np.sin(angle2)
y2 = y1 - L2 * np.cos(angle2)
return x1, y1, x2, y2

def pendulumAnimation(timeLst, angle1Lst, angle2Lst, L1, L2, ax, color):
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, xlim=(-(L1 + L2), L1 + L2), ylim=(-(L1 + L2), L1 + L2))
ax.grid()
line, = ax.plot([], [], 'o-', lw=2, color=color)
trail, = ax.plot([], [], 'r-', alpha=0.6, linewidth=0.8, color=color)

trail_x = []
trail_y = []
def init():
line.set_data([], [])
trail.set_data([], [])
return line, trail
def update(frame):
angle1 = angle1Lst[frame]
angle2 = angle2Lst[frame]
x1, y1, x2, y2 = calculate_coordinates(L1, L2, angle1, angle2)
line.set_data([0, x1, x2], [0, y1, y2])
trail_x.append(x2)
trail_y.append(y2)
trail.set_data(trail_x, trail_y)
return line, trail
animation = FuncAnimation(fig, update, frames=len(timeLst), interval=10, init_func=init, blit=True)
return animation

def extract_data(targetFile):
dataSet = np.loadtxt(targetFile)

timeData = dataSet[:, 0]
theta1Set = dataSet[:, 1]
theta2Set = dataSet[:, 2]
return timeData, theta1Set, theta2Set

def animationCall():
fig, ax = plt.subplots(figsize=(6, 6))
ax.set_aspect('equal')
ax.grid()

time_evo1, theta1_1, theta2_1 = extract_data(loc1)
animation1 = pendulumAnimation(time_evo1, theta1_1, theta2_1, 1, 1, ax, color=&#39;blue&#39;)
time_evo2, theta1_2, theta2_2 = extract_data(loc2)
animation2 = pendulumAnimation(time_evo2, theta1_2, theta2_2, 1, 1, ax, color=&#39;red&#39;)
plt.show()

animationCall()


The .txt files are generated by a different program (which later will be written in C++, thus the programs are independent). Note that I still haven&#39;t double checked these double pendulum equations but for the questions sake that&#39;s irrelevant. You can generate these double pendulum data for your own with the following code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

File loactions for: read/write

loc1 = "DoublePendRes2.txt"

def double_pendulum(t, y, L1, L2, m1, m2):
theta1, theta1_dot, theta2, theta2_dot = y
g = 9.8

c = np.cos(theta1 - theta2)
s = np.sin(theta1 - theta2)
# Compute the equations of motion
theta1_ddot = -(g * (2 * m1 + m2) * np.sin(theta1) + m2 * g * np.sin(theta1 - 2 * theta2) + 2 * s * m2 * (theta2_dot ** 2 * L2 + theta1_dot ** 2 * L1 * c)) / (L1 * (2 * m1 + m2 - m2 * c ** 2))
theta2_ddot = (2 * s * (theta1_dot ** 2 * L1 * (m1 + m2) + g * (m1 + m2) * np.cos(theta1) + theta2_dot ** 2 * L2 * m2 * c)) / (L2 * (2 * m1 + m2 - m2 * c ** 2))
dydt = [theta1_dot, theta1_ddot, theta2_dot, theta2_ddot]
return dydt

Define the Runge-Kutta-4 method

def runge_kutta_step(t, y, h, L1, L2, m1, m2):
k1 = h * np.array(double_pendulum(t, y, L1, L2, m1, m2))
k2 = h * np.array(double_pendulum(t + h/2, y + k1/2, L1, L2, m1, m2))
k3 = h * np.array(double_pendulum(t + h/2, y + k2/2, L1, L2, m1, m2))
k4 = h * np.array(double_pendulum(t + h, y + k3, L1, L2, m1, m2))

return y + (k1 + 2*k2 + 2*k3 + k4) / 6

def solve_double_pendulum(t_span, y0, h, L1, L2, m1, m2):
num_steps = int((t_span1 - t_span[0]) / h)
t_values = np.linspace(t_span[0], t_span1, num_steps + 1)
y_values = np.zeros((num_steps + 1, 4))
y_values[0] = y0

for i in range(num_steps):
t = t_values[i]
y = y_values[i]
y_values[i + 1] = runge_kutta_step(t, y, h, L1, L2, m1, m2)
return t_values, y_values

def doublePendulumCall(initLst, L1, L2, m1, m2):
#Initail Conditions: [theta1, theta1_dot, theta2, theta2_dot]
y0 = initLst

# Time span for the simulation
t_span = (0, 10)
h = 0.01
# Solve the system of differential equations using RK4
t_values, y_values = solve_double_pendulum(t_span, y0, h, L1, L2, m1, m2)
# Extract the solution
theta1 = y_values[:, 0]
theta2 = y_values[:, 2]
# Write to files:
file1 = open(loc1, &quot;w&quot;)
def writeToLst(targetFile, Lst1, Lst2, Lst3):
length = len(Lst1)
if(length == len(Lst2) == len(Lst3)):
for i in range(length):
elt1, elt2, elt3 = &quot;{:.10f}&quot;.format(Lst1[i]), &quot;{:.10f}&quot;.format(Lst2[i]), &quot;{:.10f}&quot;.format(Lst3[i])
targetFile.write(f&quot;{elt1}\t{elt2}\t{elt3}\n&quot;)
print(&#39;Data stored successfully.&#39;)
else:
print(&#39;Error, ists must be of the same length.&#39;)
writeToLst(file1, t_values, theta1, theta2)

initCond1 = np.array([np.pi/2, 0, np.pi/2, 0])
doublePendulumCall(initCond1, 1, 1, 1, 1)

I would greatly appreciate your help with resolving the issues highlighted in the bold text.
</details>
# 答案1
**得分**: 1
以下是您要翻译的内容:
"For the animations to be in the same figure, they need to be created in the same call to `FuncAnimation`. What you do is make two different animations.
I would do this by storing the data in lists and zipping them to deal with each dataset. I pass those lists to the function called by `FuncAnimation` (`FuncAnimation` accepts arguments, but the documentation recommends using `functools.partial`). That way, everything is combined into one figure, as desired.
I also made some other minor changes, like using the `unpack=True` argument for `np.loadtxt` and extracting all the coordinates in one call (that's the benefit of numpy arrays).
```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from functools import partial
plt.close("all")
def calculate_coordinates(L1, L2, angle1, angle2):
x1 = L1 * np.sin(angle1)
y1 = -L1 * np.cos(angle1)
x2 = x1 + L2 * np.sin(angle2)
y2 = y1 - L2 * np.cos(angle2)
return x1, y1, x2, y2
time, theta11, theta12 = np.loadtxt("DoublePendRes1.txt",
delimiter="\t", unpack=True)
_, theta21, theta22 = np.loadtxt("DoublePendRes2.txt",
delimiter="\t", unpack=True)
x11, y11, x12, y12 = calculate_coordinates(1, 1, theta11, theta12)
x21, y21, x22, y22 = calculate_coordinates(1, 1, theta21, theta22)
data = [[x11, y11, x12, y12], [x21, y21, x22, y22]]
fig, ax = plt.subplots()
ax.set_xlim([-2.5,2.5])
ax.set_ylim([-2.5,0.5])
line1, = ax.plot([], [], color="tab:blue")
line2, = ax.plot([], [], color="tab:orange")
trail1, = ax.plot([], [], color="tab:blue", alpha=0.5)
trail2, = ax.plot([], [], color="tab:orange", alpha=0.5)
lines = [line1, line2]
trails = [trail1, trail2]
def func_full(i, lines, trails, data):
for (x1, y1, x2, y2), line, trail in zip(data, lines, trails):
line.set_data([0, x1[i], x2[i]], [0, y1[i], y2[i]])
trail.set_data(x2[i-20 if i > 20 else 0:i+1], 
y2[i-20 if i > 20 else 0:i+1])
func = partial(func_full, lines=lines, data=data, trails=trails)
anim = FuncAnimation(fig, func, frames=len(x11), interval=0)

使用FuncAnimation在同一个窗口中叠加两个动画。

As for why you had three figures, it's because you made one at the beginning of animationCall and two more when you called pendulumAnimation for each pendulum."

英文:

For the animations to be in the same figure, they need to be created in the same call to FuncAnimation. What you do is make two different animations.

I would do this by storing the data in lists and zipping them to deal with each dataset. I pass those lists to the function called by FuncAnimation (FuncAnimation accepts arguments, but the documentation recommends using functools.partial). That way, everything is combined into one figure, as desired.

I also made some other minor changes, like using the unpack=True argument for np.loadtxt and extracting all the coordinates in one call (that's the benefit of numpy arrays).

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from functools import partial

plt.close(&quot;all&quot;)


def calculate_coordinates(L1, L2, angle1, angle2):
    x1 = L1 * np.sin(angle1)
    y1 = -L1 * np.cos(angle1)
    x2 = x1 + L2 * np.sin(angle2)
    y2 = y1 - L2 * np.cos(angle2)
    return x1, y1, x2, y2


time, theta11, theta12 = np.loadtxt(&quot;DoublePendRes1.txt&quot;,
                                    delimiter=&quot;\t&quot;, unpack=True)
_, theta21, theta22 = np.loadtxt(&quot;DoublePendRes2.txt&quot;,
                                 delimiter=&quot;\t&quot;, unpack=True)

x11, y11, x12, y12 = calculate_coordinates(1, 1, theta11, theta12)
x21, y21, x22, y22 = calculate_coordinates(1, 1, theta21, theta22)

data = [[x11, y11, x12, y12], [x21, y21, x22, y22]]

fig, ax = plt.subplots()
ax.set_xlim([-2.5,2.5])
ax.set_ylim([-2.5,0.5])
line1, = ax.plot([], [], color=&quot;tab:blue&quot;)
line2, = ax.plot([], [], color=&quot;tab:orange&quot;)
trail1, = ax.plot([], [], color=&quot;tab:blue&quot;, alpha=0.5)
trail2, = ax.plot([], [], color=&quot;tab:orange&quot;, alpha=0.5)
lines = [line1, line2]
trails = [trail1, trail2]

def func_full(i, lines, trails, data):
    for (x1, y1, x2, y2), line, trail in zip(data, lines, trails):
        line.set_data([0, x1[i], x2[i]], [0, y1[i], y2[i]])
        trail.set_data(x2[i-20 if i &gt; 20 else 0:i+1], 
                       y2[i-20 if i &gt; 20 else 0:i+1])
        
func = partial(func_full, lines=lines, data=data, trails=trails)
anim = FuncAnimation(fig, func, frames=len(x11), interval=0)

使用FuncAnimation在同一个窗口中叠加两个动画。

As for why you had three figures, it's because you made one at the beginning of animationCall and two more when you called pendulumAnimation for each pendulum.

huangapple
  • 本文由 发表于 2023年6月30日 02:49:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76583868.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定