In [109]:
import numpy as np
import random
import math

#first I set up the boundaries of the container
max_w = 100
min_w = -100
max_h = 100
min_h = -100

current_time = 0 

#now the big particle
mass = 1 #0
radius = 1 #1
bigx_loc = 0 #2
bigy_loc = 0 #3
bigv_x = 0 #4
bigv_y = 0 #5

big_particle = np.array([mass,radius,bigx_loc,bigy_loc,bigv_x,bigv_y])

The first function populates the area with a bunch of particles. It gives each a random x coordinate, y coordinate, x velocity, and y velocity.

In [ ]:
def populate_box():
    #this function returns arrays of particles that populate the box
    
    #first we set up empty arrays
    pos_x = np.array([])
    pos_y = np.array([])
    v_x = np.array([])
    v_y = np.array([])
    
    #next I want to define the distribution
    
    
    for i in range(100):
        particlex = random.randint(-10,10)
        pos_x = np.append(pos_x, particlex)
        
        particley = random.randint(-10,10)
        pos_y = np.append(pos_y, particley)
        
        #these are test velicities, not how I will actually determine velocities
        part_vx = np.random.normal(0,.1)
        v_x = np.append(v_x, part_vx)
        
        part_vy = np.random.normal(0,.1)
        v_y = np.append(v_y, part_vy)
        
    return pos_x, pos_y, v_x, v_y

The time step function takes all the particles positions, their velocities, the current time, and a variable $\Delta t$ and moves the positions and updates the time of the system based on the velocities and the $\Delta t$. Works forwards as well as backwards!

In [ ]:
def time_step(x,y,x_velocity,y_velocity, time, delta_t,big):
    #given particle positions and velocities and current time return new positions and new time
    for i in range(100):
        x[i] = x[i] + x_velocity[i]/delta_t
        y[i] = y[i] + y_velocity[i]/delta_t
        if x[i] > 10 or x[i] < -10:
            x_velocity[i] = -x_velocity[i]
        if y[i] > 10 or y[i] < -10:
            y_velocity[i] = -y_velocity[i]
        big[2] += big[4]/delta_t
        big[3] += big[5]/delta_t
    time = time +delta_t
    return x, y, x_velocity, y_velocity, time, big

This function, when given the position of all particles, checks the distance of each small particle from the big particle via $r = \sqrt{(x_s - x_b)^2 + (y_s - y_b)^2}$ where $x_s$ and $y_s$ is the small particles coordinates and $x_b$ and $y_b$ are the coordinates of the big function. If $r$ is less than the radius of the sphere the function returns the number of whichever particle is inside the big particle. If no particles are inside the big particle the function returns None.

In [ ]:
def collision_detect(x, y, big):
    #given positions of particles, detects if collision has occurred and returns collided particle
    for i in range(100):
        distance_from_big = math.sqrt((x[i] - big[2])**2 + (y[i] - big[3])**2)
        if distance_from_big < big[1]:
            return i
    return None

This next function determines how far back in time one must go in order to get the intersecting particle to the edge of the large particle. The first thing it does is shifts everything into the frame of reference of the big particle.

Once in the frame of reference, we simply need to find the negative time it takes for the particle to move to a position at radius the radius. By the pythagorgean theorem $r^2 = (x +v_x t)^2 + (y + v_y t)^2$ This can be rearranged into $0 = (v_x^2 + v_y^2)t^2 + (2v_x x +2 v_y y)t + (x^2 + y^2 + r^2) $. From this $t$ can be found from the quadratic equation.

Problem with this function. Determinant sometimes negative? Should I use absolute velocity? But I think this means there is another problem

In [110]:
def pointofcollision_finder(offendingparticle, x,y,x_velocity,y_velocity,big):
    # given a bunch of stuff, return the value of delta t the system has to move in order to find the point of contact
    
    #first I will shift everything into frame of reference of the big particle to make this easier.
    shifted_vx = (x_velocity[offendingparticle] - big[4])
    shifted_vy = (y_velocity[offendingparticle] - big[5])
    shifted_x = (x[offendingparticle] - big[2])
    shifted_y = (y[offendingparticle] - big[3])
    
    #okay, now I need to figure out how to calculate delta t such that the position is at radius
    #uh I can find that from just the quadratic equation
    A = (shifted_vx**2 + shifted_vy**2)
    B = (2*shifted_vx*shifted_x + 2*shifted_vy*shifted_y)
    C = (shifted_x**2 + shifted_y**2+ big[1]**2)
    time_delta = (-B - math.sqrt(B**2 - 4*A*C)) / 2*A
    
    return time_delta

Once the particles are at the point of collision (when a particle as at the radius of the large particle), this function calculates the new velocities as a result of collision. First, the function calculates the velocity of the center of mass. $v_{cm} = \frac{m_1v_1 + m_2v_2}{m_1 + m_2}$

Next, this is subtracted from the velocity of the big particle and the small particle in order to shift into the frame of reference of the center of mass. Then the shifted velocities are reflected (the collision), and finally shifted back into the normal frame of reference where the new velocities are output to rewrite the old.

Problem with this function. The velocities when output are ridiculously high. Often get overflow error.

In [ ]:
def velocity_exchanger(big, x_velocity, y_velocity, offendingparticle):
    #given big particle info and offending particle info, adjust new velocities
    #first thing that needs to be done is shift into frame of reference for center of mass
    mass = 1 #this is mass of small particles
    cm_vx = (big[0]*big[4] + x_velocity[offendingparticle]*mass) / (big[4] + mass)
    cm_vy = (big[0]*big[5] + y_velocity[offendingparticle]*mass) / (big[5] + mass)
    #next we need to shiift into that frame of reference
    shift_big_vx = big[4] + cm_vx
    shift_big_vy = big[5] + cm_vy
    shift_small_vx = x_velocity[offendingparticle] + cm_vx
    shift_small_vy = y_velocity[offendingparticle] + cm_vy
    #reflect from the actual collision
    new_big_vx = -shift_big_vx 
    new_big_vy = -shift_big_vy
    new_small_vx = - shift_small_vx 
    new_small_vy = -shift_small_vy
    
    #now shift back into standard frame of reference
    big[4] = new_big_vx - cm_vx
    big[5] = new_big_vy - cm_vy
    x_velocity[offendingparticle] = new_small_vx -cm_vx
    y_velocity[offendingparticle] = new_small_vy -cm_vy
    
    return big, x_velocity, y_velocity

Finally we put all the functions together. First we populate the area. Then we do a time step, followed by a collision check. If the collision check indicates no collision, we go back to the time step and continue on. If the collision check indicates a collision, we use pointofcollision_finder to find how far back in time we need to move the system in order to for the particle that has intersected the large particle to not do that. Then, with this we do a backwards time step. After this step we collision check again. If there is still a particle inside the big particle, we go back to the point of collision finder function. If this collision check indicates no collision, then the velocities are updated. At this point it all goes back to the first time step. This process continues until the time hits a certain value

In [112]:
#test of everything
record = np.array([])
a,b,c,d = populate_box()
time = 0
while time < 500:

    last_collision = None
    a,b,c,d, time,big_particle = time_step(a,b,c,d,time,1,big_particle)
    f = collision_detect(a,b,big_particle)
    while f:
        neg_time = pointofcollision_finder(f,a,b,c,d,big_particle)
        a,b,c,d,time,big_particle = time_step(a,b,c,d,time,neg_time,big_particle)
        last_collision = f
        f = collision_detect(a,b,big_particle)
        print(time)
        if last_collision:
            big_particle,c,d = velocity_exchanger(big_particle,c,d,last_collision)
    print(f)
None
None
None
None
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-112-fe2c7fd8ff41> in <module>()
     10     f = collision_detect(a,b,big_particle)
     11     while f:
---> 12         neg_time = pointofcollision_finder(f,a,b,c,d,big_particle)
     13         a,b,c,d,time,big_particle = time_step(a,b,c,d,time,neg_time,big_particle)
     14         last_collision = f

<ipython-input-110-b31c1fbc3e90> in pointofcollision_finder(offendingparticle, x, y, x_velocity, y_velocity, big)
     13     B = (2*shifted_vx*shifted_x + 2*shifted_vy*shifted_y)
     14     C = (shifted_x**2 + shifted_y**2+ big[1]**2)
---> 15     time_delta = (-B - math.sqrt(B**2 - 4*A*C)) / 2*A
     16 
     17     return time_delta

ValueError: math domain error

$g(v_x) = \big ( \frac{m}{2 \pi k T} \big)^{\frac{1}{2}} e^{-m v_x^2 / 2 K T}$