我正在模拟3D洒水装置。在previous question中,我得到了2D动画效果(谢谢@William Miller)。我尝试使用相同的逻辑并将其实现为3D。在update
函数中,出现以下错误:
'tuple'对象不可调用第129行-> self.scat = self.ax.scatter(x,y,z, 'b.')
编辑代码时,我看着this answer,这就是为什么我使用_offsets3d
。我浏览了Matplotlib文档并试图弄清楚为什么我得到一个元组对象,但是没有成功。我还尝试在给定的轴上简单地使用scatter
,但是内核变得无响应(即使连续5滴,8秒,50帧)。我不确定下一步该怎么做?
#Based on code by William Miller
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
from math import sin, cos
from mpl_toolkits.mplot3d import Axes3D
import random
#Parameters
rho = 1.225
c = 0.5
v0 = 50
g = 9.81
#Timing
fps = 20
tmax = 1*10
nframes = tmax*fps
time = np.linspace(0,tmax, nframes)
dt = time[1]-time[0]
#Waterdroplets
ndrops = 100
#Positioning
maxs = [0.0, 0.0, 0.0]
rmax = [0.0008, 0.0065] #range of radii of water droplets in m
finx = [] #record landing positions of the droplets to be used for analysis
finy = []
#Droplet sizing
theta = np.radians(np.random.normal(37, 8, 80))
phi = np.radians(np.random.normal(3, 1.07, 80))
radii = np.random.normal(0.004, 0.001, 80)
class drop:
def __init__(self,pos,vel,r):
self.pos = pos
self.vel = vel
self.r = r
class sprinkler:
def __init__(self):
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111, projection = '3d')
self.drops = [None]*ndrops # creates empty list of length ndrops
self.maxt = 0.0
theta = np.radians(np.random.normal(37, 8, 100))
phi = np.radians(np.random.normal(3, 1.07, 100))
radii = np.random.normal(0.004, 0.001, 100)
#Find the maximum flight time for each droplet
#and the maximum distance the droplets will travel
for i in range(len(phi)):
m = [drop([0.0, 0.0,0.1], [v0*cos(theta[i])*cos(phi[i]),
v0*cos(theta[i])*sin(theta[i]),
v0*sin(theta[i])],0.0008),
drop([0.0, 0.0,0.1], [v0*cos(theta[i])*cos(phi[i]),
v0*cos(theta[i])*sin(theta[i]),
v0*sin(theta[i])],0.0065)]
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[2] > 0:
a = np.power(d.vel, 2) * coef * np.sign(d.vel)/mass
a[2] -= g
d.pos += (d.vel + a * dt) * dt
d.vel += a * dt
t += dt
if d.pos[2] > maxs[2]:
maxs[2] = d.pos[2]
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[2] < 0.0:
if t > self.maxt:
self.maxt = t
break
#print('Max time is:',maxt)
#print('Max positions are:', maxs)
#Create initial droplets
for ii in range(ndrops):
phiang = random.randint(0,len(phi)-1)
thetang = random.randint(0,len(theta)-1)
rad = random.randint(0,len(radii)-1)
self.drops[ii] = drop([0.0, 0.0, 0.1],
[v0*cos(theta[thetang])*cos(phi[phiang]),
v0*cos(theta[thetang])*sin(phi[phiang]),
v0*sin(theta[thetang])],
radii[random.randint(0,len(radii)-1)])
ani = animation.FuncAnimation(self.fig, self.update, init_func = self.setup,
interval = 200, frames = nframes)
ani.save('MySprinkler.mp4', fps = 20, extra_args=['-vcodec', 'libx264'])
plt.show()
def setup(self):
self.scat = self.ax.scatter([d.pos[0] for d in self.drops],
[d.pos[1] for d in self.drops],
[d.pos[2] for d in self.drops], 'b.')
self.ax.set_xlim(-1, 100)
self.ax.set_ylim(-1, 100)
self.ax.set_zlim(0, 50)
self.ax.set_xlabel('X Distance')
self.ax.set_ylabel('Y Distance')
self.ax.set_zlabel('Height')
return self.scat
def update(self, frame):
if time[frame] <(tmax-self.maxt*1.1):
self.create(ndrops)
self.step()
for d in self.drops:
x = d.pos[0]
y = d.pos[1]
z = d.pos[2]
self.scat = self.scat._offsets3d(x,y,z, 'b.')
return self.scat,
def create(self, i):
for l in range(i):
phiang = random.randint(0,len(phi)-1)
thetang = random.randint(0,len(theta)-1)
rad = random.randint(0,len(radii)-1)
self.drops.append(drop([0.0, 0.0, 0.0],
[v0*cos(theta[thetang])*cos(phi[phiang]),
v0*cos(theta[thetang])*sin(phi[phiang]),
v0*sin(theta[thetang])],
radii[rad]))
def step(self):
global finx, finy
for x in range(len(self.drops)):
coef = -0.5*c*np.pi*self.drops[x].r**2*rho
mass = 4/3*np.pi*self.drops[x].r**3*1000
a = np.power(self.drops[x].vel,2) * coef * np.sign(self.drops[x].vel)/mass
a[2] = a[2]-g
self.drops[x].pos += np.array(self.drops[x].vel)*dt +0.5*a*dt**2
self.drops[x].vel += a*dt
if self.drops[x].pos[2] < 0.0:
self.drops[x].pos[2] = 0.0
self.drops[x].vel = [0.0, 0.0, 0.0]
finx = np.append(finx, self.drops[x].pos[0])
finy = np.append(finy, self.drops[x].pos[1])
return self.drops, finx, finy,
sprinkler()
更新功能似乎有两个问题:
'tuple' object is not callable
所示,_offsets3d(x,y,z, 'b.')
不是可调用函数。它是一个包含x,y和z值的元组。我将您的更新功能更改为:
def update(self, frame):
if time[frame] <(tmax-self.maxt*1.1):
self.create(ndrops)
self.step()
x = np.array([d.pos[0] for d in self.drops])
y = np.array([d.pos[1] for d in self.drops])
z = np.array([d.pos[2] for d in self.drops])
self.scat._offsets3d = (x, y, z)
return self.scat,
如下所示,给出一个工作动画(我用5滴干了)。
我没有调查速度问题。您可以更改的一件事是,直接将液滴保存为_offsets3d所需的格式,但这并不是真正的罪魁祸首。可能更有用的是使用numpy's broadcasting进行计算。还请注意,您会不断向列表中添加越来越多的水滴。