Now, here is the code I'm working with:
import numpy
from matplotlib import pyplot as plt
import time, sys
nx = 41
dx = 2 / (nx-1)
nt = 25
dt = 0.025
c = 1
fig = plt.figure()
u = numpy.ones(nx)
u[int(.5 / dx):int(1 / dx + 1)] = 2
print(u)
un = numpy.ones(nx)
for n in range(nt):
un = u.copy()
plt.plot(numpy.linspace(0, 2, nx), u)
for i in range(1, nx):
u[i] = un[i] - c*dt/dx * (un[i] - un[i - 1])
plt.show()
It should animate the solution to the equation ∂u/∂t + c * ∂u/∂x = 0; but I don't know how to animate it - because at the current state, it shows at once the function at all time steps; and if instead I put plt.show() inside the loop (the outer one), it shows the graphs one at a time, and I have to close the graph window to see the next, which is not very convenient.
FuncAnimation can be used to create animations.
The code of the post can be rendered as an animation as follows:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
nx = 41
dx = 2 / (nx-1)
nt = 25
dt = 0.025
c = 1
fig = plt.figure()
u = np.ones(nx)
u[int(.5 / dx):int(1 / dx + 1)] = 2
x = np.linspace(0, 2, nx)
plot1, = plt.plot(x, u)
def update(t):
un = u.copy()
for i in range(1, nx):
u[i] = un[i] - c*dt/dx * (un[i] - un[i - 1])
plot1.set_ydata(u)
return plot1,
FuncAnimation(fig, update, frames=nt, blit=True)
plt.show()
PS: Note the comma after plot1 in plot1, = plt.plot(.... This grabs the first element in the list returned by plt.plot.
You could save it as a gif using the following:
import numpy
from matplotlib import pyplot as plt
import time, sys
from celluloid import Camera
from IPython.display import Image
nx = 41
dx = 2 / (nx-1)
nt = 25
dt = 0.025
c = 1
fig = plt.figure()
u = numpy.ones(nx)
u[int(.5 / dx):int(1 / dx + 1)] = 2
print(u)
un = numpy.ones(nx)
fig = plt.figure()
camera = Camera(fig)
for n in range(nt):
un = u.copy()
plt.plot(numpy.linspace(0, 2, nx), u, color= "blue")
for i in range(1, nx):
u[i] = un[i] - c*dt/dx * (un[i] - un[i - 1])
camera.snap()
animation = camera.animate()
animation.save('solution.gif', writer = 'imagemagick')
In essence it recursively takes a camera "snap" for each dt and collates them into a gif saved as "solution.gif" in the current working directory.
Related
I want to create an animation of a moving sphere in matplotlib. For some reason it isnt working:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from matplotlib import cm
from matplotlib import animation
import pandas as pd
fig = plt.figure(facecolor='black')
ax = plt.axes(projection = "3d")
u = np.linspace(0, 2*np.pi, 100)
v = np.linspace(0, np.pi, 100)
r = 4
ax.set_xlim(0, 60)
ax.set_ylim(0, 60)
ax.set_zlim(0, 60)
x0 = r * np.outer(np.cos(u), np.sin(v)) + 10
y0 = r * np.outer(np.sin(u), np.sin(v)) + 10
z0 = r * np.outer(np.ones(np.size(u)), np.cos(v)) + 50
def init():
ax.plot_surface(x0,y0,z0)
return fig,
def animate(i):
ax.plot_surface(x0 + 1, y0 + 1, z0 + 1)
return fig,
ani = animation. FuncAnimation(fig, animate, init_func = init, frames = 90, interval = 300)
plt.show()
Here, I have attempted to move the sphere by (1,1,1) in each new iteration, but it fails to do so.
There are a couple of mistakes with your approach:
In your animate function you are adding a sphere at each iteration. Unfortunately, Poly3DCollection objects (created by ax.plot_surface) cannot be modified after they have been created, hence to animate a surface we need to remove the surface of the previous iteration and add a new one.
In your animation the sphere didn't move because at each iteration you were adding a new sphere at the same location as the previous one.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from matplotlib import cm
from matplotlib import animation
import pandas as pd
fig = plt.figure(facecolor='black')
ax = plt.axes(projection = "3d")
u = np.linspace(0, 2*np.pi, 100)
v = np.linspace(0, np.pi, 100)
r = 4
ax.set_xlim(0, 60)
ax.set_ylim(0, 60)
ax.set_zlim(0, 60)
x0 = r * np.outer(np.cos(u), np.sin(v)) + 10
y0 = r * np.outer(np.sin(u), np.sin(v)) + 10
z0 = r * np.outer(np.ones(np.size(u)), np.cos(v)) + 50
surface_color = "tab:blue"
def init():
ax.plot_surface(x0, y0, z0, color=surface_color)
return fig,
def animate(i):
# remove previous collections
ax.collections.clear()
# add the new sphere
ax.plot_surface(x0 + i, y0 + i, z0 + i, color=surface_color)
return fig,
ani = animation. FuncAnimation(fig, animate, init_func = init, frames = 90, interval = 300)
plt.show()
Trying to plot two separate animations, i.e. in different windows as separate figures. Running this code for me rightly creates two windows, but animates the data on the second figure at the same time. Closing figure 1 results in only the intended data for figure 2 being animated, removing the overlap from the data intended for figure 1. Closing figure 2 results in only the intended data for figure 1 being animated, removing the overlap from the data intended for figure 2.
Minimum code below:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
dx, dv, N, Nb, decp = 2, 1.5, 100, 12, int(1)
Pd = np.zeros([N + 1, 2 * Nb])
Vd = np.zeros([N + 1, 2 * Nb])
Pd[:, 1] = 4
Vd[:, 3] = 2
t = np.zeros(N + 1)
t[0] = 0
for i in range(0, N):
t[i + 1] = (i + 1) * 0.1
Px = []
for i in range(0, (2 * Nb)):
PX = dx * (-Nb + i) / 4
Px.append(PX)
lblx = []
for i in range(0, int((Nb / 2) + 1)):
if i == (Nb / 4):
LBL = r"$\mu_x$"
lblx.append(LBL)
else:
LBL = r"${0}\sigma_x$".format(-(Nb / 4) + i)
lblx.append(LBL)
Pv = []
for i in range(0, (2 * Nb)):
PV = dv * (-Nb + i) / 4
Pv.append(PV)
lblv = []
for i in range(0, int((Nb / 2) + 1)):
if i == (Nb / 4):
LBL = r"$\mu_v$"
lblv.append(LBL)
else:
LBL = r"${0}\sigma_v$".format(-(Nb / 4) + i)
lblv.append(LBL)
fig1 = plt.figure(figsize=(8,6))
def animatex(i):
fig1.clear()
plt.bar(Px, Pd[i, :], width = dx / 4, align = 'edge', color = 'b', \
label = 't = {} seconds'.format(round(t[i], decp)))
s_ticks = np.arange(-3 * dx, (3 + 1) * dx, dx)
plt.xticks(s_ticks, lblx)
plt.ylim(0, np.max(Pd))
plt.xlim(-3 * dx, 3 * dx)
plt.legend()
plt.draw()
anix = FuncAnimation(fig1, animatex, repeat = True, interval = 200, frames = N + 1)
fig2 = plt.figure(figsize=(8,6))
def animatev(i):
fig2.clear()
plt.bar(Pv, Vd[i, :], width = dv / 4, align = 'edge', color = 'b', \
label = 't = {} seconds'.format(round(t[i], decp)))
s_ticks = np.arange(-3 * dv, (3 + 1) * dv, dv)
plt.xticks(s_ticks, lblv)
plt.ylim(0, np.max(Vd))
plt.xlim(-3 * dv, 3 * dv)
plt.legend()
plt.draw()
aniv = FuncAnimation(fig2, animatev, repeat = True, interval = 200, frames = N + 1)
plt.show()
As is probably clear, they are two bar plots, with different vertical and horizontal dimensions. I've seen some solutions for these kinds of problems where the data shares an axis through a shared variable, but here they are not (as can be seen).
For this minimum code, the solution involves having the two bars, one in Pd and the other in Vd, being on their respective intended figures, not both on the second figure.
Let me know if there are any issues with the information here i.e. minimal code requirements not met, more information etc. and I will update.
Ignore any wayward writing style, it is not relevant.
Simplifying your code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
dx, dv, N, Nb, decp = 2, 1.5, 10, 12, int(1)
Px = np.arange(Nb)
Pd = np.random.randn(N, Nb)
Vd = np.random.randn(N, Nb)
fig1, ax1 = plt.subplots(figsize=(8, 6))
def animatex(i):
ax1.clear()
ax1.bar(Px, Pd[i, :], width=dx / 4, align='edge', color='b')
anix = FuncAnimation(fig1, animatex, repeat=True, interval=200, frames=N)
fig2, ax2 = plt.subplots(figsize=(8, 6))
def animatev(i):
ax2.clear()
ax2.bar(Px, Vd[i, :], width = dv / 4, align='edge', color='b')
aniv = FuncAnimation(fig2, animatev, repeat=True, interval=200, frames=N)
plt.show()
works fine for me. You can add the esthetic/data details back in...
import numpy as np
import matplotlib.pyplot as plot
from IPython.display import HTML
from matplotlib import animation
#setup fig with axis
fig, ax = plot.subplots(figsize=(8,8))
#set axis limits
ax.set(xlim=(-2,2), ylim=(0,600), xlabel="position, metres", ylabel="height, metres", title="falling apple")
#initial params
T = 100.
m = 3
g = 9.81
v0x = 10
H = 553.
#setting calc interval
dt = 0.1
N = int(T/dt)
#arrays
v = np.zeros((N+1 , 2))
x = np.zeros((N+1 , 2))
f = np.zeros((N+1 , 2))
#array start [x ,y] format
v[0] = np.array([0. , H])
x[0] = np.array([v0x , 0.])
# the only force is gravity
f[:] = np.array([0., m * g])
#running the dynamics sim
for n in range(N):
v[n+1] = v[n] + ((f[n]/m) * dt)
x[n+1] = x[n] + (v[n+1] * dt)
#scatter plot
scat_plt = ax.scatter(x[0,0], x[0,1], marker='o', c='#1f77b4', s=200)
## animating
def animate(i):
scat_plt.set_offsets(x[i])
ani = animation.FuncAnimation(fig, func=animate, frames=N)
ani.save('ball.html', writer=animation.HTMLWriter(fps= 1//dt))
plot.close()
ani.save('ball.mp4', fps= 1//dt)
HTML('ball.html')
The out put is just a circle going straight up where as this is supposed to simulate a ball being thrown horizontally off a tower
It would be highly appreciated if someone could suggest any changes to be made to the logic/physics or the code.
Thank you!!
I think you mixed x with v at some point. Also the force should be negative in y. I tried this and it seems to work:
import numpy as np
import matplotlib.pyplot as plot
from IPython.display import HTML
from matplotlib import animation
#setup fig with axis
fig, ax = plot.subplots(figsize=(8,8))
#set axis limits
ax.set(xlim=(-200,200), ylim=(0,600), xlabel="position, metres", ylabel="height, metres", title="falling apple")
#initial params
T = 100.
m = 3
g = 9.81
v0x = 10
H = 553.
#setting calc interval
dt = 0.1
N = int(T/dt)
#arrays
v = np.zeros((N+1 , 2))
x = np.zeros((N+1 , 2))
f = np.zeros((N+1 , 2))
#array start [x ,y] format
x[0] = np.array([0. , H])
v[0] = np.array([v0x , 0.])
# the only force is gravity
f[:] = np.array([0., -m * g])
#running the dynamics sim
for n in range(N):
v[n+1] = v[n] + ((f[n]/m) * dt)
x[n+1] = x[n] + (v[n+1] * dt)
#scatter plot
scat_plt = ax.scatter(x[0,0], x[0,1], marker='o', c='#1f77b4', s=200)
## animating
def animate(i):
scat_plt.set_offsets(x[i])
ani = animation.FuncAnimation(fig, func=animate, frames=N)
ani.save('ball.html', writer=animation.HTMLWriter(fps= 1//dt))
plot.close()
ani.save('ball.gif', fps= 1//dt)
HTML('ball.html')
I would like to plot a graph like the one in the following picture:
I wrote the following code that plots a wave graph for each time step.
import matplotlib.pyplot as plt
import numpy as np
def u_0(x):
a = 1.0/np.cosh(2.0*(x+8.0))
b = 1.0/np.cosh((x+1.0))
return 8.0*a*a+2.0*b*b
#spatial grid
N = 100
x = np.linspace(-10,10,N)
#time
Nt = 100
tlist = np.linspace(0.0,2.0,Nt)
#velocity
c = 5.0
count = 0
for t in tlist:
u = u_0(x-c*t)
plt.figure()
plt.plot(x,u)
plt.savefig(str(count))
count = count+1
plt.close()
How can I join these pictures together and get a graph like the one in the picture?
Is there a standard way to do this?
Don't close plot and draw all on one image.
Every plot would need some offset for Y values
u += count # offset
Code
import matplotlib.pyplot as plt
import numpy as np
def u_0(x):
a = 1.0/np.cosh(2.0*(x+8.0))
b = 1.0/np.cosh((x+1.0))
return 8.0*a*a + 2.0*b*b
# spatial grid
N = 100
x = np.linspace(-10, 10, N)
# time
Nt = 100
tlist = np.linspace(0.0, 2.0, Nt)
#velocity
c = 5.0
count = 0
for t in tlist:
u = u_0(x-c*t)
u += count # offset
plt.plot(x, u)
count += 1
plt.savefig("result.png")
Image:
EDIT: Something similar in 3D
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # need for `projection=`
import numpy as np
def u_0(x):
a = 1.0/np.cosh(2.0*(x+8.0))
b = 1.0/np.cosh((x+1.0))
return 8.0*a*a + 2.0*b*b
#velocity
c = 5.0
#spatial grid
N = 30
x = np.linspace(-10, 10, N)
t = np.linspace(0.0, 2.0, N)
X, T = np.meshgrid(x, t)
Y = u_0(X-c*T)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(X, T, Y)
plt.show()
plt.savefig('result.png')
I have to make an animation of a large number (~90,000) figures. For context, it's a plot of a map for every day from 1700 - 1950, with events of interest marked on relevent days.
I can do this using matplotlib.animation.FuncAnimation, and I have code that does this successfully for a small test period. However, with the complete set of figures this is taking an impractical amount of time to render and will result in a very large movie file.
I have read that apparently moviepy offers both speed and file size advantages. However, I am having trouble getting this to work – I believe my problem is that I have not understood how to correctly set the duration and fps arguments.
A simplified version of my code is :
import numpy as np
import matplotlib.pyplot as plt
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy
fig = plt.figure()
ax = plt.axes()
x = np.random.randn(10,1)
y = np.random.randn(10,1)
p = plt.plot(x,y,'ko')
time = np.arange(2341973,2342373)
def animate(i):
xn = x+np.sin(2*np.pi*time[i]/10.0)
yn = y+np.cos(2*np.pi*time[i]/8.0)
p[0].set_data(xn,yn)
return mplfig_to_npimage(fig)
fps = 1
duration = len(time)
animation = mpy.VideoClip(animate, duration=duration)
animation.write_videofile("test.mp4", fps=fps)
However, this does not produce the intended result of producing a movie with one frame for each element of time and saving this to an .mp4. I can’t see where I have gone wrong, any help or pointers would be appreciated.
Best wishes,
Luke
Same solution as JuniorCompressor, with just one frame kept in memory to avoid RAM issues. This example runs in 30 seconds on my machine and produces a good quality 400-second clip of 6000 frames, weighing 600k.
import numpy as np
import matplotlib.pyplot as plt
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy
fig = plt.figure(facecolor="white") # <- ADDED FACECOLOR FOR WHITE BACKGROUND
ax = plt.axes()
x = np.random.randn(10, 1)
y = np.random.randn(10, 1)
p = plt.plot(x, y, 'ko')
time = np.arange(2341973, 2342373)
last_i = None
last_frame = None
def animate(t):
global last_i, last_frame
i = int(t)
if i == last_i:
return last_frame
xn = x + np.sin(2 * np.pi * time[i] / 10.0)
yn = y + np.cos(2 * np.pi * time[i] / 8.0)
p[0].set_data(xn, yn)
last_i = i
last_frame = mplfig_to_npimage(fig)
return last_frame
duration = len(time)
fps = 15
animation = mpy.VideoClip(animate, duration=duration)
animation.write_videofile("test.mp4", fps=fps)
On a sidenote, there is dedicated class of videoclips called DataVideoClip for precisely this purpose, which looks much more like matplotlib's animate. For the moment it's not really speed-efficient (I didn't include that little memoizing trick above). Here is how it works:
from moviepy.video.VideoClip import DataVideoClip
def data_to_frame(time):
xn = x + np.sin(2 * np.pi * time / 10.0)
yn = y + np.cos(2 * np.pi * time / 8.0)
p[0].set_data(xn, yn)
return mplfig_to_npimage(fig)
times = np.arange(2341973, 2342373)
clip = DataVideoClip(times, data_to_frame, fps=1) # one plot per second
#final animation is 15 fps, but still displays 1 plot per second
animation.write_videofile("test2.mp4", fps=15)
Same observations:
In animate a float number will be passed
One frame per second may cause playback problems in many players. It's better to use a bigger frame rate like 15 fps.
Using 15 fps will need many frames. It's better to use caching.
So you can do the following:
import numpy as np
import matplotlib.pyplot as plt
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy
fig = plt.figure()
ax = plt.axes()
x = np.random.randn(10, 1)
y = np.random.randn(10, 1)
p = plt.plot(x, y, 'ko')
time = np.arange(2341973, 2342373)
cache = {}
def animate(t):
i = int(t)
if i in cache:
return cache[i]
xn = x + np.sin(2 * np.pi * time[i] / 10.0)
yn = y + np.cos(2 * np.pi * time[i] / 8.0)
p[0].set_data(xn, yn)
cache.clear()
cache[i] = mplfig_to_npimage(fig)
return cache[i]
duration = len(time)
fps = 15
animation = mpy.VideoClip(animate, duration=duration)
animation.write_videofile("test.mp4", fps=fps)