英文:
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='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 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, "w")
def writeToLst(targetFile, Lst1, Lst2, Lst3):
length = len(Lst1)
if(length == len(Lst2) == len(Lst3)):
for i in range(length):
elt1, elt2, elt3 = "{:.10f}".format(Lst1[i]), "{:.10f}".format(Lst2[i]), "{:.10f}".format(Lst3[i])
targetFile.write(f"{elt1}\t{elt2}\t{elt3}\n")
print('Data stored successfully.')
else:
print('Error, ists must be of the same length.')
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)
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("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)
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论