I am simulating sprinkler in 3D. In a previous question, I was helped to animate it in 2D (thank you @William Miller). I tried to use the same logic and implement it into 3D. In the update
function, I get the following error:
'tuple' object is not callable
line 129 --> self.scat = self.ax.scatter(x,y,z, 'b.')
I looked at this answer when editing the code, which is why I used _offsets3d
. I have looked through the Matplotlib documentation and tried to figure out why I'm getting a tuple object, but no success. I have also tried to simply use scatter
on the given axes, but the kernel becomes unresponsive (even for 5 drops, 8 seconds, 50 frames). I'm not sure what to try next?
#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()