Monday, July 9, 2018

Simulation #2: Ball motion in gravitational field (part 2 - program)

Previous simulation #1 was visualisation rather than real simulation, because it does not show for user any calculated physical properties. This simulation is different, because:
  • it calculates physical properties that describe physical phenomenon (ball motion in gravitational field) with units;
  • it compares results with theory, to assess calculation error.

Numerical simulations are used for solving complex equations like differential equations for which finding analytical solutions is very difficult or the solution even does not exists. Ball motion in gravitational field, on the other hand is described by well known simple physics formulas. Therefore numerical methods has no practical usage in that case, because even simple calculator can do the work. However, I introduce numerical methods for this case for educational purpose. I am going to show that we can calculate different physical properties describing ball's motion without using final formulas.

In details, the purpose of program that realizes this simulation is:
  • to make possible for user to enter easily initial values of physical properties that describe ball in $t=0$ moment (initial velocity $\vec{V}_0=[V_{x0}, V_{y0}]$ of ball and its initial height $h_0$);
  • to make possible for user to enter size of time step $dt$ used in simulation;
  • to calculate physical properties that describe ball motion with units (max height $h_{max}$, final velocity $V_{end}$, max distance $d$, time of flight $t_{end}$, time to reach max height $t_{max}$);
  • to visualize ball motion (without alignment of time in simulation with clock time, but with alignment with calculation progress);
  • to calculate delta between simulated results and theoretical results (based on formulas from part 1);
  • to make possible for user to store results from at least one simulation run to be able to compare them with results from other run.

Program/simulation presentation


Below picture (Fig 1) presents UI of the program. It contains 3 radio boxes to select 1 from 3 motions. Each one has related set of input fields in which we can set entry conditions.

Fig 1.

When simulation is finished, results values that describe ball motion are calculated (which is indicated by red outline around resize button). Results can be saved in the second table. It is very useful if we want to compare results between two different simulations. Fig. 2 shows presentation of calculated results:

Fig 2.


Below picture (Fig 3) shows how time step $dt$ size increases/decrease error of calculations. When the time step decreases, the accuracy of calculations increases and vice versa. Using second table we can compare results easily.

Fig 3.

It can be seen that ball does not hit the ground on above pictures. It is because, simulation calculates and visualizes the last step when the ball height is still above the ground: $y\geq 0$. Case $y=0$ for last step happens only for enough small time step value $dt$ or accidentally for larger $dt$ (in lucky cases). The smaller $dt$ is - the closer ball to the ground will be. $dt$ with order of magnitude $10^{-4}$ is enough to reach $y=0$ for all types of motions (all entry conditions). Below picture (Fig 4.) shows results of calculation with $dt=0.0001$:

Fig 4.

It can be seen that calculations are very accurate. Difference between theoretical and simulated values are exactly the same with the 2nd decimal place. Excellent view!

The following figure (Figure 5) shows the possibility of adjusting the angle and distance of the camera setting from the object on the scene in VPython. Change can be done by hold and drag right or both buttons of the mouse:

Fig 5.

It looks interesting and can be useful for example for more visually complex simulations (with many moving objects) like for example model of solar system.

Implementation


To write this program, I used combination of wxPython and VPython libraries. wxPython is library for making UI in Python and VPython is scientific library (for Physics mainly) that display 3D objects and animations. Objects in that meaning are for example: vectors, spheres, cubes.. They can look like 2D, if we do not move them along z-axis, however in fact they are always 3D (it can be seen on Fig. 5). I do not use tkinter library for UI like in Simulation#1, because VPython uses wxPython. VPython usually opens animation in new window and I want to present everything in only one window, so to be able to do this we need to use wxPython.

I) Program structure:

Program consists of two classes: Ball and Simulation. Beyond that, it contains couple of functions whose are related to UI. The following tables contains list of variable and methods with short description about each one:

Class: Ball
VariablesDescription
ttime
dttime step
ggravitational acceleration
x0,y0initial coordinates of the ball (for t=0)
vx0,vy0initial velocity of the ball (for t=0)
x,ycurrent ball coordinates
vx,vycurrent ball velocity
nextYauxiliary variable, it stores value of y for next calculation's interation
mysphereVpython object (sphere) which represents the ball
velocityVectorVpython object (arrow) which represents velocity vector of the ball
MethodsDescription
nextStepcalculates next ball position and velocity after time t=dt


Class: Simulation
VariablesDescription
ggravitational acceleration
ifSimulationWorksauxiliary variable of boolean type, is True if simulation is already working
simulationCalculatedParametersdictionary, where keys are physical properties that describe ball motion (hmax, Vend, d, hmin, tend, vxend, vyend) and values are their results
initialDatadictionary, stores: initial position, initial velocity and time step values
MethodsDescription
runSimulationruns simulation
stopSimulationstops simulation
initEntryDataauxiliary method, reads data from all input fields and store them in initialData dictionary
calculateEndParametersmethod calculates physical properties that describe ball motion: hmax, Vend, d, hmin, tmax, tend, vxend, vyend
getTheoreticalResultsmethod calculates theoretical values for physical properties that describe ball motion: hmax, Vend, d, tmax, tend

Table related to class Simulation does not contain all variables and methods used in implementation. Some of them are UI related and I skipped them.

You can find project files on github: https://github.com/sim-num/BallInGravityField.


II) Steps to program simulation:
  1. Set initial ball's position and velocity
  2. Calculate position and velocity for next simulation step
  3. If calculated position is above the ground accept results, if not - stop simulation
  4. Update calculated ball's physical properties
  5. Go to step 2

III) Implementation of the steps:
  • Ad 1. (Set initial ball's position and velocity)

    Initial position and velocity is set by user in proper fields in UI. Then, those values are read by method initEntryData from class Simulation and stored in dictionary initialData:
    
        def initEntryData(self):
            if radio1.GetValue():  #if radio button nr 1 is selected (is true)
                self.initialData={'x0':0,'y0':int(inputH0_1.GetValue()), 'vx0':int(inputVx0_1.GetValue()), 'vy0':int(inputVy0_1.GetValue()), 'dt':float(inputDt.GetValue())}
            if radio2.GetValue():
                self.initialData={'x0':0,'y0':int(inputH0_2.GetValue()), 'vx0':int(inputVx0_2.GetValue()), 'vy0':int(inputVy0_2.GetValue()), 'dt':float(inputDt.GetValue())}
            if radio3.GetValue():
                self.initialData={'x0':0,'y0':int(inputH0_3.GetValue()), 'vx0':int(inputVx0_3.GetValue()), 'vy0':int(inputVy0_3.GetValue()), 'dt':float(inputDt.GetValue())}
    
  • initEntryData method is called at the beginning of simulation inside runSimulation method from class Simulation (see code in Ad 4.). Then initialData dict is passed to contructor of class Ball to initialize new Ball instance:
    
    class Ball(object):
        ...
        def __init__(self, initialData, dtt):
            self.vx0 = initialData['vx0']
            self.vy0 = initialData['vy0']
            self.x0 = initialData['x0']
            self.y0 = initialData['y0']
    
    
    NOTICE: "$\ldots$" in code represents some skipped part of the code irrelevant in described context.

  • Ad 2. (Calculate position and velocity for next simulation step)

    This step is implemented by method nextStep from class Ball:
    
        def nextStep(self):
            self.nextY = self.y + self.vy * self.dt
            if self.nextY > 0:
             self.t+=self.dt
             self.vy = self.vy-self.g*self.dt
             self.x = self.x + self.vx * self.dt
             self.y =self.y + self.vy * self.dt
             ...
    
    
    $V_x$ component of ball's velocity vector is constant, therefore it is not calculated during simulation. What we need to calculate are components of position vector: $x$, $y$ and velocity component $V_y$. They can be denoted by $x(t)$, $y(t)$, $V_y(t)$, because they are time dependent. All of them are calculated numerically for each time step. It is done using above algorithm. This part of algorithm is so important that requires another part of this topic for exmplanation.
  • Ad 3. (If calculated position is above the ground accept results, if not - stop simulation)

    In method nextStep, we check if after calculation of next step, ball position is still above the ground. If yes - then method returns True, of not - then returns False, which is the indicator to stop the simulation.
  • 
        def nextStep(self):
            self.nextY = self.y + self.vy * self.dt
            if self.nextY > 0:
             self.t+=self.dt
             self.vy = self.vy-self.g * self.dt
             self.x = self.x + self.vx * self.dt
             self.y = self.y + self.vy * self.dt
             ...
             return True
            else:
             return False
    
  • Ad 4. (Update calculated ball's physical properties)

    nextStep method is called inside method runSimulation from class Simulation. After each simulation step, for which ball is still above the ground, as I mentioned previously - nextStep method returns value True. Next, this value is assigned to variable status. Its value is condition for while loop in which we invoke nextStep method and calculateEndParameters method. This last method is responsible for calculation of physical properties that are stored in simulationCalculatedParameters dictionary. The code below shows hierarchy of methods and while loop:
    
        def runSimulation(self, event=None):
            ...
            self.simulationCalculatedParameters={'hmax':-1,'vxend':-1,'vyend':-1,'Vend':-1,'d':-1,'tend':-1,'hmin':1000000}
            ...
            self.initEntryData()
            self.ourBall=gravityFieldBall.Ball(self.initialData, float(inputDt.GetValue()))
    
    
            if self.ifSimulationWorks == False:
                self.ifSimulationWorks = True
                status=True
                ...
                i=0
                ...
                while self.ifSimulationWorks and status:
                    self.calculateEndParameters()
                    ...
                    status=self.ourBall.nextStep()
                    ...
                    i += 1
            ...
            self.ifSimulationWorks=False
            return
    
    
    Code of calculateEndParameters method is presented below:
    
        def calculateEndParameters(self):
            if self.ourBall.y>self.simulationCalculatedParameters['hmax']:
                self.simulationCalculatedParameters['hmax']=self.ourBall.y
                self.simulationCalculatedParameters['tmax']=self.ourBall.t
    
            if self.ourBall.y>0 and self.ourBall.y<self.simulationCalculatedParameters['hmin'] and self.ourBall.vy<0:
                self.simulationCalculatedParameters['hmin']=self.ourBall.y
                self.simulationCalculatedParameters['d']=self.ourBall.x
                self.simulationCalculatedParameters['vxend']=self.ourBall.vx
                self.simulationCalculatedParameters['vyend']=self.ourBall.vy
                self.simulationCalculatedParameters['Vend']=math.sqrt(self.ourBall.vx**2+self.ourBall.vy**2)
                self.simulationCalculatedParameters['tend']=self.ourBall.t
    
    First if condition checks if current y is the largest value found, if yes - then it is stored in simulationCalculatedParameters dict as hmax value with corresponding tmax value to it. The second if condition checks if ball is still above the ground, if current y position component is at the same time the smallest value found . The last part of 2nd condition checks if ball is falling down (Vy velocity component is negative). We need to consider only falling part of the ball motion for searching physical properties like: $V_{end}$, $t_{end}$, $d$. hmin is auxiliary variable, used to figure out if current y position is the lowest position of the ball above the ground (during falling down part).
  • Ad 5. (Go to step 2)

    This step is realized by while loop inside runSimulation method.

IV) Simulation visualisation

This time I used VPython library for making simulation's visualisation. Because I used it for the first time here, I will list every object I used with quick description:

  • display - to combine VPython window with wxPython window, we define display object. It creates VPython scene. To create it, we put window type object from wxPython libary as constructor parameter:
    
    w = window(width=L, height=W,
               menus=False, title='Ball motion in gravity field - 2D simulation',
               style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)
    
    #create scene in VPython inside window:
    scene = display(window=w, x=10, y=105, width=600, height=500, center=(40,28,0), background=(0,0,0), autoscale=True)
    
  • label - object is used for creating label (for example white box with "h= V=" values):
    
    hV_label = label(pos=(45, 55, 0), text='h=\nV=', xoffset=1, line=0, box=True, opacity=0)
    
    Another example is axis description:
    
    labelX = text(text='x [m]', depth=0.4, color=color.white, height=1.5, pos=[ymax, -3 * tic_h], font='serif')
    
  • arrow - object is used for creating axes in XY coordinate system:
    
    xmin = 0.
    xmax = 50.
    ymin = 0.
    ymax = 55.
    
    # tick marks
    tic_dx = 5
    tic_h = 1
    self.xaxis = arrow(pos=(xmin, 0, 0), axis=(xmax+tic_dx, 0, 0), shaftwidth=0.2)        # axes
    
    self.yaxis = arrow(pos=(0, ymin, 0), axis=(0, ymax+tic_dx, 0), shaftwidth=0.2)
    
    The same object can be used to create $\vec{g}$ vector:
    
    self.gravityVector = arrow(pos=(70, 55, 0), axis=(0, -5, 0), shaftwidth=0.5, color=color.blue)
    
  • curve - I use it to create markers on the axes:
    
    for i in arange(xmin,xmax+tic_dx,tic_dx):
     tic = curve(pos=[(i,-0.5*tic_h),(i,0.5*tic_h)])
    
  • sphere - object is used for creating ball:
    
    self.mysphere = sphere(pos=vector(self.x0, self.y0, 0), radius=self.R, color=color.red)
    
Each object has some attributes like for example visible. I use visible attribute to show or hide vector or sphere:

         if showVelcheck.GetValue()==True:
            self.ourBall.velocityVector.visible=True
         else:
            self.ourBall.velocityVector.visible=False


Summary

Simulation is done according to the plan. It shows that simple formulas can be calculated in other way - numerically. Simulation can be extended by adding wind factor or air resistance. I will add them in next parts. Algorithm used for calculations can be also improved. Its description and improvement methods will be shown in part 3. During this simulation I learnt that VPython is powerful framework for making visualisation. Therefore, I am going to use it for the future simulations.

Monday, April 30, 2018

Simulation #2: Ball motion in gravitational field (part 1 - theory)

In this simulation, I would like to take up the subject of the ball's movement in the Earth's gravitational field. We will calculate real values of physical quantities with their units. I know that it is simple simulation, however I treat it as base for more complicated simulations. During implementation, I would like to focus also on learning Python's variant (for physical simulations) called VPython. It will be used in this simulation.

First, a bit of theory. We can distinguish couple of classic examples of physical body motion in gravitational field:
  1. upward projection and free fall
  2. horizontal projection
  3. angular projection
We assume that ball does not collide with any barrier and no other forces are acting on it. In other words - there is only Earth's surface, gravity and the ball with or without initial velocity. What is more, this assumption is valid for all motions described in this post.

Below is description of each motion:

Ad 1. Upward projection and free fall

Depending on initial conditions (position and velocity), we can distinguish the following combination of those 2 motions:
  1. upward motion and then free fall
  2. only free fall
Below picture (fig 1) presents both types of motion:

Fig 1.

  • Ad a. Ball is moving upward and then does free fall, when its initial velocity:
  • $\vec{V}_0=[0,V_y]$, where $V_y\gt 0$.

    We can define key parameters that describe ball movement:

    $t_0$ - time when movement starts (here $t_0=0$),
    $t_{max}$ - time, when ball reaches highest height above the Earth's surface,
    $t_{end}$ - time, when ball falls to the ground (moment of contact with Earth's surface),
    $E_k$ - kinetic energy of ball,
    $E_p$ - potential energy of ball,
    $V_0$ - initial speed of ball ($V_0=|\vec{V_0}|$),
    $V_{end}$ - speed of ball, when it contacts the ground,
    $h$ - height of ball above the ground,
    $h_0$ - initial height of ball laying on the ground. It equals to its radius $r$. If $r\ll h_{max}$, then $h_0\approx0$,
    $h_{max}$ - max height of ball above the ground,
    $g$ - acceleration of gravity (9.8 $m/s^2$). In our simulation we omit force due to air resistance. We can use energy conservation law, where only factors are potential and kinetic energy. Therefore: $$ E_p(t_{max})=E_k(t_0)=E_k(t_{end}) $$ $$ mgh_{max}=\frac{mV^2_0}{2}=\frac{mV^2_{end}}{2} $$ $$ h_{max}=\frac{V^2_0}{2g}=\frac{V^2_{end}}{2g} \tag{1}$$ We can come to conclusion: $$ V_0=V_{end}\tag{2}$$ Let's calculate $t_{max}$, $t_{end}$ now. To do this, we need to use formula for $h$: $$h=h_0+V_0t-\frac{gt^2}{2} \tag{3}$$ $t_{max}$ corresponds to $h_{max}$, therefore: $$h_{max}=h_0+V_0t_{max}-\frac{gt_{max}^2}{2} \tag{4}$$ Now, equation (1) needs to be put into formula (4). Finally $t_{max}$ is: $$t_{max}=\frac{V_0}{g} \tag{5}$$ Condition for $t_{end}$ looks like this: $$h_0=h_0+V_0\cdot t_{end}-\frac{gt_{end}^2}{2}$$ Therfore after simple calculations, $t_{end}$ is: $$t_{end}=\frac{2V_0}{g} \tag{6}$$ After comparison of $t_{max}$ with $t_{end}$, we can conclude that motion of ball, when it is first moving upward and then falls to the ground is completely symmetrical.

  • Ad b. Ball does only free fall, when its initial velocity is $\vec{0}$ and initial position is above the ground.
  • Parameters that describe the ball's motion are:
    $t_0$ - time when free fall motion starts,
    $t_{end}$ - time, when ball falls to the ground (moment of contact with Earth's surface),
    $V_{end}$ - speed of ball, when it contacts the ground,
    $h$ - height of ball above the ground,
    $h_0$ - initial height of ball above the ground.
    Height $h$ is given by the formula: $$h=h_0-\frac{gt^2}{2} \tag{7}$$ We need to find parameters: $t_{end}$ and $V_{end}$. Condition for $t_{end}$ is: $$0=h_0-\frac{gt_{end}^2}{2}$$ Finally, $t_{end}$ is: $$t_{end}=\sqrt{\frac{2h_0}{g}} \tag{8}$$ Condition for $V_{end}$ is: $$mgh_0=\frac{mV_{end}^2}{2}$$ Finally, $V_{end}$ is: $$V_{end}=\sqrt{2gh_0} \tag{9}$$


Ad 2. Horizontal projection


Below picture (fig 2) presents this type of motion:

Fig 2.

Ball is moving in gravitational field according to horizontal projection motion, when its:
  • initial position is above the ground level,
  • has initial horizontal velocity $V_0$.
Ball's velocity is sum of vectors: $$\vec{V}=\vec{V}_0+\vec{V}_g, \tag{10}$$ where:
$\vec{V}_0=[V_0,0]$ - initial horizontal velocity, $\vec{V}_g=-gt$ - velocity caused by gravitational acceleration.

Parameters, that describe horizontal projection:
$t_0$ - time when horizontal projection starts,
$t_{end}$ - time, when ball falls to the ground (moment of contact with Earth's surface),
$V$ - ball's speed,
$V_{end}$ - speed of ball, when it contacts the ground,
$h$ - height of ball above the ground,
$h_0$ - initial height of ball above the ground,
$d$ - distance that ball moves.

Height $h$ is given by the same formula as free fall (equation (7)).
We need to find parameters: $t_{end}$, $V$, $V_{end}$, $d$.
$t_{end}$ is actually identical with $t_{end}$ for free fall motion (given by equation (8)) Based on formula (10), we can calculate speed of ball $V$: $$V=|\vec{V}|=\sqrt{V_0^2+(gt)^2} \tag{11}$$ Condition for $V_{end}$ is energy conservation law: $$mgh_0+\frac{mv_0^2}{2}=\frac{mV_{end}^2}{2}$$ Finally, after calculations we get: $$V_{end}=\sqrt{V_0^2+2gh_0} \tag{12}$$ Distance $d$ that balls reaches is pretty easy to figure out. It is just $V_0t_{end}$: $$d=V_0\sqrt{\frac{2h_0}{g}}. \tag{13}$$.

Ad 3. Angular projection


Below Fig 3. presents this type of ball's motion:

Fig 3.

Parameters, that describe angular projection are:
$t_0$ - time when angular projection starts,
$t_{max}$ - time after which ball reaches $h_{max}$,
$t_{end}$ - time, when ball falls to the ground (moment of contact with ground level),
$V$ - ball's speed,
$\vec{V_0}$ - initial ball's velocity vector,
$V_0$ - initial speed of ball,
$\alpha$ - angle between initial velocity vector $\vec{V}_0$ and ground level,
$V_{end}$ - speed of ball, when it contacts the ground (in $t=t_{end}$ moment),
$h$ - height of ball above the ground,
$h_0$ - initial height of ball laying on the ground. It equals to its radius $r$. If $r\ll h_{max}$, then $h_0\approx0$,
$h_{max}$ - max height of ball above the ground,
$d$ - distance that ball moves.

Initial velocity vector $\vec{V}_0$ can be present in general form: $$\vec{V}_0=[V_{0x},V_{0y}].$$ We can present initial velocity vector's components by $\alpha$ angle and its length $V_0$ as follows: $$ \begin{cases} V_{0x}=V_{0}\cdot cos(\alpha)\\ V_{0y}=V_{0}\cdot sin(\alpha) \end{cases} \tag{14}$$ Therefore, height of ball $h$ is: $$h=\underbrace{V_{0}\cdot sin(\alpha)}_{V_{0y}}\cdot t -\frac{gt^2}{2} \tag{15}$$ To calculate $h_{max}$ we use as usual energy conversation law: $$mgh_{max}+\frac{m\overbrace{V_0^2cos^2(\alpha)}^{V_{0x}^2}}{2}=\frac{mV_0^2}{2}$$ After basic calculations, we get: $$h_{max}=\frac{\overbrace{V_0^2\cdot sin^2(\alpha)}^{V_{0y}^2}}{2g} \tag{16}$$ To calculate $V_{end}$ we use energy conversation law again: $$\frac{mV_0^2}{2}=\frac{mV_{end}^2}{2}.$$ Based on above equation, we can easily conclude that: $$V_{end}=V_0. \tag{17}$$ To calculate $t_{max}$ we use equation (15) to create proper condition: $$h_{max}=V_{0y}t_{max}-\frac{gt_{max}^2}{2}. $$ $h_{max}$ is known because we calculate it before. It can be put to above equation: $$\frac{V_{0y^2}}{2g}=V_{0y}t_{max}-\frac{gt_{max}^2}{2}. $$ Finally, after basic calculations, $t_{max}$ is: $$t_{max}=\frac{V_{0y}}{g}. \tag{18}$$ Based on equation (15), we can write condition for $t_{end}$ parameter: $$0=V_{0y}\cdot t_{end}-\frac{g\cdot t_{end}^2}{2}.$$ After simple transformations we get: $$t_{end}=\frac{2V_{0y}}{g}. \tag{19}$$ We can easily figure out that $t_{end}=2\cdot t_{max}$, which means that angular projection motion is completely symmetrical.
Distance of ball $d$ is given by similar equation to eq(13): $$d=V_{0x}\cdot t_{end}= V_0cos(\alpha) \cdot \frac{2V_{0y}}{g}.$$ After simple calculations we get final version: $$d=\frac{V_0^2}{g}sin(2\alpha). \tag{20}$$

Friday, February 16, 2018

Simulation #1: 2D Pool game (part 2 - program)

In this article, I am going to describe program, that realizes 2D pool simulation.

Language that I chose is Python. I like it, because writing process is fast: it has concised syntax and many built in helpful methods. I am aware that it doesn't belong to fastest languages, but I think that it is completely enough fast to realize this task.

Program consists of 3 classes: Ball, Table and Simulation. Simulation class realize GUI besides running simulation. Below, I described in the form of table, content of each class:

Class: Ball
VariablesDescription
ttime
dttime step
x0,y0initial coordinates of the ball (t=0)
vx0,vy0initial velocity of the ball (t=0)
x,ycurrent ball coordinates
vx,vycurrent ball velocity
Rradius of the ball
idball number (id)
colorball color, for example "blue"
collisionBallNrid of ball that collids with current ball
ifCollisionCompletedvariable of Boolean type, returns True if collision with ball is completed
MethodsDescription
nextStepcalculates next ball position after dt (according to equation (0) from part 1)
ifCollisionWithBallcheck if collision with ball happened, if yes - it calculates new velocities for it
ifCollisionWithEdgecheck if collision with edge of the table happened, if yes - it calculates new velocities for it
setInitialBallVelocityset random initial velocity for the ball

Class: Table
VariablesDescription
L, Wtable dimentions
borderCoordinatesXarray contains 2 edge values of x variable - that are: 0 and L
borderCoordinatesYarray contains 2 edge values of y variable - that are: 0, W
Nnumber of balls
ballsarray that contains Ball objects
colorsarray contains name of colors
MethodsDescription
setInitialBallsPositionsset initial random positions for all balls, in a way that they do not overlap themself
actualPositionrun next step of simulation, i.e. run methods: nextStep, ifCollisionWithEdge, ifCollisionWithBall, returns actual position after the step
actualVelocityauxiliary method, returns actual velovity of selected ball
getInitialPositionsAndVelocitiesauxiliary method, returns initial velocities of all balls


Class: Simulation
VariablesDescription
ttime
ifSimulationWorksauxiliary variable of boolean type
ifPauseauxiliary variable of boolean type
MethodsDescription
getNDtgets N and dt variables from input fields
runSimulationruns simulation
stopstops simulation

The program window and the working simulation are finally presented as follows:
You can find project source and executable file on github: https://github.com/sim-num/2DpoolGameSimulation

Steps to program simulation:

  1. Set initial values for each ball (position and velocity)
  2. Calculate new position according to equation (0) from part 1
  3. Check if collision with another ball or with the edge of the table occures (if yes - calculate new velocity)
  4. Go back to step 2.


  • Ad 1. Velocity initialisation (in class Ball):
    
        def setInitialBallVelocity(self):
            self.vx0 = random.randint(-10, 10)
            self.vy0 = random.randint(-10, 10)
            self.vx = self.vx0
            self.vy = self.vy0
           
     
    Initial velocity vector components are initialised with random integer values from range (-10, 10). Initialisation of position vector is realised by following method (in class Table):
    
        def setInitialBallsPositions(self, balls):
    
            for i in balls:
                while i.x0 == -1 and i.y0 == -1:
                    x = random.randint(i.R, Table.L - i.R)
                    y = random.randint(i.R, Table.W - i.R)
                    for j in balls:
                        if i.id != j.id and x <= (j.x + 2 * j.R) and y <= (j.y + 2 * j.R) and x >= (
                                j.x - 2 * j.R) and y >= (j.y - 2 * j.R):
                            x = -1
                            y = -1
                    i.x0 = x
                    i.y0 = y
                    i.x = i.x0
                    i.y = i.y0
           
     
    To initialise ball position, it is need to know other balls positions to avoid overlapping. This is the reason why position is initialised in class Table instead of Ball. We search for not overlapped position until it is found (while loop is used for that reason).
  • Ad 2. Calculation of new position is performed by following method (in class Ball):
  • 
        def nextStep(self):
            self.x = self.x + self.vx * Ball.dt
            self.y = self.y + self.vy * Ball.dt
           
     
    As you can easily see, equation (0) is programmed inside method nextStep. Second method that calculate next position is method called actualPosition inside class Table. It just calls method nextStep as well as other methods related to collision handling (see point 3).
    
        def actualPosition(self, ballNr, t):
    
            for i in range(0, self.N):
                self.balls[i].nextStep()
                self.balls[i].ifCollisionWithEdge()
                self.balls[i].ifCollisionWithBall(self.balls)
            return [self.balls[ballNr].x, self.balls[ballNr].y]
           
     
    After ball move due to calling nextStep method, ifCollisionWithEdge and ifCollisionWithBall methods modify velocity vector appropriately if the collision conditions are met.
  • Ad 3. Methods ifCollisionWithEdge and ifCollisionWithBall as said before are responsible for collision handling. Both belongs to class Ball. First method ifCollisionWithEdge is responsible for collision handling with edge. According to theory (see case 1 in part 1), depending on whether ball collids with vertical or horizontal edge of the table - respectively $V_x$ or $V_y$ component changes:
  • 
        def ifCollisionWithEdge(self):
            if self.x + self.R >= Table.borderCoordinatesX[1] and self.vx > 0:
                self.vx = -self.vx
            if self.x - self.R <= Table.borderCoordinatesX[0] and self.vx < 0:
                self.vx = -self.vx
            if self.y - self.R <= Table.borderCoordinatesY[0] and self.vy < 0:
                self.vy = -self.vy
            if self.y + self.R >= Table.borderCoordinatesY[1] and self.vy > 0:
                self.vy = -self.vy
           
     
    ifCollisionWithBall is most complex method in program. Bellow is description step by step, how is it programmed (flag variable called ifCollisionCompleted is initialised in __init__ class method with value False):
    1. Calculate distance between ball and another ball (for loop over all balls)
    2. If distance is smaller than 2R and ifCollisionCompleted is False, then go to 3. else set ifCollisionCompleted to False for both balls and go back to step 1
    3. Calculate $\alpha$ angle. It is related to arctan function as shown in part 1.
    4. Calculate normal and tangent component of velocity vector for both collided balls (use equation (1b) from part 1.)
    5. Swap normal component of velocity vector between balls.
    6. Set ifCollisionCompleted flag to True for both balls.
    
        def ifCollisionWithBall(self, balls):  
            Xcoll = 0
            Ycoll = 0
            distanceFromBall = 99999
            for i in balls:
                if i.id != self.id:
                    distanceFromBall = sqrt((self.x - i.x) ** 2 + (self.y - i.y) ** 2)
                    if distanceFromBall <= 2 * self.R and self.ifCollisionCompleted == False:
                        self.collisionBallNr = i.id
                        Xcoll = (self.x + i.x) / 2
                        Ycoll = (self.y + i.y) / 2
                        alpha = 3.14159 / 2 - atan(float(self.y - i.y) / (self.x - i.x))  # collision angle
    
                        Vs = cos(alpha) * self.vx - sin(alpha) * self.vy
                        Vn = sin(alpha) * self.vx + cos(alpha) * self.vy
                        Vs_i = cos(alpha) * i.vx - sin(alpha) * i.vy
                        Vn_i = sin(alpha) * i.vx + cos(alpha) * i.vy
                        Vn_po = Vn_i
                        Vni_po = Vn
                        self.vx = cos(alpha) * Vs + Vn_po * sin(alpha)
                        self.vy = cos(alpha) * Vn_po - sin(alpha) * Vs
                        i.vx = cos(alpha) * Vs_i + Vni_po * sin(alpha)
                        i.vy = cos(alpha) * Vni_po - sin(alpha) * Vs_i
                        i.ifCollisionCompleted = True
                        self.ifCollisionCompleted = True
                    elif distanceFromBall > 2 * self.R and self.ifCollisionCompleted == True and i.id == self.collisionBallNr:
                        self.ifCollisionCompleted = False
                        i.ifCollisionCompleted = False
           
     
    Please notice that flag ifCollisionCompleted is static variable, which means that it is sufficient to set it by any of 2 collided balls. It should be like that, because if first ball ends the collision - second should know it.

Simulation visualisation


To visulize simulation I used Tkinter python library. Visualisation is implemented in method runSimulation, which belongs to class Simulation.

    def runSimulation(self):
        self.getNDt()
        self.ifPause = False
        if self.ifSimulationWorks == False:
            self.ifSimulationWorks = True
            while self.ifSimulationWorks:
                position = []
                velocity = []
                N = self.N
                for i in range(0, N):
                    position.append(self.Table.actualPosition(i, self.t))
                    velocity.append(self.Table.actualVelocity(i, self.t))
                ballSymbol = []
                velocityWektor = []
                for i in range(0, N):
                    ballSymbol.append(canvas.create_circle(position[i][0], position[i][1], 25, width=2,
                                                           fill=self.Table.balls[i].color, tags=('ball' + str(i))))
                canvas.update()
                canvas.after(40)
                for i in range(0, N):
                    canvas.delete(ballSymbol[i])

                self.t += 1
       
 
position and velocity arrays are used to keep position and velocity vector for all balls and t moment of simulation. Based on those values we can draw balls on screen on those positions and draw velocity vectors (however it is not implemented now). To draw balls we can use modified create_oval function:

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
       
 
Modified function allows you to draw circles in the indicated position, which is the center of circle as well. Created ball symbol is stored in ballSymbol array. canvas.after(40) line causes sleep of 40 ms. It means that each t moment will last 40 ms. This value can be changed of course. If value is smaller then simulation will be more smooth, but slower because more calculations will be performing. Next, section:

                for i in range(0, N):
                    canvas.delete(ballSymbol[i])       
 
deletes all balls for t moment to make place for balls from t+1 moment.

Summary


Simulation is done according to my own idea. I hope it can be improved, code can be more concised. Maybe flag variables are not needed in this case. I am fan of simple code. I am really open to see other ways. Please leave comment if you have any suggestions.