I made an orbit simulator in python, but it doesn't work. Objects behave strangely instead of attracting each other, plus if they have a lot of mass they fly off into infinity. Here is the code:
from tkinter import *; import math; import random; import time
root=Tk()
root.geometry("2366x720")
mouseY, mouseX = 0,0
Objectx, Objecty, Objectxvel, Objectyvel, ObjectMass = [],[],[],[],[]
for i in range(3): # numero objetos
Objectx.append(random.randint(100,1000))
Objecty.append(random.randint(100,700))
Objectxvel.append(0) #Objectxvel.append(random.randint(-30,30) / 100)
Objectyvel.append(0) #Objectyvel.append(random.randint(-30,30) / 100)
ObjectMass.append(random.randint(500,500000))
canvas=Canvas(root, bg="white")
canvas.pack(fill="both", expand=True)
def mousePos(event):
global mouseX; global mouseY
mouseX,mouseY = event.x, event.y
root.bind("<Motion>", mousePos)
mouseMass=0 # masa del circulo rojo. No es importante pero se puede cambiar por diversion
G = 6.67428e-11
while True:
canvas.delete("all")
#----------------------------- Objects ---------------------------------------------------------------------------------------
#print(range(len(Objectx)))
for Num in range(len(Objectx)):
angle = math.atan2( mouseY-Objecty[Num], mouseX-Objectx[Num] )
distance = math.sqrt( ((mouseX-Objectx[Num])**2) + ((mouseY-Objecty[Num])**2) )
Objectxvel[Num] += math.cos(angle) * G * ObjectMass[Num] * mouseMass / (distance ** 2)
Objectyvel[Num] += math.sin(angle) * G * ObjectMass[Num] * mouseMass / (distance ** 2)
Objectx[Num] += Objectxvel[Num]
Objecty[Num] += Objectyvel[Num]
if distance < 20:
if distance < 15:
Objectx[Num] -= Objectxvel[Num] * 1.5
Objecty[Num] -= Objectyvel[Num] * 1.5
Objectxvel[Num] = 0
Objectyvel[Num] = 0
else:
Objectx[Num] -= Objectxvel[Num] * 1.01
Objecty[Num] -= Objectyvel[Num] * 1.01
Objectxvel[Num] = 0
Objectyvel[Num] = 0
#----------------------------------------------------objects & objects detection--------------------------------------------
for Num2 in range(len(Objectx)):
angle = math.atan2( Objectx[Num]-Objecty[Num2], Objecty[Num]-Objectx[Num2] )
distance = math.sqrt( ((Objectx[Num]-Objectx[Num2])**2) + ((Objecty[Num]-Objecty[Num2])**2) )
#print(distance)
try:
Objectxvel[Num] += math.cos(angle) * G * ObjectMass[Num] * ObjectMass[Num2] / (distance ** 2)
Objectyvel[Num] -= math.sin(angle) * G * ObjectMass[Num] * ObjectMass[Num2] / (distance ** 2)
except:
pass
Objectx[Num] += Objectxvel[Num]
Objecty[Num] += Objectyvel[Num]
if distance < 20 and distance > 0:
try:
Objectx[Num] -= Objectxvel
Objecty[Num] -= Objectyvel
except:
pass
#Objectxvel[Num] = 0
#Objectyvel[Num] = 0
canvas.create_oval(Objectx[Num]+5, Objecty[Num]+5, Objectx[Num]-5, Objecty[Num]-5, fill="black", width=0)
#----------------------------- No Object ---------------------------------------------------------------------------------------
oval1=canvas.create_oval(mouseX+15, mouseY+15, mouseX-15, mouseY-15, fill="red", width=0)
#print(Objectx, Objecty)
root.update()
PS: to help understand the code, the first thing it does is create lists with values, like the coordinates of each object, the force of movement in x and y, etc. then inside the "While True" I have a "For Num in range(quantity of objects)" that I use to individually calculate the physics of each object with each object, accessing the element number "Num" of each list.
PD2:
Objectxvel[Num] += math.sin(angle) * G * ObjectMass[Num] * ObjectMass[Num2] / (distance ** 2) this is the law of universal gravitation. Objectx is constantly increased by Objectxy, which is constantly increased by the law of universal gravitation. The same goes for Objecty and Objectyvel.
As far as I can see, there is an error in one of the equations in which you update the speed on the Y axis of the objects, since you use a
-=
where it should be a+=
.Corrected that problem, I think the code behaves as it should, although there are a number of observations due to which the animation you finally get seems counterintuitive:
However, I think everything is correct. A more normal and less counterintuitive behavior is achieved if you give the "sun" (the mouse cursor) a much larger mass than the "planets" and try to bring it closer to them to see how they are attracted. It can also help a lot to draw on each planet an arrow-vector that indicates the direction and magnitude of its speed, to confirm that the attraction and inertia are working as they should. You can also make the size of each "planet" proportional to its mass as I discussed earlier, which can make understanding its movements more intuitive.
The following code implements these "improvements" in addition to correcting the aforementioned sign error:
And the following animation shows how I was "playing" a bit with the sun (which I move with the mouse) and the planets (which move according to the equations). I don't know why they sometimes leave a trail.
update
After a comment from the user, I revised the code and indeed there were more errors in the part where the interaction of the bodies with each other is computed. Specific:
Num2==Num
and skip that case.except: pass
. In the end it is better to remove this. If you wanted to simulate a collision you would have to detect when the distance is less than the radius and in that case "merge" the two bodies (remove one of them from the list and leave only the other, but add their masses), while you should Calculate the resultant velocity of the remainder by the law of conservation of momentum. Removing all this, the bodies simply "pass through", or rather are considered zero-dimensional point masses that, although they attract each other, cannot collide.That is, eliminating the part that interacted with the "sun" and leaving only the computation of the interactions of the bodies with each other, it would be as follows. Of fixed step the value of
random.seed()
so that the result is repeatable and always comes out the same. I have found that for a seed 4 an interesting result comes out. For other values, the bodies were immediately thrown out of the image. I have also changed the value of G, increasing it several orders of magnitude so that the animation goes faster, than if it is not eternalized.The resulting animation is now:
(I have solved the problem of the "trace" left
width=0
by removing the optioncreate_oval()
)The subject became super interesting to me, and I had free time, so I decided to pass it on to an object-oriented model so that
And based on abulafia's answer (keeping the imports and the seed), I achieved my goal:
1. Treat each "object" as a class (I called it Planet)
With this you avoid having to manage many variables in many lists, and instead, a list full of objects, and that each object has "inside" the variables you need (position, speed, mass)
2. Pass the simulator to class
Basically, I tried:
for i in range(x)
by afor each
, to make it look more pythonicPlaneta
, so simply by calling the functioncalcular()
for each Planet, you can already see its effect!Finally, the only thing left would be to instantiate the simulator (I put 8 planets in it, in this case):
The result is the same, only the mouse already affects the planets!