Tutorial: Numpy simultions of 2D gas#
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interactive
import matplotlib.animation as animation
from IPython.display import HTML
class GasSimulation:
def __init__(self, N=100, L=10.0, dt=0.01, radius=0.2, temperature=1.0, mass=1.0, steps=500):
"""Initialize gas particle simulation."""
self.N, self.L, self.dt, self.radius, self.temperature, self.mass, self.steps = N, L, dt, radius, temperature, mass, steps
# Initialize positions (left side) and velocities (Maxwell-Boltzmann distribution)
self.positions = np.column_stack((np.random.uniform(0, L/2, N), np.random.uniform(0, L, N)))
self.velocities = np.random.normal(0, np.sqrt(temperature / mass), (N, 2))
# Pre-allocate arrays for performance
self.positions_history = np.zeros((steps, N, 2), dtype=np.float32)
self.velocities_history = np.zeros((steps, N, 2), dtype=np.float32)
def update_positions(self):
"""Update positions and handle wall collisions."""
self.positions += self.velocities * self.dt
collisions = (self.positions - self.radius < 0) | (self.positions + self.radius > self.L) # Grab particles that collided with the the walls
self.velocities[collisions] *= -1 # Reverse the velocity upon collision
self.positions = np.clip(self.positions, self.radius, self.L - self.radius) # Clip positions and make sure no particle is exceeding the limits
def run_simulation(self):
"""Run simulation and store trajectory history."""
for step in range(self.steps):
self.update_positions()
self.positions_history[step], self.velocities_history[step] = self.positions.copy(), self.velocities.copy()
# Create and run simulation
sim = GasSimulation(N=100, steps=500)
sim.run_simulation()
print(sim.positions_history.shape) # Needs some tools to summarize the information
(500, 100, 2)
Post-processing simulation#
Show code cell source
def visualize(sim):
def scatter_plot(frame=0):
plt.figure(figsize=(5, 5))
plt.xlim(0, sim.L), plt.ylim(0, sim.L)
plt.title(f"Frame {frame + 1} / {len(sim.positions_history)}")
plt.scatter(*sim.positions_history[frame].T, s=20, c='blue', alpha=0.6)
plt.show()
return interactive(scatter_plot, frame=(0, sim.steps - 1, 1))
def animate(sim):
L, pos, steps = sim.L, sim.positions_history, sim.steps
fig, ax = plt.subplots(figsize=(5, 5))
ax.set_xlim(0, L), ax.set_ylim(0, L)
scatter = ax.scatter([], [], s=20, c='blue', alpha=0.6)
def update(frame):
scatter.set_offsets(pos[frame])
return scatter,
ani = animation.FuncAnimation(fig, update, frames=steps, interval=20, blit=True)
plt.close()
return HTML(ani.to_jshtml())
# Create and run simulation
sim = GasSimulation(N=100, steps=500)
sim.run_simulation()
visualize(sim)
animate(sim)
Project 1: Plot velocity and particle distributions#
#plot_speed_distribution(sim.velocities_history)
#plot_position_heatmap(sim.positions_history)
Project 2: Implement elastic collisions between gas particles#
1. Conservation Laws#
When two particles collide elastically, both momentum and kinetic energy are conserved.
Momentum Conservation#
Kinetic Energy Conservation#
where:
, are the initial speeds of particles 1 and 2. , are the final speeds after the collision.
2. Collision Setup#
For two colliding particles, we define:
Positions:
Velocities (before collision):
Masses:
The relative velocity between the two particles before the collision is:
The unit vector along the collision axis (the direction along the line joining the particle centers) is:
3. Velocities After Collision#
After the collision, the velocity component along the collision axis changes, while the perpendicular components remain unchanged.
Using momentum and energy conservation, the updated velocities are:
where:
is the projection of relative velocity along the collision axis.Projection is negative when particles moving towards each other and positive if moving away from each other.
The prefactors
and ensure momentum and energy conservation.
Need to update positions by checking for collisions#
for i in range(self.N):
for j in range(i + 1, self.N):
r_rel = self.positions[i] - self.positions[j] # Relative position
v_rel = self.velocities[i] - self.velocities[j] # Relative velocity
dist = np.linalg.norm(r_rel)
if dist < 2 * self.radius: # Collision condition
# Fill out code
# Ensure that particles moving towards each other and not away from each other to avoid extra computation