I have written a simple program to do trajectory simulation in the Earth-Moon system, it still has a long way to go I am working on making it more class oriented and am looking into implementing a better system than just the straight time step integration method.
I want to focus on having it more class-oriented. I am unsure where to draw a good line as to how much stuff should be class/function driven and how much should be in the main block. I am also looking for feedback on how to make it faster, as right now when I have the timestamp low it takes quite a while to run. In addition I am trying to find a good way to implement planets, as the way I have them now is not it, I could make a planet
class, and initialize all the ones I am going to use, but I would rather just import/use them.
Here is some example output:
Console: (old output)
Days remaining: 28.84, (96.14% left)
Tics per second: 49286 est time remaining: 00:00:50
Days remaining: 27.69, (92.28% left)
Tics per second: 49555 est time remaining: 00:00:48
Days remaining: 26.53, (88.43% left)
Tics per second: 49653 est time remaining: 00:00:46
Days remaining: 25.37, (84.57% left)
Tics per second: 49770 est time remaining: 00:00:44
Days remaining: 24.21, (80.71% left)
Tics per second: 49777 est time remaining: 00:00:42
Days remaining: 23.06, (76.85% left)
Tics per second: 49789 est time remaining: 00:00:40
Days remaining: 21.90, (72.99% left)
Tics per second: 49844 est time remaining: 00:00:37
Days remaining: 20.74, (69.14% left)
Tics per second: 49901 est time remaining: 00:00:35
Days remaining: 19.58, (65.28% left)
Tics per second: 49796 est time remaining: 00:00:33
Days remaining: 18.43, (61.42% left)
Tics per second: 49725 est time remaining: 00:00:32
Days remaining: 17.27, (57.56% left)
Tics per second: 49682 est time remaining: 00:00:30
Days remaining: 16.11, (53.70% left)
Tics per second: 49632 est time remaining: 00:00:28
Days remaining: 14.95, (49.85% left)
Tics per second: 49645 est time remaining: 00:00:26
Days remaining: 13.80, (45.99% left)
Tics per second: 49553 est time remaining: 00:00:24
Days remaining: 12.64, (42.13% left)
Tics per second: 49541 est time remaining: 00:00:22
Days remaining: 11.48, (38.27% left)
Tics per second: 49519 est time remaining: 00:00:20
Days remaining: 10.32, (34.41% left)
Tics per second: 49523 est time remaining: 00:00:18
Days remaining: 9.17, (30.56% left)
Tics per second: 49538 est time remaining: 00:00:15
Days remaining: 8.01, (26.70% left)
Tics per second: 49512 est time remaining: 00:00:13
Days remaining: 6.85, (22.84% left)
Tics per second: 49532 est time remaining: 00:00:11
Days remaining: 5.69, (18.98% left)
Tics per second: 49501 est time remaining: 00:00:09
Days remaining: 4.54, (15.12% left)
Tics per second: 49518 est time remaining: 00:00:07
Days remaining: 3.38, (11.27% left)
Tics per second: 49516 est time remaining: 00:00:05
Days remaining: 2.22, (7.41% left)
Tics per second: 49533 est time remaining: 00:00:03
Days remaining: 1.06, (3.55% left)
Tics per second: 49526 est time remaining: 00:00:01
Total Tics per second: 49509.11
Total time: 00:00:52
Plot:
The blue line is the craft (whose orbit has been altered by the moon), and the Green line is the moons position over time.
(Also here in a git repo, please excuse the fluff I am trying to get started with readthedocs and sphinx, but it's not going so well).
Orbital.py (main bit)
import time as t
import math
import sys
from astropy.time import Time
import numpy as np
#Custum objects live in sub directory
sys.path.append('./Objects/')
# Import Objects
from Objects import Craft
import moon
import earth
# Generate output for plotting a sphere
def drawSphere(xCenter, yCenter, zCenter, r):
# Draw sphere
u, v = np.mgrid[0:2 * np.pi:20j, 0:np.pi:10j]
x = np.cos(u) * np.sin(v)
y = np.sin(u) * np.sin(v)
z = np.cos(v)
# Shift and scale sphere
x = r * x + xCenter
y = r * y + yCenter
z = r * z + zCenter
return (x, y, z)
def plot(ship,planets):
"""3d plots earth/moon/ship interaction"""
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Initilize plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X Km')
ax.set_ylabel('Y Km')
ax.set_zlabel('Z Km')
ax.set_xlim3d(-500000, 500000)
ax.set_ylim3d(-500000, 500000)
ax.set_zlim3d(-500000, 500000)
ax.plot(xs=ship.hist[0][0::10],
ys=ship.hist[1][0::10],
zs=ship.hist[2][0::10],
zdir='z', label='ys=0, zdir=z')
# Plot planet trajectory
for planet in planets:
ax.plot(xs=moon.hist[0],
ys=moon.hist[1],
zs=moon.hist[2],
zdir='z', label='ys=0, zdir=z')
# Plot Earth (plot is moon position relative to earth)
# also plotting to scale
(xs, ys, zs) = drawSphere(0, 0, 0, 6367.4447)
ax.plot_wireframe(xs, ys, zs, color="r")
plt.show()
def sim(startTime, endTime, step, ship, planets):
"""Runs orbital simulation given ship and planet objects as well as start/stop times"""
# Caculate moon planet update rate (1/10th as often as the craft)
plan_step = int(math.ceil(((endTime - startTime) / step)/100))
# Initilize positions of planets
for planet in planets:
planet.getRelPos(startTime)
planet.log()
start = t.time()
for i, time in enumerate(np.arange(startTime, endTime, step)):
# Every plan_step update the position estmation
if (i % plan_step == 0):
for planet in planets:
planet.getRelPos(time)
planet.log()
# Update craft_vol
for planet in planets:
ship.force_g(planet.mass, planet.pos[0], planet.pos[1], planet.pos[2])
ship.update()
# Log the position of the ship every 1000 steps
if (i % 1000) == 0:
# Append the position to the lists
ship.log()
# Print status update every 100,000 steps
if (i % 100000) == 0:
if t.time()-start > 1:
print "Days remaining: {0:.2f}, ({1:.2f}% left)".format((endTime - time), ((1-((time - startTime) / (endTime - startTime)))*100))
# Caculate estmated time remaining
# Total tics
tot_tics = int((endTime - startTime) / step)
# Tics per second till now
tics_s = int(math.ceil(i/(t.time()-start)))
# Estmated remaining seconds
sec_rem = (tot_tics-i)/tics_s
m, s = divmod(sec_rem, 60)
h, m = divmod(m, 60)
print "Tics per second: {0} est time remaining: {1:0>2}:{2:0>2}:{3:0>2}".format(tics_s, h, m, s)
print "Total Tics per second: {0:.2f}".format(((endTime - startTime) / step)/(t.time()-start))
tot_s = t.time()-start
m, s = divmod(tot_s, 60)
h, m = divmod(m, 60)
print "Total time: {0:0>2.0f}:{1:0>2.0f}:{2:0>2.0f}".format(h, m, s)
if __name__ == "__main__":
# Delta time for simulations (in days)
del_t = np.longdouble(1.0) / (24.0 * 60.0 * 60.0)
ship = Craft(del_t, x=35786, y=1, z=1, v_x=0, v_y=4.5, v_z=0, mass=12)
planets = [earth,moon]
# Initilize simulation time
start_time = Time('2015-09-10T00:00:00')
end_time = Time('2015-10-10T00:00:00')
# Initilize start date/time (Julian)
time = start_time.jd
sim(start_time.jd, end_time.jd, del_t, ship, planets)
plot(ship,[moon])
./Orbital/Objects.py
import math
import numpy as np
from jplephem.spk import SPK
import os
class Craft(object):
def __init__(self, delt_t, x=0.0, y=0.0, z=0.0,
v_x=0.0, v_y=0.0, v_z=0.0, mass=0):
# Pos is in km
self.pos = np.array([np.longdouble(x),
np.longdouble(y),
np.longdouble(z)])
# Vol is in km/s
self.vol = np.array([np.longdouble(v_x),
np.longdouble(v_y),
np.longdouble(v_z)])
# Mass is in kg
self.mass = mass
# Delta_t is in days
self.del_t = np.longdouble(delt_t) * 86400
# Initilize non input vars
# Initilize history of positions
self.hist = [[np.longdouble(x)],
[np.longdouble(y)],
[np.longdouble(z)]]
# Initilize force array
self.f = np.array([np.longdouble(0),
np.longdouble(0),
np.longdouble(0)])
# Initilize constants
# Gravitational constant (6.674*10-11 N*(m/kg)2)
self.G_const = np.longdouble(0.00000000006674)
def force_g(self, body_mass, body_x=0.0, body_y=0.0, body_z=0.0):
# Caculate x, y, z distances
xdist = (self.pos[0] - body_x)
ydist = (self.pos[1] - body_y)
zdist = (self.pos[2] - body_z)
# Caculate vector distance
d = math.sqrt((xdist**2) + (ydist**2) + (zdist**2)) * 1000
# Caculate comman force of gravity
g_com = ((self.G_const * body_mass * self.mass) / (d**3))
# Update forces array
self.f -= np.array([g_com * (xdist * 1000),
g_com * (ydist * 1000),
g_com * (zdist * 1000)])
# Function to step simulation based on force caculations
def update(self):
# Step velocity
self.vol += np.array([(((self.f[0] / self.mass) * self.del_t)) / 1000,
(((self.f[1] / self.mass) * self.del_t)) / 1000,
(((self.f[2] / self.mass) * self.del_t)) / 1000])
# Step position
self.pos += np.array([(self.del_t * self.vol[0]),
(self.del_t * self.vol[1]),
(self.del_t * self.vol[2])])
# Reset force profile
self.f = np.array([np.longdouble(0),
np.longdouble(0),
np.longdouble(0)])
def VV_update(self):
"""Parameters:
r is a numpy array giving the current position vector
v is a numpy array giving the current velocity vector
dt is a float value giving the length of the integration time step
a is a function which takes x as a parameter and returns the acceleration vector as an array"""
r_new = r + v*dt + a(r)*dt**2/2
v_new = v + (a(r) + a(r_new))/2 * dt
def dist(self, body_x=0.0, body_y=0.0, body_z=0.0):
return math.sqrt(((self.pos[0] - body_x)**2) +
((self.pos[1] - body_y)**2) +
((self.pos[2] - body_z)**2))
def log(self):
self.hist[0].append(self.pos[0])
self.hist[1].append(self.pos[1])
self.hist[2].append(self.pos[2])
./Objects/earth.py
import numpy as np
from jplephem.spk import SPK
import os
if not os.path.isfile('de430.bsp'):
raise ValueError('de430.bsp Was not found!')
kernel = SPK.open('de430.bsp')
# Initilize mass
mass = np.longdouble(5972198600000000000000000)
# Initilize history of positions
hist = [[],[],[]]
pos = np.array([np.longdouble(0),
np.longdouble(0),
np.longdouble(0)])
def getPos(time):
"""Returns the earths position relative to the solar system barycentere
"""
return kernel[3, 399].compute(time)
def getRelPos(time):
global pos
"""Returns relitive position of the earth (which is the origin
in an earth moon system)"""
pos = np.array([np.longdouble(0),
np.longdouble(0),
np.longdouble(0)])
return pos
def log():
global pos
"""log current position"""
hist[0].append(pos[0])
hist[1].append(pos[1])
hist[2].append(pos[2])
./Objects/moon.py
import numpy as np
from jplephem.spk import SPK
import os
if not os.path.isfile('de430.bsp'):
raise ValueError('de430.bsp Was not found!')
kernel = SPK.open('de430.bsp')
# Initilize mass
mass = np.longdouble(7.34767309 * 10**22)
# Initilize history of positions
hist = [[],[],[]]
pos = []
def getPos(time):
"""Returns the moons position relative to the solar system barycentere"""
global pos
pos = kernel[3, 301].compute(time)
return pos
def getRelPos(time):
"""Returns relitive position of the moon (relative to the earth)"""
global pos
pos = kernel[3, 301].compute(time) - kernel[3, 399].compute(time)
return np.array([np.longdouble(pos[0]),
np.longdouble(pos[1]),
np.longdouble(pos[2])])
def log():
"""log current position"""
global pos
global hist
hist[0].append(pos[0])
hist[1].append(pos[1])
hist[2].append(pos[2])
(Note: if you would like to run this code you will need the de430.bsp file you can get that here)
My updated code can be found here.