# Colliding balls in a 2D box

• Python
jedishrfu
Mentor
It's my experience that it is possible to stare at code for a long, long time without spotting a simple error. The real trick in debugging is to develop techniques that allow one to narrow down to the specific location of the problem. It may take much less time to stuff a lot of prints into code than it does to find a bug by staring at code.
But before you do that, please put it into a good interpreter/compiler and eliminate all warnings.
Spot on!

DaveC426913
Gold Member
Once you've done all the above, start the sim at the beginning and check if/where it's first going awry as compared to the expected tracking.

Are the initial positions identical?
Are the initial velocities identical?
Are the initial trajectories identical?
And - this one will be harder, because the plot doesn't show it - are they decelerating correctly?
Is the first rail bounce outcome identical?
Is the first collision outcome identical?

Where exactly does your result first deviate from the expected result?
This will help you narrow down where to look for bugz in teh codez.

FactChecker
Gold Member
I am surprised that it compiles with this mistake.
I am surprised too

But before you do that, please put it into a good interpreter/compiler and eliminate all warnings.
I am using VS Code.

1) The def statement (line 16) shows collision_test function with two arguments. No args inline 75. Even assuming global variables, the function call does not match the definition.
When you guys spot the error, I was also surprised that my code worked without giving any error, however when I run the code in different compilers it works.

Try this code
Python:
def motion(a):
return 2*a

if motion:
print("yey")
It works in VS code, or online compilers.

I don't know how but it does. Probably function names are considered as True (?)
It will help to write unit tests for each of your functions before attempting to debug the program as a whole.
I already did that. It seems correct as far as I know

Have you tested this condition? Should not line 75 read
if collision_test (arg1, arg2)
if collision_test (arg1, arg2):
....

and

if collision_test (arg1, arg2) == True

are the same, its just writing "True" in an explicit form. I sometimes use the other one. But professional coders prefer to write implicitly I guess.

The answer will be accepted as correct within a range of ±50 for each pair of coordinate.

Also, dt is very important (I guess someone pointed out the importance of this before )

check if/where it's first going awry as compared to the expected tracking.

Are the initial positions identical?
Are the initial velocities identical?
Are the initial trajectories identical?
That's kind of the problem it's not obligated to be identical but I guess they should give somewhat similar results (?)

I tested a simple case where the data was "100 50 300 50 100 0 0 0 "
So Pa = [100, 50], Pb = [300, 50], Va = [100 0], Vb = [0, 0]

This means that when the ball "a" hits the ball "b", the ball "a" will stop and "b" will start to move with the same speed.

Here is the graph of it which I made by running my code.

as you can see the ball "a" stops ( the red line) after hitting the ball "b" ( the green dashed line)
----------------

Now our actual data was
235 124 365 176 181 34 -34 -14

I am getting kind of different images for different dt values. However, it also seems there are patterns in it.

In general, the answer is accepted when each value is within a range of the ±50 from the answer. The answer is 399 104 106 62

I am getting something like
355.7245911085971 202.9357121674485 109.5838027447148 30.913552143226795
or
399.8285196503435 194.65099630877506 173.18316603478155 98.13456362376074
or
355.2222801476294 203.03006892254314 86.53567162928971 74.22940763646008

So only Pa[1] value is off the range for an accepted answer.

Last edited:
Gold Member
One thing to change is from the function atan to atan2. Only atan2 keeps track of the quadrant correctly.
I noticed an interesting thing. When I dont use atan2 (and use atan()) Pa[0], Pb[0] and Pb[1] are near the correct value but Pa[1} is not within the range of the accepted value. For instance

355.7245911085971 202.9357121674485 109.5838027447148 30.913552143226795

However, when I use atan2(), Pa[1] becomes an acceptable value but this time Pa[0], Pb[0] and Pb[1] are not within the range of the accepted value (sometimes they are but sometimes not)

For instance,
120.63023480435133 102.51617670357922 126.89167814780772 87.78085456018776

423.0257577924453 131.3197556076701 433.00009977473553 122.80783144468634

88.07944536334713 70.8248555671061 204.2954606777806 127.95018019597957

350.04117035554316 117.60994360228256 351.7414000498847 119.25903023712435

(all data values represent Pa[0], Pa[1], Pb[0], Pb[1] repectively)

FactChecker
Gold Member
When you guys spot the error, I was also surprised that my code worked without giving any error, however when I run the code in different compilers it works.

Try this code
Python:
def motion(a):
return 2*a

if motion:
print("yey")
It works in VS code, or online compilers.

I don't know how but it does.
I don't know what to think about this. I am not a Python programmer. I would not trust a program that accepted that as valid code.
That's kind of the problem it's not obligated to be identical but I guess they should give somewhat similar results (?)

I tested a simple case where the data was "100 50 300 50 100 0 0 0 "
So Pa = [100, 50], Pb = [300, 50], Va = [100 0], Vb = [0, 0]
I assume that he means that the initial conditions of the simulation must match the initial conditions of the actual data.
So only Pa[1] value is off the range for an accepted answer.
View attachment 248210
The green line makes some unexplained changes of direction. It would be nice to know exactly what was going on when that happened. That is why I keep trying to encourage you to dump as much data as possible into a file. Then you could go to that part of the data file and see why the direction changed.

FactChecker
Gold Member
However, when I use atan2(), Pa[1] becomes an acceptable value but this time Pa[0], Pb[0] and Pb[1] are not within the range of the accepted value (sometimes they are but sometimes not)
I'm not sure what you mean by "acceptable value". The function atan2 will return the correct angle. What you do with that angle is up to you.

Gold Member
I'm not sure what you mean by "acceptable value". The function atan2 will return the correct angle. What you do with that angle is up to you.
When I use atan2 for dt = 0.001 I get
526.4046809656061 170.8742588241399 445.23248886773865 256.0652790027961
But for atan I get
341.36661845405075 44.75395042783804 338.65671584881693 84.45754194958526

I dont know why this happens.

The green line makes some unexplained changes of direction
I guess thats the part where two particles collide. Since they have a radius of 20. The collision will happen when they are near then 40. Since we are plotting these datas on the graph this change might have looked like a direction change.
I assume that he means that the initial conditions of the simulation must match the initial conditions of the actual data.
I am just copying the initial conditions copying them and putting them on a file to run the code. Theres no mistake in there.
I don't know what to think about this. I am not a Python programmer. I would not trust a program that accepted that as valid code.
Python:
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
>>> def motion(a):
...     return 2 * a
...
>>> if motion:
...     print("yey")
...
yey
>>>
Its in the core of the python. I mean it somehow works. Its of course wrong to use it like that... Weird side of the python :p

you to dump as much data as possible
What do you mean by dump as much as data. Like giving some random meaningful inputs and see what happens ?. Also I couldnt do it before since one of my fucntions were wrong...
I tried a simple case for the linear momentum conservation now I ll try with introcuding some angles.. and we will see.

Okay I ll try that

DavidSnider
Gold Member
When you use if on a non-boolean object in python it converts it to a boolean

>>> [(i,bool(i)) for i in [None, 0, 0.0, 3.14, "abc", "", 'a', '',True, False, [1,2,3],[]]]
[(None, False), (0, False), (0.0, False), (3.14, True), ('abc', True), ('', False), ('a', True), ('', False), (True, True), (False, False), ([1, 2, 3], True), ([], False)]

in the case of the motion function it is the same as saying "if motion(x) != 0"

Arman777
FactChecker
Gold Member
When I use atan2 for dt = 0.001 I get
526.4046809656061 170.8742588241399 445.23248886773865 256.0652790027961
But for atan I get
341.36661845405075 44.75395042783804 338.65671584881693 84.45754194958526

I dont know why this happens.
What do you mean "you get" these numbers? Both functions return an angle, not position values. Are these the final results of the 10 second run? That is no help at all, with all the questionable stuff happening before the end of the run.
I guess thats the part where two particles collide. Since they have a radius of 20. The collision will happen when they are near then 40. Since we are plotting these datas on the graph this change might have looked like a direction change.
It looks like one ball changes direction, but the other does not. So how can that be a collision?
What do you mean by dump as much as data. Like giving some random meaningful inputs and see what happens ?.
I mean that you should print a lot of detailed data to a file so that you can see exactly what calculations are happening at each step. Put prints at every key point in your code so you can see the exact calculation results. There are some points in the plots where one ball changes direction but I don't see the other ball change direction. You need to see what is happening there. A file of data would help. Until then, we are just blind to what is going on.

Last edited:
Gold Member
When you use if on a non-boolean object in python it converts it to a boolean

>>> [(i,bool(i)) for i in [None, 0, 0.0, 3.14, "abc", "", 'a', '',True, False, [1,2,3],[]]]
[(None, False), (0, False), (0.0, False), (3.14, True), ('abc', True), ('', False), ('a', True), ('', False), (True, True), (False, False), ([1, 2, 3], True), ([], False)]

in the case of the motion function it is the same as saying "if motion(x) != 0"
Thanks for the info
There are some points in the plots where one ball changes direction but I don't see the other ball change direction.
Yes I noticed that too. The thing is one ball changes its direction and other the other one dont. Its really confusing for me.
I also noticed that atan is wrong and atan2 works.

Gold Member
Colliding_Ball:
from math import sqrt, atan2, sin, cos, pi, inf
from numpy import array

W = 600  # width of the table
H = 300  # height of the table
R = 10  # the radius of the ball
A = 0  # deceleration constant
dt = 10 ** -3
ma = mb = 1  # masses of the particles a and b

def vec_magnitude(V1):
return sqrt(V1[0]**2 + V1[1]**2)

def collision_test(V1, V2):
if vec_magnitude(V1 - V2) <= 2 * R:
return True

def dot_product(V1, V2):
return sum(V1 * V2)

def after_collision_velocity(Va, Vb, Ra, Rb):
''' the equation that produces the velocity of the objects after the collision'''
Va_new = Va - ((2 * mb * dot_product(Va - Vb, Ra - Rb)) /
((ma + mb) * (vec_magnitude(Ra - Rb))**2) * (Ra - Rb))
Vb_new = Vb - ((2 * ma * dot_product(Vb - Va, Rb - Ra)) /
((ma + mb) * (vec_magnitude(Rb - Ra))**2) * (Rb - Ra))
return Va_new, Vb_new

def check_reflection(P, V_mag, angle, V):
if P[1] < R:
P += array([0, 2 * (R - P[1])])
angle *= -1
return P, V_mag, angle, V
if P[0] < R:
P += array([2 * (R - P[0]), 0])
angle = pi - angle
return P, V_mag, angle, V
if P[1] > H - R:
P += array([0, 2 * (H - R - P[1])])
angle *= -1
return P, V_mag, angle, V
if P[0] > W - R:
P += array([2 * (W - R - P[0]), 0])
angle = pi - angle
return P, V_mag, angle, V
else:
V_mag -= A * dt
Vx = V_mag * cos(angle)
Vy = V_mag * sin(angle)
P += array([Vx * dt, 0])
P += array([0, Vy * dt])
V = array([Vx, Vy])
return P, V_mag, angle, V

file = open("test_drawing.txt", "w")
for line in open("tex.txt", "r"):
t = 0
Xa, Ya, Xb, Yb, Vxa, Vya, Vxb, Vyb = [
int(i) for i in (line.rstrip()).split(" ")]
Pa = array([Xa, Ya], dtype=float)
Pb = array([Xb, Yb], dtype=float)
Va = array([Vxa, Vya], dtype=float)
Vb = array([Vxb, Vyb], dtype=float)
Va_mag = vec_magnitude(Va)
Vb_mag = vec_magnitude(Vb)
if Vxa == 0:
Vxa = inf
angle_a = atan2(Vya, Vxa)
if Vxb == 0:
Vxb = inf
angle_b = atan2(Vyb, Vxb)
while t <= 10:
Pa, Va_mag, angle_a, Va = check_reflection(Pa, Va_mag, angle_a, Va)
Pb, Vb_mag, angle_b, Vb = check_reflection(Pb, Vb_mag, angle_b, Vb)
if collision_test(Pa, Pb) == True:
Va, Vb = after_collision_velocity(Va, Vb, Pa, Pb)
Va_mag = vec_magnitude(Va)
Vb_mag = vec_magnitude(Vb)
if Va[0] == 0:
Va[0] = inf
angla_a = atan2(Va[1], Va[0])
if Vb[0] == 0:
Vb[0] = inf
angle_b = atan2(Vb[1], Vb[0])
t += dt
file.write(str(Pa[0]) + " " + str(Pa[1]) + " " + str(Pb[0]) + " " + str(Pb[1]) + "\n")
print(Pa[0], Pa[1], Pb[0], Pb[1])
file.close()
data for a simple collision program:
100 200 140 200 4 4 -4 4
Drawing Program:
from pylab import plot, show

Xa = [100]
Ya = [200]
Xb = [140]
Yb = [200]

for line in open("test_drawing.txt", "r"):
data = [float(i) for i in line.split(" ")]
Xa.append(data[0])
Ya.append(data[1])
Xb.append(data[2])
Yb.append(data[3])

plot(Xa, Ya, "-r")
plot(Xb, Yb, "-.g")
show()
Now when you run this you ll get a data of points for each coordinate (Pa, Pb).

The image is like this

Its clear that ball b reflects but ball a does not. Thats really strange... I mean ball b acts like normal but not the a..

Who can spot the error , cause I cannot.

P is the position vector so Pa and Pb are the position vectors of the ball a and ball b respectively. V is the velocity.

Gold Member
I wrote angla_a instead of angle_a ... that was the reason.. So simple ..
It's my experience that it is possible to stare at code for a long, long time without spotting a simple error
The case has been proved :)

Last edited:
Klystron, jedishrfu and FactChecker
jedishrfu
Mentor
Each of these kinds of mistakes that you've made and fixed gives you a personalized list of things to check in your code.

Interpretive languages often fall down in this respect where any variable name can be defined incorrectly and it will use it.

I once tried to teach a coworker on how to write programs. My first assignment to him was to enter some code I wrote on paper. I told him to see if he could get it working. He successfully got it to compile but it was most certainly wrong. Due to spelling mistakes in variable names, partly my fault and partly his typing, the compiler would indicate variable xyyzy was undefined and so he defined it not realizing that it was already defined as xyzzy. However, he didn't understand the flow of the program and so added a new variable instead of correcting the spelling mistake.

I know in python if you call a variable before it's defined you will get a warning. However, I've seen cases in my own code where I accidentally reused the wrong indexing variable in a loop and nothing was said. It's only through knowing what you wrote and why you wrote it that the error is spotted quickly.

Lastly, try not to get bogged down in the accuracy of your numbers early on. I had a boss once who was always suspicious of the COBOL compiler math that he would insist on looking at the machine code it generated. Later, I found this was due to some hardware issues with the extended math processor that computed using packed decimal format which COBOL routinely used for some datatypes. However, he never found anything wrong.

Klystron, Arman777 and FactChecker
FactChecker
Gold Member
I know in python if you call a variable before it's defined you will get a warning.
This is a good reason to take a very hard look at anything that gives a warning. Otherwise, typos are very hard to spot.

Klystron, Arman777 and jedishrfu
Gold Member
Otherwise, typos are very hard to spot.
Indeed they are...I guess I should have noticed it before.
Lastly, try not to get bogged down in the accuracy of your numbers early on.
I agree..Well I run the code and It worked yey!!

So the problem is solved. Thank you all for your help

Klystron
FactChecker
Gold Member
Indeed they are...I guess I should have noticed it before.
My advice for the future is to get in the habit of following the coding standards that an editor like PyCharm uses. That will minimize the PyCharm warnings due to cosmetic things and it will be easier to spot the remaining serious errors. I brought your code into PyCharm (free version) and it did flag the angla_a typo, but it was among so many other warnings that it was easy to overlook. I am not familiar with PyCharm, but a good editor will allow you to control the types of things that generate a warning. I didn't see a way to control that in the PyCharm free version.

EDIT: The free version of PyCharm has an option under Code => Inspect Code that immediately spotted the 'angla' typo as a serious problem.

Arman777
Gold Member
My advice for the future is to get in the habit of following the coding standards that an editor like PyCharm uses. That will minimize the PyCharm warnings due to cosmetic things and it will be easier to spot the remaining serious errors. I brought your code into PyCharm (free version) and it did flag the angla_a typo, but it was among so many other warnings that it was easy to overlook. I am not familiar with PyCharm, but a good editor will allow you to control the types of things that generate a warning. I didn't see a way to control that in the PyCharm free version.

EDIT: The free version of PyCharm has an option under Code => Inspect Code that immediately spotted the 'angla' typo as a serious problem.
When I started coding a year ago I started with python IDE then I switched to PyCharm. PyCharm was great for me back then but it seemed slow to me. Then this year I updated the Pycharm and it was really slow( I guess there was some kind of a bug) then, I discovered VS code. VS code seems ligther and somewhat faster. I noticed that I can easily switch files, create different files just easily in the VS Code etc. But I guess PyCharm is more focused on the python. Maybe I should start to use it. Thanks

FactChecker