0

I'm currently trying to make two particles in a gaseous state collide, but I'm not too sure how to write a code that will make this happen, I have a rough draft of what I could do but I don't really know how I could properly implent it (lines 43-54 i.e the code after the comment #collision of i with another particle j) after the collision occurs I wanted them to go in the opposite direction with different speedssince I will be computing the kinetic energy depending on the mass of the particles. The goal of the project is to basically show the conservation of kinetic energy of multiple particles of differnet parameters(velocity,mass,direction) moving in gaz. Here's my current code, any help would be greatly appreciated!!:

from tkinter import *
from random import *
myHeight=400
myWidth=600
mySpeed=10

x1=5
y=5
radius1=20

x2=7
y2=4
radius1=20

x=60
width=40
length=10

global particules 
particules = []

def initialiseBall(dx,dy,radius,color):
    b = [myWidth/2,myHeight/2, dx, dy, radius]
    particules.append(b)
    k = myCanvas.create_oval(myWidth/2-radius, myHeight/2,\
                        myWidth/2+radius,myHeight/2+radius,\
                        width=2,fill=color)
    print(k)

def updateBalls():
  N = len(particules)

  for i in range(N):
    particules[i][0] += particules [i][2]
    particules[i][1] += particules [i][3]

    # collision of i with the walls
    if particules[i][0]<0 or particules[i][0]>=myWidth:
          particules[i][2] *= -1
    if particules[i][1]<0 or particules[i][1]>=myHeight:
          particules[i][3] *= -1

    #collision of i with another particle j
    # for j in range(N):
    #   if j != i:
        # compare the position of i and j
        # dij = ...
        # if dij ... :
          #if collision, compute the normal vector
          #change velocities

  #  if particules[i][1]<=particules[i][1]:
  #    particules[i][2] *= -1
  #  r = particules[i][4]

    myCanvas.coords(i+1, particules[i][0]-particules[i][4],
    particules[i][1]-particules[i][4], 
    particules[i][0]+particules[i][4], 
    particules[i][1]+particules[i][4])

def animation ():
    updateBalls()
    myCanvas.after(mySpeed, animation)

def kineticenergy(mass, velocity):
  Ec = 1/2 * mass * velocity ** 2
  return Ec

# def averagetrip(number, radius):
#   # 
#   #
#   # 
#   #

mainWindow=Tk()
mainWindow.title('particles reservoir')

myCanvas = Canvas(mainWindow, bg = 'grey', height = myHeight, width = myWidth)
myCanvas.pack(side=TOP)

# create 2 particules 
initialiseBall(-1,0, 50, 'red')
initialiseBall(1,0, 50, 'blue')

print(particules)

'''
N = 100
for n in range(N):
  initialiseBalle(-1 ,0, randint(5,10), 'red')
'''

animation()
mainWindow.mainloop()
Rii22
  • 5
  • 1
  • To check if 2 circles touch/intersect use this formula: `(x2 - x1)**2 + (y2 - y1)**2 <= (radius1 + radius2)**2`. Are you using the [coefficient of restitution](https://en.wikipedia.org/wiki/Coefficient_of_restitution)? Or are you assuming perfectly elastic collisions? – TheLizzard Apr 07 '21 at 13:58
  • Ok got it, I am using the coefficient of restitution. Do you, by any chance know where in the code I would add the formula to make it work? – Rii22 Apr 08 '21 at 07:08
  • A year ago I tried making a program like that but I ran into problems like [this one](https://stackoverflow.com/q/54844710/11106801). I am going to try to do it again. – TheLizzard Apr 08 '21 at 08:43
  • Made it and it works. I will finish adding comments and I will post the answer in about 1h – TheLizzard Apr 08 '21 at 11:34

1 Answers1

0

Try this:

from math import sqrt, sin, cos
import tkinter as tk
import time

# For more info about this read: https://stackoverflow.com/a/17985217/11106801
def _create_circle(self, x, y, r, **kwargs):
    return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
tk.Canvas.create_circle = _create_circle


WINDOW_WIDTH = 500
WINDOW_HEIGHT = 500

# The coefficient of restitution
# Set to 1 for perfectly elastic collitions
e = 1


class Ball:
    def __init__(self, mass:float, r:float, x:float, y:float,
                 vx:float=0, vy:float=0, **kwargs):
        """
        This is a class that defines what a ball is and how it interacts
        with other balls.

        Arguments:
            mass:float    # The mass of the ball
                          ------------------------------------------------------
            r:float       # The radius of the ball, must be >0
                          ------------------------------------------------------
            x:float       # The x position of the ball
                          #   must be >0 and <WINDOW_WIDTH
            y:float       # The y position of the ball
                          #   must be >0 and <WINDOW_HEIGHT
                          ------------------------------------------------------
            vx:float      # The x velocity of the ball
            vy:float      # The y velocity of the ball
                          ------------------------------------------------------
            **kwargs      # All of the args to be passed in to `create_circle`
        """
        self.m = mass
        self.r = r
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.kwargs = kwargs

    def display(self, canvas:tk.Canvas) -> int:
        """
        Displays the ball on the screen and returns the canvas_id (which
        is a normal python int).
        """
        canvas_id = canvas.create_circle(self.x, self.y, self.r, **self.kwargs)
        return canvas_id

    def move(self, all_balls:list, dt:float) -> (float, float):
        """
        This moves the ball according to `self.vx` and `self.vy`.
        It also checks for collisions with other balls.

        Arguments:
            all_balls:list   # A list of all balls that are in the
                             # simulation. It can include this ball.
                             ---------------------------------------------------
            dt:float         # delta time - used to move the balls

        Returns:
            dx:float         # How much the ball has moved in the x direction
            dy:float         # How much the ball has moved in the y direction

        Note: This function isn't optimised in any way. If you optimise
        it, it should run faster.
        """

        # Check if there are any collitions:
        for other, _ in all_balls:
            # Skip is `ball` is the same as this ball
            if id(other) == id(self):
                continue
            # Check if there is a collision:
            distance_squared = (other.x - self.x)**2 + (other.y - self.y)**2
            if distance_squared <= (other.r + self.r)**2:
                # Now the fun part - calulating the resultant velocity.
                # I am assuming you already know all of the reasoning
                # behind the math (if you don't ask physics.stackexchange.com)

                # First I will find the normal vector of the balls' radii.
                # That is just the unit vector in the direction of the
                #  balls' radii
                ball_radii_vector_x = other.x - self.x
                ball_radii_vector_y = other.y - self.y
                abs_ball_radii_vector = sqrt(ball_radii_vector_x**2 +\
                                             ball_radii_vector_y**2)
                nx = ball_radii_vector_x / abs_ball_radii_vector        **2*2
                ny = ball_radii_vector_y / abs_ball_radii_vector        **2*2

                # Now I will calculate the tangent
                tx = -ny
                ty = nx

                """ Only for debug
                print("n =", (nx, ny), "\t t =", (tx, ty))
                print("u1 =", (self.vx, self.vy),
                      "\t u2 =", (other.vx, other.vy))
                #"""

                # Now I will split the balls' velocity vectors to the sum
                #  of 2 vectors parallel to n and t
                # self_velocity  = λ*n + μ*t
                # other_velocity = a*n + b*t
                λ = (self.vx*ty - self.vy*tx) / (nx*ty - ny*tx)
                # Sometimes `tx` can be 0 if so we are going to use `ty` instead
                try:
                    μ = (self.vx - λ*nx) / tx
                except ZeroDivisionError:
                    μ = (self.vy - λ*ny) / ty

                """ Only for debug
                print("λ =", λ, "\t μ =", μ)
                #"""

                a = (other.vx*ty - other.vy*tx) / (nx*ty - ny*tx)
                # Sometimes `tx` can be 0 if so we are going to use `ty` instead
                try:
                    b = (other.vx - a*nx) / tx
                except ZeroDivisionError:
                    b = (other.vy - a*ny) / ty

                """ Only for debug
                print("a =", a, "\t b =", b)
                #"""

                self_u = λ
                other_u = a

                sum_mass_u = self.m*self_u + other.m*other_u
                sum_masses = self.m + other.m

                # Taken from en.wikipedia.org/wiki/Inelastic_collision
                self_v = (e*other.m*(other_u-self_u) + sum_mass_u)/sum_masses
                other_v = (e*self.m*(self_u-other_u) + sum_mass_u)/sum_masses

                self.vx = self_v*nx + μ*tx
                self.vy = self_v*ny + μ*ty
                other.vx = other_v*nx + b*tx
                other.vy = other_v*ny + b*ty

                print("v1 =", (self.vx, self.vy),
                      "\t v2 =", (other.vx, other.vy))

        # Move the ball
        dx = self.vx * dt
        dy = self.vy * dt
        self.x += dx
        self.y += dy
        return dx, dy


class Simulator:
    def __init__(self):
        self.balls = [] # Contains tuples of (<Ball>, <canvas_id>)
        self.root = tk.Tk()
        self.root.resizable(False, False)
        self.canvas = tk.Canvas(self.root, width=WINDOW_WIDTH,
                                height=WINDOW_HEIGHT)
        self.canvas.pack()

    def step(self, dt:float) -> None:
        """
        Steps the simulation as id `dt` seconds have passed
        """
        for ball, canvas_id in self.balls:
            dx, dy = ball.move(self.balls, dt)
            self.canvas.move(canvas_id, dx, dy)

    def run(self, dt:float, total_time:float) -> None:
        """
        This function keeps steping `dt` seconds until `total_time` has
        elapsed.

        Arguments:
            dt:float          # The time resolution in seconds
            total_time:float  # The number of seconds to simulate

        Note: This function is just for proof of concept. It is badly written.
        """
        for i in range(int(total_time//dt)):
            self.step(dt)
            self.root.update()
            time.sleep(dt)

    def add_ball(self, *args, **kwargs) -> None:
        """
        Adds a ball by passing all of the args and keyword args to `Ball`.
        It also displays the ball and appends it to the list of balls.
        """
        ball = Ball(*args, **kwargs)
        canvas_id = ball.display(self.canvas)
        self.balls.append((ball, canvas_id))


app = Simulator()
app.add_ball(mass=1, r=10, x=20, y=250, vx=100, vy=0, fill="red")
app.add_ball(mass=1, r=10, x=240, y=250, vx=0, vy=0, fill="blue")
app.add_ball(mass=1, r=10, x=480, y=250, vx=0, vy=0, fill="yellow")
app.run(0.01, 10)

That code has a lot of comments describing how it works. If you still have any questions, ask me. Also the move method isn't optimised. The run method isn't great and can throw an error if it's still running and the user closes the window. I will try to find a better approch for that method. If you find any bugs, please let me know. I will try to fix them.

TheLizzard
  • 7,248
  • 2
  • 11
  • 31
  • thank you so much!! the comments are really helpful, i dont have any questions for now!:) – Rii22 Apr 08 '21 at 15:28