Python 弹丸直线动画
Python Animation of projectile giving straight line
目标是 animate/simulate 洒水器。基本思想是在一个实例中创建多个液滴,每个液滴以不同的角度离开洒水器。
然而,我得到的不是一个移动的点,而是一条直线,静态线。我只用一个或两个 drops/points 尝试了相同的代码,但我仍然得到相同的直线。起初我以为是因为我在动画中包含了拖动(在之前的试验中,当我包含拖动时我得到了 strange/unexpected 结果,所以我希望这是同样的问题)
下面是我写的代码。
编辑:dragx 和 dragy 不再设置为相同的值。初始速度是在更新函数之外计算的。
每次增加指数时都会重新计算阻力,因为阻力取决于速度。
进口现在也包括在内。
我现在也尝试使用散点图而不是直线,这在原点处给了我一个点。从那时起我又回到了线
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
from math import sin, cos
import matplotlib.axes as axess
#pressure in system
def pressure(pump):
return 10.197*pump*10**5/998.2
#Initial velocity based on the pressure of the system
def vinit(init_head):
return (2*9.81*init_head)**0.5
# Number of droplets per iteration
n = 2
v0 = vinit(pressure(5.8))
cd = 0.5 #drag coefficient of a sphere;
rho = 1.225 #density of air; kg/m^3
fps = 20
runtime = 1*20
numframes = fps*runtime
time = np.linspace(0,runtime,numframes)
#Initialize droplets
droplets = np.zeros(numframes, dtype = [('position', float, 2),
('velocity', float, 2),
('force', float, 2),
('angle',float, 1),
('radius', float, 1)])
droplets['radius'] = np.random.normal(0.0045, 0.001, numframes)
A = 4*np.pi*droplets['radius']**2
mass = ((4*np.pi*droplets['radius']**3)/3)*1000 #mass of waterdroplet
drag = np.zeros(numframes,dtype = [('dragx', float, 1),('dragy', float, 1)])
rads = np.radians(np.random.normal(37,1,numframes))
droplets['angle'] = rads
droplets['force'][:,1] = -9.8*mass
#Initialize velocity
for i in range(0, numframes):
droplets['velocity'][i,0] = v0*cos(droplets['angle'][i])
droplets['velocity'][i,1] = v0*sin(droplets['angle'][i])
#Initialize the drag
drag['dragx'] = -0.5*rho*cd*A*droplets['velocity'][:,0]
drag['dragy'] = -0.5*rho*cd*A*droplets['velocity'][:,1]
droplets['position'][:,0] = 0
#Initialize the figure
fig,ax = plt.subplots()
axess.Axes.set_frame_on(ax,True)
ax.autoscale(True)
ax.set_xlim(0)
ax.set_ylim(0)
line = ax.plot(droplets['position'][0, 0], droplets['position'][0, 1],'b.')[0]
line = ax.plot(droplets['position'][:, 0], droplets['position'][:, 1],'b.')[0]
xdata = [droplets['position'][0, 0]]
ydata = [droplets['position'][0, 1]]
ln = plt.plot([],[],'b')[0]
def initfunc():
droplets['position'][0,:] = 0
ln.set_data(droplets['position'][0, 0], droplets['position'][0, 1])
return ln
def update(framenum):
index = framenum
cd = 0.5 #drag coefficient of a sphere;
rho = 1.225 #density of air; kg/m^3
A = 4*np.pi*droplets['radius']**2 #surface area of droplet; m^2
mass = ((4*np.pi*droplets['radius']**3)/3)*1000 #mass of waterdroplet
#Update the drag force on the droplet
drag['dragx'] = -0.5*rho*cd*A*droplets['velocity'][:,0]
drag['dragy'] = -0.5*rho*cd*A*droplets['velocity'][:,1]
droplets['force'][index,0] += drag['dragx'][index]
droplets['force'][index,1] += drag['dragy'][index]
#droplets['position'][0] = [0,0]
droplets['velocity'][index,0] = droplets['velocity'][index,0] + (droplets['force'][index,0]/mass[index])*index
droplets['velocity'][index,1] = droplets['velocity'][index,1] + (droplets['force'][index,1]/mass[index])*index
droplets['position'][index,0] = droplets['position'][index,0] + (droplets['velocity'][index,0])*index
droplets['position'][index,1] = droplets['position'][index,1] + (droplets['velocity'][index,1])*index
xdata.append(droplets['position'][index,0])
ydata.append(droplets['position'][index,1])
ln.set_data(xdata,ydata)
line.set_data(droplets['position'][:,0], droplets['position'][:,1])
return ln
sprink = animation.FuncAnimation(fig, update,init_func = initfunc,interval= 200, frames = numframes)
plt.show()
sprink.save('Sprinkler.mp4', fps = fps)
除了 @JohanC 指出的错误之外,您还使用了 表面积 (4 * pi * r^2
) 而不是 水滴(近似为球体)的横截面积 (pi * r^2
)。此外,您似乎在尝试使用假设为真空的运动方程,当添加空气阻力或其他非恒定力时,这些方程会失效。
我建议您阅读 以获得关于计算具有空气阻力的弹道射弹运动的更完整解释,但这里是一个简化版本。
计算粒子在介质中运动的位置本质上就是求解一个first order ODE,有很多方法可以数值求解这样的方程。最简单(尽管最不稳健)的方法是将问题分解成小段并近似从当前点到下一个点的变化。对于这个问题,假设您使用足够小的时间步长,这种方法效果很好。
对于你的情况,我们可以这样做
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
from math import sin, cos
g = 9.81
rho = 1.225
v0 = 50.0 # This can be initialized as desired, I chose 50 m/s for demonstration
C = 0.5
nframes = 100
ndrops = 10
tend = 8.0
time = np.linspace(0.1, tend, nframes) # Runtime of 4s chosen arbitrarily
dt = time[1] - time[0] # change in time between steps
maxs = [0.0, 0.0]
r = [0.001, 0.0045] # This stores the range of radii to be used
# I have implemented a class to store the drops for brevity
class drop:
def __init__(self, pos, vel, r):
self.pos = pos
self.vel = vel
self.r = r
class sprinkler:
# I implemented this as a class to simplify updating the scatter plot
def __init__(self):
self.fig, self.ax = plt.subplots()
self.drops = [None]*ndrops
self.maxt = 0.0
## This step is simply to figure out how far the water drops can travel
## in order to set the bounds of the plot accordingly
angles = np.linspace(0, np.pi/2, 90) # We only need to sample from 0 to pi/2
for angle in angles:
m = [drop([0.0, 0.1], [v0*cos(angle), v0*sin(angle)], 0.001),
drop([0.0, 0.1], [v0*cos(angle), v0*sin(angle)], 0.0045)]
for d in m:
t = 0.0
coef = - 0.5*C*np.pi*d.r**2*rho
mass = 4/3*np.pi*d.r**3*1000
while d.pos[1] > 0:
a = np.power(d.vel, 2) * coef * np.sign(d.vel)/mass
a[1] -= g
d.pos += (d.vel + a * dt) * dt
d.vel += a * dt
t += dt
if d.pos[1] > maxs[1]:
maxs[1] = d.pos[1]
if d.pos[0] > maxs[0]:
maxs[0] = d.pos[0]
if d.pos[1] < 0.0:
if t > self.maxt:
self.maxt = t
break
for ii in range(ndrops): # Make some randomly distributed water drops
self.drops[ii] = drop([0.0, 0.0], [cos(np.random.random()*np.pi)*v0,
sin(np.random.random()*np.pi)*v0],
np.random.random()*(r[1]-r[0]) + r[0])
anim = animation.FuncAnimation(self.fig, self.update, init_func=self.setup,
interval=200, frames=nframes)
anim.save('Sprinkler.gif', fps = 20, writer='imagemagick')
def setup(self):
self.scat = self.ax.scatter([d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops], marker='.', color='k')
self.ax.set_xlim([-maxs[0]*1.15, maxs[0]*1.15])
self.ax.set_ylim([0, maxs[1]*1.15])
return self.scat,
def update(self,frame): # Use set_offsets to move the water drops
self.step() # Advance to the next 'step'
self.scat.set_offsets(np.stack([[d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops]], 1))
return self.scat,
def step(self):
for ii in range(ndrops):
coef = - 0.5*C*np.pi*self.drops[ii].r**2*rho # Aggregated coefficient
mass = 4/3*np.pi*self.drops[ii].r**3*1000
a = np.power(self.drops[ii].vel, 2)*coef * np.sign(self.drops[ii].vel)/mass
a[1] -= g
# Approximate how much the position and velocity would change if we assume
# a(t) does not change between t and t+dt
self.drops[ii].pos += np.array(self.drops[ii].vel) * dt + 0.5 * a * dt**2
self.drops[ii].vel += a * dt
if self.drops[ii].pos[1] < 0.0: # Check if the drop has hit the ground
self.drops[ii].pos[1] = 0.0
self.drops[ii].vel = [0.0, 0.0]
sprinkler()
这可能不会产生您想要的结果,但它正确地模拟了水滴的运动。
将水滴实现为 class 并将它们存储在列表中,就像我所做的那样,只需修改 update
函数(为简单起见添加了 create
方法):
def update(self,frame): # Use set_offsets to move the water drops
# Create between 0 and ndrops new drops, but only until tend-maxt
if time[frame] < tend - self.maxt*1.1:
self.create(np.random.randint(0, ndrops))
self.step() # Advance to the next 'step'
self.scat.set_offsets(np.stack([[d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops]], 1))
return self.scat,
def create(self, i):
for ii in range(i):
self.drops.append(drop([0.0, 0.0], [cos(np.random.random()*np.pi)*v0,
sin(np.random.random()*np.pi)*v0],
np.random.random()*(r[1]-r[0]) + r[0]))
编辑 - 制作第二个动画的完整代码
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
from math import sin, cos
g = 9.81
rho = 1.225
v0 = 50.0 # This can be initialized as desired, I chose 50 m/s for demonstration
C = 0.5
nframes = 100
ndrops = 10
tend = 8.0
time = np.linspace(0.1, tend, nframes) # Runtime of 4s chosen arbitrarily
dt = time[1] - time[0] # change in time between steps
maxs = [0.0, 0.0]
r = [0.001, 0.0045] # This stores the range of radii to be used
# I have implemented a class to store the drops for brevity
class drop:
def __init__(self, pos, vel, r):
self.pos = pos
self.vel = vel
self.r = r
class sprinkler:
# I implemented this as a class to simplify updating the scatter plot
def __init__(self):
self.fig, self.ax = plt.subplots()
self.drops = [None]*ndrops
self.maxt = 0.0
## This step is simply to figure out how far the water drops can travel
## in order to set the bounds of the plot accordingly
angles = np.linspace(0, np.pi/2, 90) # We only need to sample from 0 to pi/2
for angle in angles:
m = [drop([0.0, 0.1], [v0*cos(angle), v0*sin(angle)], 0.001),
drop([0.0, 0.1], [v0*cos(angle), v0*sin(angle)], 0.0045)]
for d in m:
t = 0.0
coef = - 0.5*C*np.pi*d.r**2*rho
mass = 4/3*np.pi*d.r**3*1000
while d.pos[1] > 0:
a = np.power(d.vel, 2) * coef * np.sign(d.vel)/mass
a[1] -= g
d.pos += (d.vel + a * dt) * dt
d.vel += a * dt
t += dt
if d.pos[1] > maxs[1]:
maxs[1] = d.pos[1]
if d.pos[0] > maxs[0]:
maxs[0] = d.pos[0]
if d.pos[1] < 0.0:
if t > self.maxt:
self.maxt = t
break
for ii in range(ndrops): # Make some randomly distributed water drops
self.drops[ii] = drop([0.0, 0.0], [cos(np.random.random()*np.pi)*v0,
sin(np.random.random()*np.pi)*v0],
np.random.random()*(r[1]-r[0]) + r[0])
anim = animation.FuncAnimation(self.fig, self.update, init_func=self.setup,
interval=200, frames=nframes)
anim.save('Sprinkler.gif', fps = 20, writer='imagemagick')
def setup(self):
self.scat = self.ax.scatter([d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops], marker='.', color='k')
self.ax.set_xlim([-maxs[0]*1.15, maxs[0]*1.15])
self.ax.set_ylim([0, maxs[1]*1.15])
return self.scat,
def update(self,frame): # Use set_offsets to move the water drops
# Create between 0 and ndrops new drops, but only until tend-maxt
if time[frame] < tend - self.maxt*1.1:
self.create(np.random.randint(0, ndrops))
self.step() # Advance to the next 'step'
self.scat.set_offsets(np.stack([[d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops]], 1))
return self.scat,
def create(self, i):
for ii in range(i):
self.drops.append(drop([0.0, 0.0], [cos(np.random.random()*np.pi)*v0,
sin(np.random.random()*np.pi)*v0],
np.random.random()*(r[1]-r[0]) + r[0]))
def step(self):
for ii in range(len(self.drops)):
coef = - 0.5*C*np.pi*self.drops[ii].r**2*rho # Aggregated coefficient
mass = 4/3*np.pi*self.drops[ii].r**3*1000
a = np.power(self.drops[ii].vel, 2) * coef * np.sign(self.drops[ii].vel)/mass
a[1] -= g
# Approximate how much the position and velocity would change if we assume
# a(t) does not change between t and t+dt
self.drops[ii].pos += np.array(self.drops[ii].vel) * dt + 0.5 * a * dt**2
self.drops[ii].vel += a * dt
if self.drops[ii].pos[1] < 0.0: # Check if the drop has hit the ground
self.drops[ii].pos[1] = 0.0
self.drops[ii].vel = [0.0, 0.0]
sprinkler()
目标是 animate/simulate 洒水器。基本思想是在一个实例中创建多个液滴,每个液滴以不同的角度离开洒水器。 然而,我得到的不是一个移动的点,而是一条直线,静态线。我只用一个或两个 drops/points 尝试了相同的代码,但我仍然得到相同的直线。起初我以为是因为我在动画中包含了拖动(在之前的试验中,当我包含拖动时我得到了 strange/unexpected 结果,所以我希望这是同样的问题)
下面是我写的代码。
编辑:dragx 和 dragy 不再设置为相同的值。初始速度是在更新函数之外计算的。
每次增加指数时都会重新计算阻力,因为阻力取决于速度。
进口现在也包括在内。
我现在也尝试使用散点图而不是直线,这在原点处给了我一个点。从那时起我又回到了线
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
from math import sin, cos
import matplotlib.axes as axess
#pressure in system
def pressure(pump):
return 10.197*pump*10**5/998.2
#Initial velocity based on the pressure of the system
def vinit(init_head):
return (2*9.81*init_head)**0.5
# Number of droplets per iteration
n = 2
v0 = vinit(pressure(5.8))
cd = 0.5 #drag coefficient of a sphere;
rho = 1.225 #density of air; kg/m^3
fps = 20
runtime = 1*20
numframes = fps*runtime
time = np.linspace(0,runtime,numframes)
#Initialize droplets
droplets = np.zeros(numframes, dtype = [('position', float, 2),
('velocity', float, 2),
('force', float, 2),
('angle',float, 1),
('radius', float, 1)])
droplets['radius'] = np.random.normal(0.0045, 0.001, numframes)
A = 4*np.pi*droplets['radius']**2
mass = ((4*np.pi*droplets['radius']**3)/3)*1000 #mass of waterdroplet
drag = np.zeros(numframes,dtype = [('dragx', float, 1),('dragy', float, 1)])
rads = np.radians(np.random.normal(37,1,numframes))
droplets['angle'] = rads
droplets['force'][:,1] = -9.8*mass
#Initialize velocity
for i in range(0, numframes):
droplets['velocity'][i,0] = v0*cos(droplets['angle'][i])
droplets['velocity'][i,1] = v0*sin(droplets['angle'][i])
#Initialize the drag
drag['dragx'] = -0.5*rho*cd*A*droplets['velocity'][:,0]
drag['dragy'] = -0.5*rho*cd*A*droplets['velocity'][:,1]
droplets['position'][:,0] = 0
#Initialize the figure
fig,ax = plt.subplots()
axess.Axes.set_frame_on(ax,True)
ax.autoscale(True)
ax.set_xlim(0)
ax.set_ylim(0)
line = ax.plot(droplets['position'][0, 0], droplets['position'][0, 1],'b.')[0]
line = ax.plot(droplets['position'][:, 0], droplets['position'][:, 1],'b.')[0]
xdata = [droplets['position'][0, 0]]
ydata = [droplets['position'][0, 1]]
ln = plt.plot([],[],'b')[0]
def initfunc():
droplets['position'][0,:] = 0
ln.set_data(droplets['position'][0, 0], droplets['position'][0, 1])
return ln
def update(framenum):
index = framenum
cd = 0.5 #drag coefficient of a sphere;
rho = 1.225 #density of air; kg/m^3
A = 4*np.pi*droplets['radius']**2 #surface area of droplet; m^2
mass = ((4*np.pi*droplets['radius']**3)/3)*1000 #mass of waterdroplet
#Update the drag force on the droplet
drag['dragx'] = -0.5*rho*cd*A*droplets['velocity'][:,0]
drag['dragy'] = -0.5*rho*cd*A*droplets['velocity'][:,1]
droplets['force'][index,0] += drag['dragx'][index]
droplets['force'][index,1] += drag['dragy'][index]
#droplets['position'][0] = [0,0]
droplets['velocity'][index,0] = droplets['velocity'][index,0] + (droplets['force'][index,0]/mass[index])*index
droplets['velocity'][index,1] = droplets['velocity'][index,1] + (droplets['force'][index,1]/mass[index])*index
droplets['position'][index,0] = droplets['position'][index,0] + (droplets['velocity'][index,0])*index
droplets['position'][index,1] = droplets['position'][index,1] + (droplets['velocity'][index,1])*index
xdata.append(droplets['position'][index,0])
ydata.append(droplets['position'][index,1])
ln.set_data(xdata,ydata)
line.set_data(droplets['position'][:,0], droplets['position'][:,1])
return ln
sprink = animation.FuncAnimation(fig, update,init_func = initfunc,interval= 200, frames = numframes)
plt.show()
sprink.save('Sprinkler.mp4', fps = fps)
除了 @JohanC 指出的错误之外,您还使用了 表面积 (4 * pi * r^2
) 而不是 水滴(近似为球体)的横截面积 (pi * r^2
)。此外,您似乎在尝试使用假设为真空的运动方程,当添加空气阻力或其他非恒定力时,这些方程会失效。
我建议您阅读
计算粒子在介质中运动的位置本质上就是求解一个first order ODE,有很多方法可以数值求解这样的方程。最简单(尽管最不稳健)的方法是将问题分解成小段并近似从当前点到下一个点的变化。对于这个问题,假设您使用足够小的时间步长,这种方法效果很好。
对于你的情况,我们可以这样做
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
from math import sin, cos
g = 9.81
rho = 1.225
v0 = 50.0 # This can be initialized as desired, I chose 50 m/s for demonstration
C = 0.5
nframes = 100
ndrops = 10
tend = 8.0
time = np.linspace(0.1, tend, nframes) # Runtime of 4s chosen arbitrarily
dt = time[1] - time[0] # change in time between steps
maxs = [0.0, 0.0]
r = [0.001, 0.0045] # This stores the range of radii to be used
# I have implemented a class to store the drops for brevity
class drop:
def __init__(self, pos, vel, r):
self.pos = pos
self.vel = vel
self.r = r
class sprinkler:
# I implemented this as a class to simplify updating the scatter plot
def __init__(self):
self.fig, self.ax = plt.subplots()
self.drops = [None]*ndrops
self.maxt = 0.0
## This step is simply to figure out how far the water drops can travel
## in order to set the bounds of the plot accordingly
angles = np.linspace(0, np.pi/2, 90) # We only need to sample from 0 to pi/2
for angle in angles:
m = [drop([0.0, 0.1], [v0*cos(angle), v0*sin(angle)], 0.001),
drop([0.0, 0.1], [v0*cos(angle), v0*sin(angle)], 0.0045)]
for d in m:
t = 0.0
coef = - 0.5*C*np.pi*d.r**2*rho
mass = 4/3*np.pi*d.r**3*1000
while d.pos[1] > 0:
a = np.power(d.vel, 2) * coef * np.sign(d.vel)/mass
a[1] -= g
d.pos += (d.vel + a * dt) * dt
d.vel += a * dt
t += dt
if d.pos[1] > maxs[1]:
maxs[1] = d.pos[1]
if d.pos[0] > maxs[0]:
maxs[0] = d.pos[0]
if d.pos[1] < 0.0:
if t > self.maxt:
self.maxt = t
break
for ii in range(ndrops): # Make some randomly distributed water drops
self.drops[ii] = drop([0.0, 0.0], [cos(np.random.random()*np.pi)*v0,
sin(np.random.random()*np.pi)*v0],
np.random.random()*(r[1]-r[0]) + r[0])
anim = animation.FuncAnimation(self.fig, self.update, init_func=self.setup,
interval=200, frames=nframes)
anim.save('Sprinkler.gif', fps = 20, writer='imagemagick')
def setup(self):
self.scat = self.ax.scatter([d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops], marker='.', color='k')
self.ax.set_xlim([-maxs[0]*1.15, maxs[0]*1.15])
self.ax.set_ylim([0, maxs[1]*1.15])
return self.scat,
def update(self,frame): # Use set_offsets to move the water drops
self.step() # Advance to the next 'step'
self.scat.set_offsets(np.stack([[d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops]], 1))
return self.scat,
def step(self):
for ii in range(ndrops):
coef = - 0.5*C*np.pi*self.drops[ii].r**2*rho # Aggregated coefficient
mass = 4/3*np.pi*self.drops[ii].r**3*1000
a = np.power(self.drops[ii].vel, 2)*coef * np.sign(self.drops[ii].vel)/mass
a[1] -= g
# Approximate how much the position and velocity would change if we assume
# a(t) does not change between t and t+dt
self.drops[ii].pos += np.array(self.drops[ii].vel) * dt + 0.5 * a * dt**2
self.drops[ii].vel += a * dt
if self.drops[ii].pos[1] < 0.0: # Check if the drop has hit the ground
self.drops[ii].pos[1] = 0.0
self.drops[ii].vel = [0.0, 0.0]
sprinkler()
这可能不会产生您想要的结果,但它正确地模拟了水滴的运动。
将水滴实现为 class 并将它们存储在列表中,就像我所做的那样,只需修改 update
函数(为简单起见添加了 create
方法):
def update(self,frame): # Use set_offsets to move the water drops
# Create between 0 and ndrops new drops, but only until tend-maxt
if time[frame] < tend - self.maxt*1.1:
self.create(np.random.randint(0, ndrops))
self.step() # Advance to the next 'step'
self.scat.set_offsets(np.stack([[d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops]], 1))
return self.scat,
def create(self, i):
for ii in range(i):
self.drops.append(drop([0.0, 0.0], [cos(np.random.random()*np.pi)*v0,
sin(np.random.random()*np.pi)*v0],
np.random.random()*(r[1]-r[0]) + r[0]))
编辑 - 制作第二个动画的完整代码
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
from math import sin, cos
g = 9.81
rho = 1.225
v0 = 50.0 # This can be initialized as desired, I chose 50 m/s for demonstration
C = 0.5
nframes = 100
ndrops = 10
tend = 8.0
time = np.linspace(0.1, tend, nframes) # Runtime of 4s chosen arbitrarily
dt = time[1] - time[0] # change in time between steps
maxs = [0.0, 0.0]
r = [0.001, 0.0045] # This stores the range of radii to be used
# I have implemented a class to store the drops for brevity
class drop:
def __init__(self, pos, vel, r):
self.pos = pos
self.vel = vel
self.r = r
class sprinkler:
# I implemented this as a class to simplify updating the scatter plot
def __init__(self):
self.fig, self.ax = plt.subplots()
self.drops = [None]*ndrops
self.maxt = 0.0
## This step is simply to figure out how far the water drops can travel
## in order to set the bounds of the plot accordingly
angles = np.linspace(0, np.pi/2, 90) # We only need to sample from 0 to pi/2
for angle in angles:
m = [drop([0.0, 0.1], [v0*cos(angle), v0*sin(angle)], 0.001),
drop([0.0, 0.1], [v0*cos(angle), v0*sin(angle)], 0.0045)]
for d in m:
t = 0.0
coef = - 0.5*C*np.pi*d.r**2*rho
mass = 4/3*np.pi*d.r**3*1000
while d.pos[1] > 0:
a = np.power(d.vel, 2) * coef * np.sign(d.vel)/mass
a[1] -= g
d.pos += (d.vel + a * dt) * dt
d.vel += a * dt
t += dt
if d.pos[1] > maxs[1]:
maxs[1] = d.pos[1]
if d.pos[0] > maxs[0]:
maxs[0] = d.pos[0]
if d.pos[1] < 0.0:
if t > self.maxt:
self.maxt = t
break
for ii in range(ndrops): # Make some randomly distributed water drops
self.drops[ii] = drop([0.0, 0.0], [cos(np.random.random()*np.pi)*v0,
sin(np.random.random()*np.pi)*v0],
np.random.random()*(r[1]-r[0]) + r[0])
anim = animation.FuncAnimation(self.fig, self.update, init_func=self.setup,
interval=200, frames=nframes)
anim.save('Sprinkler.gif', fps = 20, writer='imagemagick')
def setup(self):
self.scat = self.ax.scatter([d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops], marker='.', color='k')
self.ax.set_xlim([-maxs[0]*1.15, maxs[0]*1.15])
self.ax.set_ylim([0, maxs[1]*1.15])
return self.scat,
def update(self,frame): # Use set_offsets to move the water drops
# Create between 0 and ndrops new drops, but only until tend-maxt
if time[frame] < tend - self.maxt*1.1:
self.create(np.random.randint(0, ndrops))
self.step() # Advance to the next 'step'
self.scat.set_offsets(np.stack([[d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops]], 1))
return self.scat,
def create(self, i):
for ii in range(i):
self.drops.append(drop([0.0, 0.0], [cos(np.random.random()*np.pi)*v0,
sin(np.random.random()*np.pi)*v0],
np.random.random()*(r[1]-r[0]) + r[0]))
def step(self):
for ii in range(len(self.drops)):
coef = - 0.5*C*np.pi*self.drops[ii].r**2*rho # Aggregated coefficient
mass = 4/3*np.pi*self.drops[ii].r**3*1000
a = np.power(self.drops[ii].vel, 2) * coef * np.sign(self.drops[ii].vel)/mass
a[1] -= g
# Approximate how much the position and velocity would change if we assume
# a(t) does not change between t and t+dt
self.drops[ii].pos += np.array(self.drops[ii].vel) * dt + 0.5 * a * dt**2
self.drops[ii].vel += a * dt
if self.drops[ii].pos[1] < 0.0: # Check if the drop has hit the ground
self.drops[ii].pos[1] = 0.0
self.drops[ii].vel = [0.0, 0.0]
sprinkler()