Project 2

Particle Interaction Simulator

This project was inspired by me wanting to create baseline visuals for particle interaction within a confined 3d space.

The simulation is designed to help understand the physical and chemical behaviors of particles. Going forward, I plan on implementing different physical/chemical conditions that will dictate their motion (ie gravity, temperature, etc).

Mathematical Foundation

1. Newton's Laws of Motion:

According to Newton's second law, the force acting on a particle is equal to the mass of the particle multiplied by its acceleration:

\[ F = ma \]

2. Particle Interactions:

Gravitational Forces: In the absence of other forces (closed system), particles will experience gravitational attraction towards each other.

Electrostatic Forces: Charged particles will experience repulsive or attractive forces based on Coulomb's law:

\[ F = k_e \frac{q_1 q_2}{r^2} \]

Lennard-Jones Potential: This potential models the interaction between a pair of neutral atoms or molecules. It is a function of the distance between the particles and has a form:

\[ V(r) = 4\epsilon \left[ \left(\frac{\sigma}{r}\right)^{12} - \left(\frac{\sigma}{r}\right)^{6} \right] \]

where \( \epsilon \) is the depth of the potential well, \( \sigma \) is the finite distance at which the inter-particle potential is zero, and \( r \) is the distance between the particles.

3. Boundary Conditions:

The particles are confined within a box, which means they will experience boundary conditions. When a particle hits the wall of the box, it will bounce back, which can be modeled using perfectly elastic collisions.

Simulation Logic

Initialization:

Particles are initialized with random positions and velocities within the confined box. Initial conditions include particle mass, charge, and other properties relevant to their interactions.

Time Stepping:

The simulation proceeds in discrete time steps. At each time step:

Technologies Used

Raw Code

from vpython import *
import random

# Constants
num_particles = 100
box_size = 15
particle_radius = 0.3
dt = 0.05  # time derivative that is used to calculate speed

# create the simulation box with a white outline
simulation_box = box(size=vector(box_size, box_size, box_size), opacity=0.1, color=color.white)

# create the outline using a curve for each edge
edges = [
    [vector(-box_size/2, -box_size/2, -box_size/2), vector(box_size/2, -box_size/2, -box_size/2)],
    [vector(box_size/2, -box_size/2, -box_size/2), vector(box_size/2, box_size/2, -box_size/2)],
    [vector(box_size/2, box_size/2, -box_size/2), vector(-box_size/2, box_size/2, -box_size/2)],
    [vector(-box_size/2, box_size/2, -box_size/2), vector(-box_size/2, -box_size/2, -box_size/2)],
    [vector(-box_size/2, -box_size/2, box_size/2), vector(box_size/2, -box_size/2, box_size/2)],
    [vector(box_size/2, -box_size/2, box_size/2), vector(box_size/2, box_size/2, box_size/2)],
    [vector(box_size/2, box_size/2, box_size/2), vector(-box_size/2, box_size/2, box_size/2)],
    [vector(-box_size/2, box_size/2, box_size/2), vector(-box_size/2, -box_size/2, box_size/2)],
    [vector(-box_size/2, -box_size/2, -box_size/2), vector(-box_size/2, -box_size/2, box_size/2)],
    [vector(box_size/2, -box_size/2, -box_size/2), vector(box_size/2, -box_size/2, box_size/2)],
    [vector(box_size/2, box_size/2, -box_size/2), vector(box_size/2, box_size/2, box_size/2)],
    [vector(-box_size/2, box_size/2, -box_size/2), vector(-box_size/2, box_size/2, box_size/2)]
]

for edge in edges:
    curve(pos=edge, color=color.white)

# function to generate a random color
def random_color():
    return vector(random.random(), random.random(), random.random())

class Particle:
    def __init__(self, position, velocity, radius, color):
        self.sphere = sphere(pos=position, radius=radius, color=color)
        self.velocity = velocity
        self.radius = radius

    def update_position(self):
        self.sphere.pos += self.velocity * dt
        self.check_collision_with_walls()

    def check_collision_with_walls(self):
        if abs(self.sphere.pos.x) + self.radius >= box_size / 2:
            self.velocity.x *= -1
            self.sphere.pos.x = copysign(box_size / 2 - self.radius, self.sphere.pos.x)
        if abs(self.sphere.pos.y) + self.radius >= box_size / 2:
            self.velocity.y *= -1
            self.sphere.pos.y = copysign(box_size / 2 - self.radius, self.sphere.pos.y)
        if abs(self.sphere.pos.z) + self.radius >= box_size / 2:
            self.velocity.z *= -1
            self.sphere.pos.z = copysign(box_size / 2 - self.radius, self.sphere.pos.z)

    def handle_collision(self, other):
        distance = mag(self.sphere.pos - other.sphere.pos)
        if distance < 2 * self.radius:
            pos_i = self.sphere.pos
            pos_j = other.sphere.pos
            vel_i = self.velocity
            vel_j = other.velocity

            normal = norm(pos_j - pos_i)
            relative_velocity = vel_i - vel_j
            speed = dot(relative_velocity, normal)

            if speed > 0:  # only adjust if theyre moving towards each other
                impulse = 2 * speed / (1 / self.radius + 1 / other.radius)
                self.velocity -= impulse * normal / self.radius
                other.velocity += impulse * normal / other.radius

# create a list to hold the particles
particles = []

# initialize particles with random positions, velocities, and colors
for i in range(num_particles):
    position = vector(random.uniform(-box_size/2, box_size/2),
                        random.uniform(-box_size/2, box_size/2),
                        random.uniform(-box_size/2, box_size/2))
    velocity = vector(random.uniform(-1, 1),
                        random.uniform(-1, 1),
                        random.uniform(-1, 1))
    color = random_color()  # assign a random color to each particle
    particle = Particle(position, velocity, particle_radius, color)
    particles.append(particle)

# main simulation loop
while True:
    rate(100)
    for particle in particles:
        particle.update_position()
    for i in range(num_particles):
        for j in range(i+1, num_particles):
            particles[i].handle_collision(particles[j])
        

Sources

https://web.pdx.edu/~egertonr/ph311-12/particle.htm