How do I optimise my code more?
The goal is to have my code work with more than 200 moving balls, but that takes forever right now and doesn't look smooth at all. Furthermore, I can't get the sizing of the balls to be done entirely correctly based on the size of the figure. With the current specified size, it works, but the moment I try to scale to a bigger field, the balls start overlapping before collision happens.
%matplotlib qt5
import numpy as np
from scipy.spatial.distance import pdist, squareform
import matplotlib.pyplot as plt
import matplotlib.animation as animation
rng = np.random.default_rng()
###
###
def wall_collisions(r, v, Mass, radius_out, radius_in):
"""
This function determines when the balls collide with the outer and inner walls
inputs: r 'position', v 'velocity', Mass 'masses',
radius_out 'outer radius of the donut', radius_in 'inner radius of the donut'
output: v 'velocities after ball-wall collisions'
"""
for position, velocity, weight in zip(r, v, Mass):
euclid = np.sqrt(np.sum(position**2))
dot_pro = np.dot(position, velocity)
#outer bounce
if euclid > (radius_out - weight) and dot_pro > 0:
velocity -= 2*position * dot_pro / np.dot(position, position)
#inner bounce
elif euclid < (radius_in + weight) and dot_pro < 0:
velocity -= 2*dot_pro * position / euclid**2
return v
def resolve_collisions(r, v, Mass, mass_matrix):
"""
This function determines when the balls collide with eachother
inputs: r 'position', v 'velocity', Mass 'masses',
mass_matrix 'matrix with pre-determined collision distances'
output: v 'velocities after ball-ball collisions'
"""
a, b = np.triu_indices(len(r))
dist_matrix = squareform(pdist(r))
np.fill_diagonal(dist_matrix, False)
dist_check = np.where((dist_matrix[a,b] - mass_matrix[a,b]) < 0)[0]
for i in dist_check:
tot_mass = mass_matrix[a[i], b[i]]
diff_r = r[a[i]] - r[b[i]]
dot_rv = np.dot(diff_r, v[a[i]] - v[b[i]])
if dot_rv < 0:
v_update = dot_rv * diff_r / np.sum(diff_r**2)
v[a[i]] -= (v_update) * (2*Mass[b[i]] / tot_mass)
v[b[i]] += (v_update) * (2*Mass[a[i]] / tot_mass)
return v
def animate(i):
"""
This function determines the simulation steps, based on Euler's method
"""
global r, v, dt, Mass, mass_matrix, radius_out, radius_in, n
for a in range(20):
r += v * dt
wall_collisions(r, v, Mass, radius_out, radius_in)
resolve_collisions(r, v, Mass, mass_matrix)
points.set_offsets(r)
return points,
##Variables
dt = 0.05 #time step in s
radius_out = 200 #outer circle of the donut containing the balls
radius_in = 50 #inner circle of the donut containing the balls
#box in which everything is plotted
#only necessary for figuring out the correct plot size of the balls
box_size = radius_out * 2.1
N = 100 #amount of balls
Mass = 10*rng.uniform(size=N) #randomised masses of the balls, coincidentally also their radius
#matrix with maximal distances from each mass to the others
#needed before collision happenes
mass_matrix = Mass[:, np.newaxis] + Mass[np.newaxis, :]
#position of the balls
#positioning everything within the donut
Poss_r = rng.uniform(radius_in, radius_out, N)
Poss_phi = rng.uniform(0, 2*np.pi, N)
#converting everything to cartesian coordinates
Poss_x = Poss_r * np.cos(Poss_phi)
Poss_y = Poss_r * np.sin(Poss_phi)
r = np.column_stack((Poss_x,Poss_y))
#speed of the balls
v = rng.uniform(-5, 5, (N,2))
##Plotting and animating
circle_out = plt.Circle((0, 0), radius_out, color='b', fill=False)
circle_in = plt.Circle((0, 0), radius_in , color='b', fill=False)
fig, ax = plt.subplots(figsize=(8,8))
fig.gca().set_aspect('equal', 'box')
ms = (np.pi*Mass**2)*fig.gca().get_window_extent().height/box_size * 72./fig.dpi
ax.add_patch(circle_out)
ax.add_patch(circle_in)
points = ax.scatter(r[:,0], r[:,1],
marker = "o",
s = ms,
c = Mass,
cmap = "summer",
edgecolor = "black",
)
ax.set(
xlim = (-box_size/2, box_size/2),
ylim = (-box_size/2, box_size/2),
title = "moving atoms"
)
plt.colorbar(points)
ani = animation.FuncAnimation(fig, animate, frames = 1000, repeat = False,
interval = 10, blit = False)
plt.show()
%matplotlib qt5is not standardized Python. \$\endgroup\$