Firstly, I am very new to programming and biting off more than I can chew here. Anyway...
I am trying to build a solar system/orbiting simulation in python using this code as a model.
This is my full code:
# Import math and turtle
import math
from turtle import *
import turtle
# Gravitational Constant
G = 6.67428e-11
# Scale: 1 pixel = 1 astronomical unit
# 1 astronomical unit = 1 AU = 149597900 km
AU = (149.6e6 * 1000) # 149.6 million km in meters
Scale = 250 / AU
wn = turtle.Screen()
wn = turtle.bgcolor('black')
class body(Turtle):
# Subclass of turtle representing a body
# Additional attributes:
# mass in kg
# vx, vy: x, y velocities in m/sc
# px, py: x, y positions in m
# Set background to black
# Turtle.bgcolor('black')
name = 'body'
mass = None
vx = vy = 0.0
px = py = 0.0
def attraction(self, other):
# (body): (fx, fy)
# returns the force exerted on this body by the other body.
# Report and error if the other object is the same as this one.
if self is other:
raise ValueError("Attraction of object %r to itself requested" % self.name)
# Compute the distance of the other body:
sx, sy = self.px, self.py
ox, oy = other.px, other.py
dx = (ox - sx)
dy = (oy - sy)
d = math.sqrt(dx**2 + dy**2)
# Report an Error if the distance is 0
# ZeroDivisionError would happen later otherwise
if d == 0:
raise ValueError("Collision between objects %r and %r" % (self.name, other.name))
# Compute the force of attraction
f = G * self.mass * other.mass / (d**2)
# Compute the direction of the force
theta = math.atan2(dy, dx)
fx = math.cos(theta) * f
fy = math.sin(theta) * f
return fx, fy
def updateInfo(step, bodies):
# (int, [body])
# Displays infomation about status of simulation.
print('Step #{}'.format(step))
for body in bodies:
s = '{:<8} Pos.={:>6.2f} Vel.={:>10.3f}'.format(body.name, body.px/AU, body.py/AU, body.vy)
print(s)
print()
def loop(bodies):
# (body)
# Never returns; loop through the simulation and updates the positions of given bodies
timestep = 24*3600 # One Day
for body in bodies:
body.penup()
body.hideturtle()
step = 1
while True:
updateInfo(step, bodies)
step += 1
force = {}
for body in bodies:
# Adds all the forces exerted on a body
totalFx = totalFy = 0.0
for other in bodies:
# Don't calculate attraction to itself
if body is other:
continue
fx, fy = body.attraction(other)
totalFx += fx
totalFy += fy
# Record the total force exerted
force[body] = (totalFx, totalFy)
# Update velocities based on the force.
for body in bodies:
fx, fy = force[body]
body.vx += fx / body.mass * timestep
body.vy += fy / body.mass * timestep
# Update positions
body.px += body.vx * timestep
body.py += body.vy * timestep
body.goto(body.px*Scale, body.py*Scale)
body.dot(5)
def main():
sun = body()
sun.name = 'Sun'
sun.mass = 1.98892 * 10**30
sun.pencolor('yellow')
earth = body()
earth.name = 'Earth'
earth.mass = 5.9742 * 10**24
earth.px = -1*AU
earth.vy = 29.783 * 1000
earth.pencolor('blue')
venus = body()
venus.name = 'Venus'
venus.mass = 4.8685 * 10**24
venus.px = 0.723 * AU
venus.vy = -35.02 * 1000
venus.pencolor('orange')
loop([sun, earth, venus])
if __name__ == '__main__':
main()
I have added an import turtle
line for my window as I want to change the window color to black. I know the the major differences between the two import styles and why some people prefer one over the other (or why one is not good). I want to change the structure of the class so I can have more control over the graphics of the simulation and also move away from the from x import *
method. Any advice will be welcome!
My choice would be to use the import:
from turtle import Screen, Turtle
forcing the object-oriented interface to turtle by blocking the functional one. But I would go further:
fx, fy = force[body]
body.vx += fx / body.mass * timestep
body.vy += fy / body.mass * timestep
# Update positions
body.px += body.vx * timestep
body.py += body.vy * timestep
Turtle can use vectors ( Vec2D
) so we don't need to do separate operations on the x and y values.
for body in bodies:
body.penup()
body.hideturtle()
This can be part of the Body
class initializer.
body.goto(body.px*Scale, body.py*Scale)
We can scale the turtle's coordinate system up front and avoid scaling operations in the main loop.
while True:
Not in an event driven world like turtle -- use ontimer()
instead. The following rework implements the above suggestions to show how I might go about this problem:
# Import math and turtle
import math
from turtle import Screen, Turtle, Vec2D
# Gravitational Constant
G = 6.67428e-11
# Scale: 1 pixel = 1 astronomical unit
# 1 astronomical unit = 1 AU = 149597900 km
AU = 149.6e6 * 1000 # 149.6 million km in meters
TIME_STEP = 24 * 3600 # One day
FORMAT = "{:<8} Pos.={:>6.2f} Vel.={:>10.3f}"
CURSOR_SIZE = 20
class Body(Turtle):
''' Subclass of turtle representing a body. '''
# Additional attributes:
# mass in kg
# velocity in m/sc
# position in m
name = 'body'
mass = None
velocity = Vec2D(0.0, 0.0)
Position = Vec2D(0.0, 0.0) # Position to avoid conflict with turtle's position() method
def __init__(self, dot_size=5):
super().__init__(shape='circle')
self.shapesize(dot_size / CURSOR_SIZE)
self.penup()
def attraction(self, other):
''' Return the force exerted on this body by the other body. '''
# Report an error if the other object is the same as this one.
if self is other:
raise ValueError("Attraction of object {} to itself requested".format(self.name))
# Compute the distance of the other body:
dx, dy = other.Position - self.Position
distance = math.sqrt(dx**2 + dy**2)
# Report an Error if the distance is 0
# ZeroDivisionError would happen later otherwise
if distance == 0:
raise ValueError("Collision between objects {} and {}".format(self.name, other.name))
# Compute the force of attraction
force = G * self.mass * other.mass / distance**2
# Compute the direction of the force
theta = math.atan2(dy, dx)
direction = Vec2D(math.cos(theta), math.sin(theta))
return direction * force
def updateInfo(step: int, bodies: list):
''' Display information about status of simulation. '''
print('Step #{}'.format(step))
for body in bodies:
print(FORMAT.format(body.name, abs(body.Position * (1 / AU)), abs(body.velocity)))
print()
def single_step(bodies, step=1):
''' Step through the simulation and updates the positions of given bodies. '''
updateInfo(step, bodies)
for body in bodies:
# Adds all the forces exerted on a body
totalForce = Vec2D(0.0, 0.0)
for other in bodies:
# Don't calculate attraction to itself
if body is not other:
totalForce += body.attraction(other)
# Update velocities based on the force.
body.velocity += totalForce * (1 / body.mass) * TIME_STEP
# Update position
body.Position += body.velocity * TIME_STEP
body.setposition(body.Position)
screen.update()
screen.ontimer(lambda: single_step(bodies, step + 1))
if __name__ == '__main__':
screen = Screen()
screen.bgcolor('black')
screen.setworldcoordinates(-2 * AU, -2 * AU, 2 * AU, 2 * AU)
screen.tracer(False)
sun = Body(10)
sun.name = 'Sun'
sun.mass = 1.98892 * 10**30
sun.color('yellow')
earth = Body()
earth.name = 'Earth'
earth.mass = 5.9742 * 10**24
earth.Position = Vec2D(-AU, 0.0)
earth.velocity = Vec2D(0.0, 29.783 * 1000)
earth.color('blue')
venus = Body()
venus.name = 'Venus'
venus.mass = 4.8685 * 10**24
venus.Position = Vec2D(0.723 * AU, 0.0)
venus.velocity = Vec2D(0.0, -35.02 * 1000)
venus.color('orange')
single_step([sun, earth, venus])
screen.mainloop()
The updateInfo()
function confused me as it prints three of it's four data arguments. (Ie prints the Y position as the velocity!) I just did something random instead, printing the absolute value of the vectors.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.