I have created a program in python that calculates forces between bodies (i.e earth, moon and a hypothetical moon) and make them move according to the changes in velocity and forces. This is the code for the program:
from math import sin,cos,sqrt,atan2,pi
import pygame
pygame.init()
class Planet:
dt = 1/100
G = 6.67428e-11 #G constant
scale = 1/(1409466.667) #1 m = 1/1409466.667 pixlar
def __init__(self,x=0,y=0,radius=0,color=(0,0,0),mass=0,vx=0,vy=0):
self.x = x #x-coordinate pygame-window
self.y = y #y-coordinate pygame-window
self.radius = radius
self.color = color
self.mass = mass
self.vx = vx #velocity in the x axis
self.vy = vy #velocity in the y axis
def draw(self,screen):
pygame.draw.circle(screen, self.color, (self.x, self.y), self.radius)
def orbit(self,trace):
pygame.draw.rect(trace, self.color, (self.x, self.y, 2, 2))
def update_vel(self,Fnx,Fny):
ax = Fnx/self.mass #Calculates acceleration in x- and y-axis for body 1.
ay = Fny/self.mass
self.vx -= ((ax * Planet.dt)/Planet.scale)
self.vy -= ((ay * Planet.dt)/Planet.scale)
self.update_pos()
def update_pos(self):
self.x += ((self.vx * Planet.dt)) #changes position considering each body's velocity.
self.y += ((self.vy * Planet.dt))
def move(self,body):
dx = (self.x - body.x) #Calculates difference in x- and y-axis between the bodies
dy = (self.y - body.y)
r = (sqrt((dy**2)+(dx**2))) #Calculates the distance between the bodies
angle = atan2(dy, dx) #Calculates the angle between the bodies with atan2!
if r < self.radius: #Checks if the distance between the bodies is less than the radius of the bodies. Uses then Gauss gravitational law to calculate force.
F = 4/3 * pi * r
Fx = cos(angle) * F
Fy = sin(angle) * F
else:
F = (Planet.G*self.mass*body.mass)/((r/Planet.scale)**2) #Newtons gravitational formula.
Fx = cos(angle) * F
Fy = sin(angle) * F
return Fx,Fy
def motion():
for i in range(0,len(bodies)):
Fnx = 0 #net force
Fny = 0
for j in range(0,len(bodies)):
if bodies[i] != bodies[j]:
Fnx += (bodies[i].move(bodies[j]))[0]
Fny += (bodies[i].move(bodies[j]))[1]
elif bodies[i] == bodies[j]:
continue
bodies[i].update_vel(Fnx,Fny)
bodies[i].draw(screen)
bodies[i].orbit(trace)
Fnx,Fny=0,0
screen = pygame.display.set_mode([900,650]) #width - height
trace = pygame.Surface((900, 650))
pygame.display.set_caption("Moon simulation")
FPS = 60 #how quickly/frames per second our game should update. Change?
earth = Planet(450,325,30,(0,0,255),5.97219*10**(24),-24.947719394204714/2) #450= xpos,325=ypos,30=radius
luna = Planet(450,(575/11),10,(128,128,128),7.349*10**(22),1023)
moon = Planet() #the second moon
bodies = [earth,luna]
running = True
clock = pygame.time.Clock()
while running: #if user clicks close window
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((0,0,0))
pygame.Surface.blit(screen, trace, (0, 0))
motion()
pygame.display.flip() #update? flip?
pygame.quit()
The code works and I get the moon to orbit around earth. However since I am an amateur in such an area I wonder if it can be improved on. Do the calculations look alright? Can I make some improvements? I am trying to make my simulation realistic and thus I have a scaling factor that I use to convert pixels to meters and vice versa. Here for example: Is it correct to divide by the scaling factor or should I multiply it (so I don't mix pixels with meters):
def update_vel(self,Fnx,Fny):
ax = Fnx/self.mass #Calculates acceleration in x- and y-axis for body 1.
ay = Fny/self.mass
self.vx -= ((ax * Planet.dt)/Planet.scale)
self.vy -= ((ay * Planet.dt)/Planet.scale)
self.update_pos()
Thankful for any suggestions or thoughts on my code and/or the physics involved in it!
1/(1409466.667)
should be1 / 1409466.667
,5.97219*10**(24)
should be5.97219 * 10**24
,7.349*10**(22)
should be7.349 * 10**22
,(575/11)
should be575 / 11
\$\endgroup\$motion()
operates on data which does not belong to it. It's a bad practice, especially as the code grows. Therefore, eitherbodies
should become a parameter of that function, or the function itself should become a method in a separate class, withbodies
belonging to it as an attribute, for example:motion = Motion(bodies); motion.run()
. \$\endgroup\$Planet()
constructor takes 7 arguments, all positional; that's somewhat messy. I can see you added comments forearth
to help remember which argument is which. It would be simpler for the arguments to go together with their names (keywords), just like in the constructor's definition. Bonus tip: you can enforce keyword-only arguments by putting an asterisk (*
) argument at the beginning of the constructor's definition, right afterself
:def __init__(self, *, x=...)
. \$\endgroup\$