gas.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. """A model of an ideal gas with hard-sphere collisions"""
  2. ## Based on gas.py by Bruce Sherwood for a cube as a container
  3. ## Slightly modified by Andrey Antonov for a torus.
  4. ## Adapted by M. Musy for vedo
  5. from random import random
  6. import numpy as np
  7. from vedo import Plotter, mag, versor, Torus, Spheres
  8. from vedo.addons import ProgressBarWidget
  9. #############################################################
  10. Natoms = 400 # change this to have more or fewer atoms
  11. Nsteps = 200 # nr of steps in the simulation
  12. Matom = 4e-3 / 6e23 # helium mass
  13. Ratom = 0.025
  14. RingThickness = 0.3 # thickness of the toroid
  15. RingRadius = 1
  16. k = 1.4e-23 # Boltzmann constant
  17. T = 300 # room temperature
  18. dt = 1.5e-5
  19. ############################################################
  20. def reflection(p, pos):
  21. n = versor(pos)
  22. return np.dot(np.identity(3) - 2 * n * n[:, np.newaxis], p)
  23. plt = Plotter(title="gas in toroid", interactive=False)
  24. plt += __doc__
  25. plt += Torus(r1=RingRadius, r2=RingThickness).c("green",0.1).wireframe(True)
  26. poslist = []
  27. plist, mlist, rlist = [], [], []
  28. mass = Matom
  29. pavg = np.sqrt(2.0 * mass * 1.5 * k * T) # average kinetic energy p**2/(2mass) = (3/2)kT
  30. colors = np.random.rand(Natoms)
  31. for i in range(Natoms):
  32. alpha = 2 * np.pi * random()
  33. x = RingRadius * np.cos(alpha) * 0.9
  34. y = RingRadius * np.sin(alpha) * 0.9
  35. z = 0
  36. theta = np.pi * random()
  37. phi = 2 * np.pi * random()
  38. px = pavg * np.sin(theta) * np.cos(phi)
  39. py = pavg * np.sin(theta) * np.sin(phi)
  40. pz = pavg * np.cos(theta)
  41. poslist.append((x, y, z))
  42. plist.append((px, py, pz))
  43. mlist.append(mass)
  44. rlist.append(np.abs(Ratom + Ratom*np.random.rand() / 2))
  45. pos = np.array(poslist)
  46. poscircle = pos
  47. p = np.array(plist)
  48. m = np.array(mlist)
  49. m.shape = (Natoms, 1)
  50. radius = np.array(rlist)
  51. r = pos - pos[:, np.newaxis] # all pairs of atom-to-atom vectors
  52. ds = (p / m) * (dt / 2.0)
  53. if "False" not in np.less_equal(mag(ds), radius).tolist():
  54. pos = pos + (p / mass) * (dt / 2.0) # initial half-step
  55. pbw = ProgressBarWidget(Nsteps)
  56. plt += pbw
  57. plt.show()
  58. for it in range(Nsteps):
  59. # Update all positions
  60. ds = mag((p / m) * (dt / 2.0))
  61. if "False" not in np.less_equal(ds, radius).tolist():
  62. pos = pos + (p / m) * dt
  63. r = pos - pos[:, np.newaxis] # all pairs of atom-to-atom vectors
  64. rmag = np.sqrt(np.sum(np.square(r), -1)) # atom-to-atom scalar distances
  65. hit = np.less_equal(rmag, radius + radius[:, None]) - np.identity(Natoms)
  66. hitlist = np.sort(np.nonzero(hit.flat)[0]).tolist() # i,j encoded as i*Natoms+j
  67. # If any collisions took place:
  68. for ij in hitlist:
  69. i, j = divmod(ij, Natoms) # decode atom pair
  70. hitlist.remove(j * Natoms + i) # remove symmetric j,i pair from list
  71. ptot = p[i] + p[j]
  72. mi = m[i, 0]
  73. mj = m[j, 0]
  74. vi = p[i] / mi
  75. vj = p[j] / mj
  76. ri = Ratom
  77. rj = Ratom
  78. a = mag(vj - vi) ** 2
  79. if a == 0:
  80. continue # exactly same velocities
  81. b = 2 * np.dot(pos[i] - pos[j], vj - vi)
  82. c = mag(pos[i] - pos[j]) ** 2 - (ri + rj) ** 2
  83. d = b ** 2 - 4.0 * a * c
  84. if d < 0:
  85. continue # something wrong; ignore this rare case
  86. deltat = (-b + np.sqrt(d)) / (2.0 * a) # t-deltat is when they made contact
  87. pos[i] = pos[i] - (p[i] / mi) * deltat # back up to contact configuration
  88. pos[j] = pos[j] - (p[j] / mj) * deltat
  89. mtot = mi + mj
  90. pcmi = p[i] - ptot * mi / mtot # transform momenta to cm frame
  91. pcmj = p[j] - ptot * mj / mtot
  92. rrel = versor(pos[j] - pos[i])
  93. pcmi = pcmi - 2 * np.dot(pcmi, rrel) * rrel # bounce in cm frame
  94. pcmj = pcmj - 2 * np.dot(pcmj, rrel) * rrel
  95. p[i] = pcmi + ptot * mi / mtot # transform momenta back to lab frame
  96. p[j] = pcmj + ptot * mj / mtot
  97. pos[i] = pos[i] + (p[i] / mi) * deltat # move forward deltat in time
  98. pos[j] = pos[j] + (p[j] / mj) * deltat
  99. # Bounce off the boundary of the torus
  100. for j in range(Natoms):
  101. poscircle[j] = versor(pos[j]) * RingRadius * [1, 1, 0]
  102. outside = np.greater_equal(mag(poscircle - pos), RingThickness - 2 * Ratom)
  103. for k in range(len(outside)):
  104. if outside[k] == 1 and np.dot(p[k], pos[k] - poscircle[k]) > 0:
  105. p[k] = reflection(p[k], pos[k] - poscircle[k])
  106. # then update positions of display objects
  107. outside = np.greater_equal(mag(pos), RingRadius + RingThickness)
  108. pbw.update() # update progress bar
  109. plt.remove("Spheres").add(Spheres(pos, r=radius, c='b6'))
  110. plt.render().reset_camera().azimuth(0.5)
  111. plt.interactive().close()