"""A model of an ideal gas with hard-sphere collisions""" ## Based on gas.py by Bruce Sherwood for a cube as a container ## Slightly modified by Andrey Antonov for a torus. ## Adapted by M. Musy for vedo from random import random import numpy as np from vedo import Plotter, mag, versor, Torus, Spheres from vedo.addons import ProgressBarWidget ############################################################# Natoms = 400 # change this to have more or fewer atoms Nsteps = 200 # nr of steps in the simulation Matom = 4e-3 / 6e23 # helium mass Ratom = 0.025 RingThickness = 0.3 # thickness of the toroid RingRadius = 1 k = 1.4e-23 # Boltzmann constant T = 300 # room temperature dt = 1.5e-5 ############################################################ def reflection(p, pos): n = versor(pos) return np.dot(np.identity(3) - 2 * n * n[:, np.newaxis], p) plt = Plotter(title="gas in toroid", interactive=False) plt += __doc__ plt += Torus(r1=RingRadius, r2=RingThickness).c("green",0.1).wireframe(True) poslist = [] plist, mlist, rlist = [], [], [] mass = Matom pavg = np.sqrt(2.0 * mass * 1.5 * k * T) # average kinetic energy p**2/(2mass) = (3/2)kT colors = np.random.rand(Natoms) for i in range(Natoms): alpha = 2 * np.pi * random() x = RingRadius * np.cos(alpha) * 0.9 y = RingRadius * np.sin(alpha) * 0.9 z = 0 theta = np.pi * random() phi = 2 * np.pi * random() px = pavg * np.sin(theta) * np.cos(phi) py = pavg * np.sin(theta) * np.sin(phi) pz = pavg * np.cos(theta) poslist.append((x, y, z)) plist.append((px, py, pz)) mlist.append(mass) rlist.append(np.abs(Ratom + Ratom*np.random.rand() / 2)) pos = np.array(poslist) poscircle = pos p = np.array(plist) m = np.array(mlist) m.shape = (Natoms, 1) radius = np.array(rlist) r = pos - pos[:, np.newaxis] # all pairs of atom-to-atom vectors ds = (p / m) * (dt / 2.0) if "False" not in np.less_equal(mag(ds), radius).tolist(): pos = pos + (p / mass) * (dt / 2.0) # initial half-step pbw = ProgressBarWidget(Nsteps) plt += pbw plt.show() for it in range(Nsteps): # Update all positions ds = mag((p / m) * (dt / 2.0)) if "False" not in np.less_equal(ds, radius).tolist(): pos = pos + (p / m) * dt r = pos - pos[:, np.newaxis] # all pairs of atom-to-atom vectors rmag = np.sqrt(np.sum(np.square(r), -1)) # atom-to-atom scalar distances hit = np.less_equal(rmag, radius + radius[:, None]) - np.identity(Natoms) hitlist = np.sort(np.nonzero(hit.flat)[0]).tolist() # i,j encoded as i*Natoms+j # If any collisions took place: for ij in hitlist: i, j = divmod(ij, Natoms) # decode atom pair hitlist.remove(j * Natoms + i) # remove symmetric j,i pair from list ptot = p[i] + p[j] mi = m[i, 0] mj = m[j, 0] vi = p[i] / mi vj = p[j] / mj ri = Ratom rj = Ratom a = mag(vj - vi) ** 2 if a == 0: continue # exactly same velocities b = 2 * np.dot(pos[i] - pos[j], vj - vi) c = mag(pos[i] - pos[j]) ** 2 - (ri + rj) ** 2 d = b ** 2 - 4.0 * a * c if d < 0: continue # something wrong; ignore this rare case deltat = (-b + np.sqrt(d)) / (2.0 * a) # t-deltat is when they made contact pos[i] = pos[i] - (p[i] / mi) * deltat # back up to contact configuration pos[j] = pos[j] - (p[j] / mj) * deltat mtot = mi + mj pcmi = p[i] - ptot * mi / mtot # transform momenta to cm frame pcmj = p[j] - ptot * mj / mtot rrel = versor(pos[j] - pos[i]) pcmi = pcmi - 2 * np.dot(pcmi, rrel) * rrel # bounce in cm frame pcmj = pcmj - 2 * np.dot(pcmj, rrel) * rrel p[i] = pcmi + ptot * mi / mtot # transform momenta back to lab frame p[j] = pcmj + ptot * mj / mtot pos[i] = pos[i] + (p[i] / mi) * deltat # move forward deltat in time pos[j] = pos[j] + (p[j] / mj) * deltat # Bounce off the boundary of the torus for j in range(Natoms): poscircle[j] = versor(pos[j]) * RingRadius * [1, 1, 0] outside = np.greater_equal(mag(poscircle - pos), RingThickness - 2 * Ratom) for k in range(len(outside)): if outside[k] == 1 and np.dot(p[k], pos[k] - poscircle[k]) > 0: p[k] = reflection(p[k], pos[k] - poscircle[k]) # then update positions of display objects outside = np.greater_equal(mag(pos), RingRadius + RingThickness) pbw.update() # update progress bar plt.remove("Spheres").add(Spheres(pos, r=radius, c='b6')) plt.render().reset_camera().azimuth(0.5) plt.interactive().close()