I dont understand how to setup and run code with cython.
I added cdef, double, etc to pertinent pieces of my code.
setup.py of course the name hello isn't being used.
cython doc
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [Extension("hello", ["hello.pyx"])]
setup(
name = 'Hello world app',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)
Edit
Redefining the question to make it more of a concrete example.
I want to run this system of ODEs through Cython. I am not sure if double is the best choice but the numbers are on the order of magnitudes of 10 to the 10 and are non-integer.
So I want to call this in a bigger code where mus is a variable defined in the script calling the module. I have my setup.py file to compile the pyx file. I am not sure with what I need to do so I can call this ode now. Say I name the module 3bodyproblem. I would then call it in the scipt as import 3bodyproblem and then do `3bodyproblem.3bodyproblem(what would this input be)'
I have be reading intro to cython for odes but I am not sure how to use their example with mine. Also, if it needs to be in rk format, see the code below the first code.
Code 1
cdef deriv(double u, dt):
cdef double u[0], u[1], u[2]
return [u[3],
u[4],
u[5],
-mus * u[0] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[1] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[2] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5]
dt = np.linspace(0.0, t12sec, t12sec)
u = odeint(deriv, u0, dt, atol = 1e-13, rtol = 1e-13)
x, y, z, vx, vy, vz = u.T
Code 2
cdef deriv(dt, double u):
cdef double u[0], u[1], u[2], u[3], u[4], u[5]
return [u[3],
u[4],
u[5],
-mus * u[0] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[1] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[2] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5]
solver = ode(deriv).set_integrator('dop853')
solver.set_initial_value(u0)
z = np.zeros(300000)
for ii in range(300000):
z[ii] = solver.integrate(dt[ii])[0]
x, y, z, x2, y2, z2 = z.T
If I just try to compile code 1, cython doesn't know about the variables defined the larger .py file. I want to avoid setting variables in the cython code so it can be used else where with out re compiling. Here are the errors:
Error compiling Cython file:
------------------------------------------------------------
...
import numpy as np
from scipy.integrate import odeint
cdef deriv(double u, dt):
cdef double u[0], u[1], u[2]
^
------------------------------------------------------------
ODEcython.pyx:7:17: 'u' redeclared
Error compiling Cython file:
------------------------------------------------------------
...
import numpy as np
from scipy.integrate import odeint
cdef deriv(double u, dt):
cdef double u[0], u[1], u[2]
^
------------------------------------------------------------
ODEcython.pyx:7:23: 'u' redeclared
Error compiling Cython file:
------------------------------------------------------------
...
import numpy as np
from scipy.integrate import odeint
cdef deriv(double u, dt):
cdef double u[0], u[1], u[2]
^
------------------------------------------------------------
ODEcython.pyx:7:29: 'u' redeclared
Error compiling Cython file:
------------------------------------------------------------
...
-mus * u[0] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[1] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[2] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5]
dt = np.linspace(0.0, t12sec, t12sec)
^
------------------------------------------------------------
ODEcython.pyx:16:28: undeclared name not builtin: t12sec
Error compiling Cython file:
------------------------------------------------------------
...
-mus * u[1] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[2] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5]
dt = np.linspace(0.0, t12sec, t12sec)
u = odeint(deriv, u0, dt, atol = 1e-13, rtol = 1e-13)
^
------------------------------------------------------------
ODEcython.pyx:17:16: Cannot convert 'object (double, object)' to Python object
Error compiling Cython file:
------------------------------------------------------------
...
-mus * u[1] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[2] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5]
dt = np.linspace(0.0, t12sec, t12sec)
u = odeint(deriv, u0, dt, atol = 1e-13, rtol = 1e-13)
^
------------------------------------------------------------
ODEcython.pyx:17:20: undeclared name not builtin: u0
Error compiling Cython file:
------------------------------------------------------------
...
cdef deriv(double u, dt):
cdef double u[0], u[1], u[2]
return [u[3],
u[4],
u[5],
-mus * u[0] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
^
------------------------------------------------------------
ODEcython.pyx:11:17: undeclared name not builtin: mus
building 'ODEcython' extension
creating build
creating build/temp.linux-x86_64-2.7
x86_64-linux-gnu-gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.7 -c ODEcython.c -o build/temp.linux-x86_64-2.7/ODEcython.o
ODEcython.c:1:2: error: #error Do not use this file, it is the result of a failed Cython compilation.
error: command 'x86_64-linux-gnu-gcc' failed with exit status 1
Maybe this will help
So I want to use the module in a file like this: (different deriv function but just ignore that the idea is the same)
import numpy as np
from scipy.integrate import ode
import pylab
from mpl_toolkits.mplot3d import Axes3D
from scipy.optimize import brentq
from scipy.optimize import fsolve
me = 5.974 * 10 ** 24 # mass of the earth
mm = 7.348 * 10 ** 22 # mass of the moon
G = 6.67259 * 10 ** -20 # gravitational parameter
re = 6378.0 # radius of the earth in km
rm = 1737.0 # radius of the moon in km
r12 = 384400.0 # distance between the CoM of the earth and moon
rs = 66100.0 # distance to the moon SOI
Lambda = np.pi / 6 # angle at arrival to SOI
M = me + mm
d = 300 # distance the spacecraft is above the Earth
pi1 = me / M
pi2 = mm / M
mue = 398600.0 # gravitational parameter of earth km^3/sec^2
mum = G * mm # grav param of the moon
mu = mue + mum
omega = np.sqrt(mu / r12 ** 3)
# distance from the earth to Lambda on the SOI
r1 = np.sqrt(r12 ** 2 + rs ** 2 - 2 * r12 * rs * np.cos(Lambda))
vbo = 10.85 # velocity at burnout
h = (re + d) * vbo # angular momentum
energy = vbo ** 2 / 2 - mue / (re + d) # energy
v1 = np.sqrt(2.0 * (energy + mue / r1)) # refer to the close up of moon diagram
# refer to diagram for angles
theta1 = np.arccos(h / (r1 * v1))
phi1 = np.arcsin(rs * np.sin(Lambda) / r1)
#
p = h ** 2 / mue # semi-latus rectum
a = -mue / (2 * energy) # semi-major axis
eccen = np.sqrt(1 - p / a) # eccentricity
nu0 = 0
nu1 = np.arccos((p - r1) / (eccen * r1))
# Solving for the eccentric anomaly
def f(E0):
return np.tan(E0 / 2) - np.sqrt((1 - eccen) / (1 + eccen)) * np.tan(0)
E0 = brentq(f, 0, 5)
def g(E1):
return np.tan(E1 / 2) - np.sqrt((1 - eccen) / (1 + eccen)) * np.tan(nu1 / 2)
E1 = fsolve(g, 0)
# Time of flight from r0 to SOI
deltat = (np.sqrt(a ** 3 / mue) * (E1 - eccen * np.sin(E1)
- (E0 - eccen * np.sin(E0))))
# Solve for the initial phase angle
def s(phi0):
return phi0 + deltat * 2 * np.pi / (27.32 * 86400) + phi1 - nu1
phi0 = fsolve(s, 0)
nu = -phi0
gamma = 0 * np.pi / 180 # angle in radians of the flight path
vx = vbo * (np.sin(gamma) * np.cos(nu) - np.cos(gamma) * np.sin(nu))
# velocity of the bo in the x direction
vy = vbo * (np.sin(gamma) * np.sin(nu) + np.cos(gamma) * np.cos(nu))
# velocity of the bo in the y direction
xrel = (re + 300.0) * np.cos(nu) - pi2 * r12
yrel = (re + 300.0) * np.sin(nu)
u0 = [xrel, yrel, 0, vx, vy, 0]
def deriv(u, dt):
return [u[3], # dotu[0] = u[3]
u[4], # dotu[1] = u[4]
u[5], # dotu[2] = u[5]
(2 * omega * u[4] + omega ** 2 * u[0] - mue * (u[0] + pi2 * r12) /
np.sqrt(((u[0] + pi2 * r12) ** 2 + u[1] ** 2) ** 3) - mum *
(u[0] - pi1 * r12) /
np.sqrt(((u[0] - pi1 * r12) ** 2 + u[1] ** 2) ** 3)),
# dotu[3] = that
(-2 * omega * u[3] + omega ** 2 * u[1] - mue * u[1] /
np.sqrt(((u[0] + pi2 * r12) ** 2 + u[1] ** 2) ** 3) - mum * u[1] /
np.sqrt(((u[0] - pi1 * r12) ** 2 + u[1] ** 2) ** 3)),
# dotu[4] = that
0] # dotu[5] = 0
dt = np.linspace(0.0, 259200.0, 259200.0) # secs to run the simulation
u = odeint(deriv, u0, dt)
x, y, z, x2, y2, z2 = u.T
fig = pylab.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot(x, y, z, color = 'r')
# adding the moon
phi = np.linspace(0, 2 * np.pi, 100)
theta = np.linspace(0, np.pi, 100)
xm = rm * np.outer(np.cos(phi), np.sin(theta)) + r12 - pi2 * r12
ym = rm * np.outer(np.sin(phi), np.sin(theta))
zm = rm * np.outer(np.ones(np.size(phi)), np.cos(theta))
ax.plot_surface(xm, ym, zm, color = '#696969', linewidth = 0)
ax.auto_scale_xyz([-8000, 385000], [-8000, 385000], [-8000, 385000])
# adding the earth
xe = re * np.outer(np.cos(phi), np.sin(theta)) - pi2 * r12
ye = re * np.outer(np.sin(phi), np.sin(theta))
ze = re * np.outer(np.ones(np.size(phi)), np.cos(theta))
ax.plot_surface(xe, ye, ze, color = '#4169E1', linewidth = 0)
ax.auto_scale_xyz([-8000, 385000], [-8000, 385000], [-8000, 385000])
pylab.show()
try something like this:
# hello.pyx
cimport numpy as np
import numpy as np
def deriv(np.ndarray u, double mus):
return [u[3],
u[4],
u[5],
-mus * u[0] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[1] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5,
-mus * u[2] / (u[0] ** 2 + u[1] ** 2 + u[2] ** 2) ** 1.5]
build as before, than use it in python like this:
import hello
[...]
u_new = hello.deriv(u, mus) # or put it into odeint
cdef functions are only callable from the c-side, also you must put all parameters in the function definition.
Related
I have a function that typically takes in constant args and calculates volatility. I want to pass in a vector of different C's and K's to get an array of volatilities each associated with C[i], K[i]
def vol_calc(S, T, C, K, r, q, sigma):
d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
vega = (1 / np.sqrt(2 * np.pi)) * np.exp(-q * T) * np.sqrt(T) * np.exp((-si.norm.cdf(d1, 0.0, 1.0) ** 2) * 0.5)
tolerance = 0.000001
x0 = sigma
xnew = x0
xold = x0 - 1
while abs(xnew - xold) > tolerance:
xold = xnew
xnew = (xnew - fx - C) / vega
return abs(xnew)
but if I want to pass two arrays without turning into a nested loop, I thought I could just do:
def myfunction(S, T, r, q, sigma):
for x in K,C:
return same size as K,C
but I can't get it to work
How about this?
def vol_calc(S, T, C, K, r, q, sigma):
import numpy as np
output = np.zeros(len(C))
for num, (c, k) in enumerate(zip(C, K)):
d1 = (np.log(S / k) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
vega = (1 / np.sqrt(2 * np.pi)) * np.exp(-q * T) * np.sqrt(T) * np.exp((-si.norm.cdf(d1, 0.0, 1.0) ** 2) * 0.5)
tolerance = 0.000001
x0 = sigma
xnew = x0
xold = x0 - 1
while abs(xnew - xold) > tolerance:
xold = xnew
xnew = (xnew - fx - c) / vega
output[num] = abs(xnew)
return output
I'm trying to write a simple Python program which calculates the interference pattern from 2 incoming laser beams. Everything is working as it should but it's quite slow. I'm using a 400x400 array and it takes about 1.3 seconds to recalculate the intensity after I change a parameter. However, running the code with C++ takes about 0.18 seconds. So I'm wondering if I could improve something to speed things up?
My code so far:
def calculate_intensity_array():
laser1 = zeros((400, 400), dtype=complex)
laser2 = zeros((400, 400), dtype=complex)
data = zeros((400, 400), dtype=complex)
onoff_1 = laser1_onoff_var.get()
A_1 = laser1_intensity_var.get()
sigma_1 = laser1_sigma_var.get()
sin_phi_1 = sin((laser1_phi_var.get() / 180) * pi)
cos_phi_1 = cos((laser1_phi_var.get() / 180) * pi)
sin_theta_1 = sin((laser1_theta_var.get() / 180) * pi)
cos_theta_1 = cos((laser1_theta_var.get() / 180) * pi)
mu_x_1 = laser1_xpos_var.get()
mu_y_1 = laser1_ypos_var.get()
onoff_2 = laser2_onoff_var.get()
A_2 = laser2_intensity_var.get()
sigma_2 = laser2_sigma_var.get()
sin_phi_2 = sin((laser2_phi_var.get() / 180) * pi)
sin_theta_2 = sin((laser2_theta_var.get() / 180) * pi)
cos_phi_2 = cos((laser2_phi_var.get() / 180) * pi)
cos_theta_2 = cos((laser2_theta_var.get() / 180) * pi)
mu_x_2 = laser2_xpos_var.get()
mu_y_2 = laser2_ypos_var.get()
if onoff_1 == 0:
laser1 = zeros((400, 400), dtype=complex)
elif onoff_1 == 1:
for i in range(400):
for k in range(400):
laser1[i][k] = calculate_amplitude(
(k - 200) * 10,
(i - 200) * 10,
A_1,
sigma_1,
sin_phi_1,
cos_phi_1,
sin_theta_1,
cos_theta_1,
mu_x_1,
mu_y_1)
if onoff_2 == 0:
laser2 = zeros((400, 400), dtype=complex)
elif onoff_2 == 1:
for i in range(400):
for k in range(400):
laser2[i][k] = calculate_amplitude(
(k - 200) * 10,
(i - 200) * 10,
A_2,
sigma_2,
sin_phi_2,
cos_phi_2,
sin_theta_2,
cos_theta_2,
mu_x_2,
mu_y_2)
data = abs(laser1 + laser2) ** 2
return data
def calculate_amplitude(x, y, A, sigma, sin_phi, cos_phi,
sin_theta, cos_theta, mu_x, mu_y):
amplitude = A * (1 / (sqrt(2 * pi * (sigma ** 2 / cos_theta ** 2)))) *
exp(-((cos_phi *(x - mu_x) + sin_phi *(y - mu_y)) ** 2 * cos_theta ** 2) /
(2 * sigma ** 2)) *
(1 / (sqrt(2 * pi * sigma ** 2))) *
exp(-((-sin_phi * (x - mu_x) + cos_phi * (y - mu_y)) ** 2) /
(2 * sigma ** 2)) *
cmath.exp(1j *(2 * pi / 0.650) * sin_theta *
(cos_phi * (x - mu_x) + sin_phi * (y - mu_y)))
return amplitude
start = time.clock()
draw_data = calculate_intensity_array()
print time.clock()-start
Perhaps there is something that catches your eye that should be done different? The main calculation happens in calculate_amplitude, but I tried to input only sin, cos values, such that they don't have to be evaluated each time again.
The C++ equivalent looks like:
void calculate_intensity_array()
{
double intensity_array[400][400];
static complex<double> laser1[400][400];
static complex<double> laser2[400][400];
double A1 = 1;
double sigma1 = 2000;
double cos_theta1 = 0.9999619;
double sin_theta1 = 0.00872654;
double cos_phi1 = 1;
double sin_phi1 = 0;
double mu_x1 = 0.0;
double mu_y1 = 0.0;
double A2 = 1;
double sigma2 = 2000;
double cos_theta2 = 0.9999619;
double sin_theta2 = 0.00872654;
double cos_phi2 = 1;
double sin_phi2 = 0;
double mu_x2 = 0.0;
double mu_y2 = 0.0;
for (int i=0; i<400; i++)
{
for (int j=0; j<400; j++)
{
laser1[i][j] = calculate_amplitude((i-200)*10, (j-200)*10, A1,
sigma1, sin_phi1, cos_phi1,
sin_theta1, cos_theta1,
mu_x1, mu_y1);
laser2[i][j]=calculate_amplitude((i-200)*10, (j-200)*10, A2,
sigma2, sin_phi2, cos_phi2,
sin_theta2, cos_theta2,
mu_x2, mu_y2);
intensity_array[i][j] = pow(abs(laser1[i][j] + laser2[i][j]), 2);
}
}
}
complex<double> calculate_amplitude(double x, double y, double A,
double sigma, double sin_phi,
double cos_phi, double sin_theta,
double cos_theta, double mu_x,
double mu_y)
{
complex<double> output;
output = A * (1 / (sqrt(2 * M_PI * pow(sigma / cos_theta, 2)))) *
exp(-(pow(cos_phi * (x - 200 - mu_x) + sin_phi * (y - 200 - mu_y), 2) *
pow(cos_theta, 2)) / (2 * pow(sigma, 2))) *
(1 / (sqrt(2 * M_PI * pow(sigma, 2)))) *
exp(-(pow(-sin_phi * (x - 200 - mu_x) + cos_phi *
(y - 200 - mu_y), 2)) / (2 * pow(sigma, 2))) *
exp(complex<double>(0, (2 * M_PI / 0.650) * sin_theta *
(cos_phi * (x - 200 - mu_x) + sin_phi * (y - 200 - mu_y))));
return output;
}
Turn your code into C++ automagically!
The following python code looks like yours:
#pythran export loop(int, float, float, float, float, float, float, float, float)
from math import exp, sqrt, pi
import cmath
from numpy import empty
def loop(n,A, sigma, sin_phi, cos_phi,
sin_theta, cos_theta, mu_x, mu_y):
out = empty((n,n), dtype=complex)
for x in range(n):
for y in range(n):
out[x,y] = calculate_amplitude(x,y,A, sigma, sin_phi, cos_phi,
sin_theta, cos_theta, mu_x, mu_y)
return out
def calculate_amplitude(x, y, A, sigma, sin_phi, cos_phi,
sin_theta, cos_theta, mu_x, mu_y):
amplitude = (A * (1 / (sqrt(2 * pi * (sigma ** 2 / cos_theta ** 2)))) *
exp(-((cos_phi *(x - mu_x) + sin_phi *(y - mu_y)) ** 2 * cos_theta ** 2) /
(2 * sigma ** 2)) *
(1 / (sqrt(2 * pi * sigma ** 2))) *
exp(-((-sin_phi * (x - mu_x) + cos_phi * (y - mu_y)) ** 2) /
(2 * sigma ** 2)) *
cmath.exp(1j *(2 * pi / 0.650) * sin_theta *
(cos_phi * (x - mu_x) + sin_phi * (y - mu_y))))
return amplitude
Then compiling it with pythran:
$ pythran laser.py
And run it through timeit:
$ python -m timeit -s 'import laser' 'laser.loop(20,1,200,0.9,0.08,1,.5,.5,2)'
10000 loops, best of 3: 84.7 usec per loop
While the original code ran in:
$ python -m timeit -s 'import laser' 'laser.loop(20,1,200,0.9,0.08,1,.5,.5,2)'
100 loops, best of 3: 2.65 msec per loop
You can probably achieve a similar result with numba or cython :-)
Edit:
So I found out that NDSolve for ODE is using Runge Kutta to solve the equations.
How can I use the Runge Kutta method on my python code to solve the ODE I have below?
From my post on text files with float entries, I was able to determine that python and mathematica immediately start diverging with a tolerance of 10 to the negative 6.
End Edit
For last few hours, I have been trying to figure out why my solutions in Mathematica and Python differ by 5000 something km.
I am led to believe one program has a higher error tolerance when simulating over millions of seconds in flight time.
My question is which program is more accurate, and if it isn't python, how can I adjust the precision?
With Mathematica, I am less than 10km away from L4 where as with Python I am 5947 km away.
The codes are listed below:
Python
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from numpy import linspace
from scipy.optimize import brentq
me = 5.974 * 10 ** (24) # mass of the earth
mm = 7.348 * 10 ** (22) # mass of the moon
G = 6.67259 * 10 ** (-20) # gravitational parameter
re = 6378.0 # radius of the earth in km
rm = 1737.0 # radius of the moon in km
r12 = 384400.0 # distance between the CoM of the earth and moon
d = 300 # distance the spacecraft is above the Earth
pi1 = me / (me + mm)
pi2 = mm / (me + mm)
mue = 398600.0 # gravitational parameter of earth km^3/sec^2
mum = G * mm # grav param of the moon
mu = mue + mum
omega = np.sqrt(mu / (r12 ** 3))
nu = -np.pi / 4 # true anomaly pick yourself
xl4 = r12 / 2 - 4671 # x location of L4
yl4 = np.sqrt(3) / 2 * r12 # y
print("The location of L4 is", xl4, yl4)
# Solve for Jacobi's constant
def f(C):
return (omega ** 2 * (xl4 ** 2 + yl4 ** 2) + 2 * mue / r12 + 2 * mum / r12
+ 2 * C)
c = brentq(f, -5, 0)
print("Jacobi's constant is",c)
x0 = (re + 200) * np.cos(nu) - pi2 * r12 # x location of the satellite
y0 = (re + 200) * np.sin(nu) # y location
print("The satellite's initial position is", x0, y0)
vbo = (np.sqrt(omega ** 2 * (x0 ** 2 + y0 ** 2) + 2 * mue /
np.sqrt((x0 + pi2 * r12) ** 2 + y0 ** 2) + 2 * mum /
np.sqrt((x0 - pi1 * r12) ** 2 + y0 ** 2) + 2 * -1.21))
print("Burnout velocity is", vbo)
gamma = 0.4678 * np.pi / 180 # flight path angle pick yourself
vx = vbo * (np.sin(gamma) * np.cos(nu) - np.cos(gamma) * np.sin(nu))
# velocity of the bo in the x direction
vy = vbo * (np.sin(gamma) * np.sin(nu) + np.cos(gamma) * np.cos(nu))
# velocity of the bo in the y direction
print("The satellite's initial velocity is", vx, vy)
# r0 = [x, y, 0]
# v0 = [vx, vy, 0]
u0 = [x0, y0, 0, vx, vy, 0]
def deriv(u, dt):
return [u[3], # dotu[0] = u[3]
u[4], # dotu[1] = u[4]
u[5], # dotu[2] = u[5]
(2 * omega * u[4] + omega ** 2 * u[0] - mue * (u[0] + pi2 * r12) /
np.sqrt(((u[0] + pi2 * r12) ** 2 + u[1] ** 2) ** 3) - mum *
(u[0] - pi1 * r12) /
np.sqrt(((u[0] - pi1 * r12) ** 2 + u[1] ** 2) ** 3)),
# dotu[3] = that
(-2 * omega * u[3] + omega ** 2 * u[1] - mue * u[1] /
np.sqrt(((u[0] + pi2 * r12) ** 2 + u[1] ** 2) ** 3) - mum * u[1] /
np.sqrt(((u[0] - pi1 * r12) ** 2 + u[1] ** 2) ** 3)),
# dotu[4] = that
0] # dotu[5] = 0
dt = np.linspace(0.0, 6.0 * 86400.0, 2000000.0) # secs to run the simulation
u = odeint(deriv, u0, dt)
x, y, z, x2, y2, z2 = u.T
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot(x, y, z, color = 'r')
# adding the Lagrange point
phi = np.linspace(0, 2 * np.pi, 100)
theta = np.linspace(0, np.pi, 100)
xm = 2000 * np.outer(np.cos(phi), np.sin(theta)) + xl4
ym = 2000 * np.outer(np.sin(phi), np.sin(theta)) + yl4
zm = 2000 * np.outer(np.ones(np.size(phi)), np.cos(theta))
ax.plot_surface(xm, ym, zm, color = '#696969', linewidth = 0)
ax.auto_scale_xyz([-8000, 385000], [-8000, 385000], [-8000, 385000])
# adding the earth
phi = np.linspace(0, 2 * np.pi, 100)
theta = np.linspace(0, np.pi, 100)
xm = 2000 * np.outer(np.cos(phi), np.sin(theta))
ym = 2000 * np.outer(np.sin(phi), np.sin(theta))
zm = 2000 * np.outer(np.ones(np.size(phi)), np.cos(theta))
ax.plot_surface(xm, ym, zm, color = '#696969', linewidth = 0)
ax.auto_scale_xyz([-8000, 385000], [-8000, 385000], [-8000, 385000])
plt.show()
# The code below finds the distance between path and l4
my_x, my_y, my_z = (xl4, yl4, 0.0)
delta_x = x - my_x
delta_y = y - my_y
delta_z = z - my_z
distance = np.array([np.sqrt(delta_x ** 2 + delta_y ** 2 + delta_z ** 2)])
minimum = np.amin(distance)
print("Closet approach to L4 is", minimum)
Mathematica
ClearAll["Global`*"];
me = 5.974*10^(24);
mm = 7.348*10^(22);
G = 6.67259*10^(-20);
re = 6378;
rm = 1737;
r12 = 384400;
\[Pi]1 = me/(me + mm);
\[Pi]2 = mm/(me + mm);
M = me + mm;
\[Mu]1 = 398600;
\[Mu]2 = G*mm;
\[Mu] = \[Mu]1 + \[Mu]2;
\[CapitalOmega] = Sqrt[\[Mu]/r12^3];
\[Nu] = -\[Pi]/4;
xl4 = 384400/2 - 4671;
yl4 = Sqrt[3]/2*384400 // N;
Solve[\[CapitalOmega]^2*(xl4^2 + yl4^2) + 2 \[Mu]1/r12 +
2 \[Mu]2/r12 + 2*C == 0, C]
x = (re + 200)*Cos[\[Nu]] - \[Pi]2*r12 // N
y = (re + 200)*Sin[\[Nu]] // N
{{C -> -1.56824}}
-19.3098
-4651.35
vbo = Sqrt[\[CapitalOmega]^2*((x)^2 + (y)^2) +
2*\[Mu]1/Sqrt[(x + \[Pi]2*r12)^2 + (y)^2] +
2*\[Mu]2/Sqrt[(x - \[Pi]1*r12)^2 + (y)^2] + 2*(-1.21)]
10.8994
\[Gamma] = 0.4678*Pi/180;
vx = vbo*(Sin[\[Gamma]]*Cos[\[Nu]] - Cos[\[Gamma]]*Sin[\[Nu]]);
vy = vbo*(Sin[\[Gamma]]*Sin[\[Nu]] + Cos[\[Gamma]]*Cos[\[Nu]]);
r0 = {x, y, 0};
v0 = {vx, vy, 0}
{7.76974, 7.64389, 0}
s = NDSolve[{x1''[t] -
2*\[CapitalOmega]*x2'[t] - \[CapitalOmega]^2*
x1[t] == -\[Mu]1/((Sqrt[(x1[t] + \[Pi]2*r12)^2 +
x2[t]^2])^3)*(x1[t] + \[Pi]2*
r12) - \[Mu]2/((Sqrt[(x1[t] - \[Pi]1*r12)^2 +
x2[t]^2])^3)*(x1[t] - \[Pi]1*r12),
x2''[t] +
2*\[CapitalOmega]*x1'[t] - \[CapitalOmega]^2*
x2[t] == -\[Mu]1/(Sqrt[(x1[t] + \[Pi]2*r12)^2 + x2[t]^2])^3*
x2[t] - \[Mu]2/(Sqrt[(x1[t] - \[Pi]1*r12)^2 + x2[t]^2])^3*
x2[t], x3''[t] == 0, x1[0] == r0[[1]], x1'[0] == v0[[1]],
x2[0] == r0[[2]], x2'[0] == v0[[2]], x3[0] == r0[[3]],
x3'[0] == v0[[3]]}, {x1, x2, x3}, {t, 0, 1000000}];
ParametricPlot3D[
Evaluate[{x1[t], x2[t], x3[t]} /. s], {t, 0, 10*24*3600},
PlotStyle -> {Red, Thick}]
g1 = ParametricPlot3D[
Evaluate[{x1[t], x2[t], x3[t]} /. s], {t, 0, 5.75*3600*24},
PlotStyle -> {Red},
PlotRange -> {{-10000, 400000}, {-10000, 400000}}];
g2 = Graphics3D[{Blue, Opacity[0.6], Sphere[{-4671, 0, 0}, re]}];
g3 = Graphics3D[{Green, Opacity[0.6], Sphere[{379729, 0, 0}, rm]}];
g4 = Graphics3D[{Black, Sphere[{xl4, yl4, 0}, 2000]}];
Show[g2, g1, g3, g4, Boxed -> False]
(*XYdata=Flatten[Table[Evaluate[{x1[t],x2[t],x3[t]}/.s],{t,5.5*24*\
3600,5.78*24*3600,1}],1];
X1Y1data=Flatten[Table[Evaluate[{x1'[t],x2'[t],x3'[t]}/.s],{t,5.5*24*\
3600,5.78*24*3600,1}],1];
SetDirectory[NotebookDirectory[]];
Export["OrbitData.txt",XYdata,"CSV"];
Export["OrbVeloc.txt",X1Y1data,"CSV"];*)
If at this point your problem has reduced to just wanting to use Runge-Kutta, you can for example replace this line:
u = odeint(deriv, u0, dt)
with something like this:
#reverse the order of arguments
def deriv2(t,u):
return deriv(u,t)
# initialize a 4th order Runge-Kutta ODE solver
solver = ode(deriv2).set_integrator('dopri5')
solver.set_initial_value(u0)
u = np.empty((len(dt), 6))
u[0,:] = u0
for ii in range(1,len(dt)):
u[ii] = solver.integrate(dt[ii])
(+obviously replace the odeint import with ode).
Note that this is significantly slower for this type of ODE.
To use the dop853, use solver.set_integrator('dop853').
I re-wrote the def deriv part of the ode and it works now! So the Mathematica plot and the Python agree.
def deriv(u, dt):
return [u[3], # dotu[0] = u[3]
u[4], # dotu[1] = u[4]
u[5], # dotu[2] = u[5]
(2 * omega * u[4] + omega ** 2 * u[0] - mue * (u[0] + pi2 * r12) /
np.sqrt(((u[0] + pi2 * r12) ** 2 + u[1] ** 2) ** 3) - mum *
(u[0] - pi1 * r12) /
np.sqrt(((u[0] - pi1 * r12) ** 2 + u[1] ** 2) ** 3)),
# dotu[3] = that
(-2 * omega * u[3] + omega ** 2 * u[1] - mue * u[1] /
np.sqrt(((u[0] + pi2 * r12) ** 2 + u[1] ** 2) ** 3) - mum * u[1] /
np.sqrt(((u[0] - pi1 * r12) ** 2 + u[1] ** 2) ** 3)),
# dotu[4] = that
0] # dotu[5] = 0
One way to check the accuracy could be this:
take a time where the 2 trajectories are sufficiently different from one another (the last time/point in the trajectory for example). Use this as the new starting point and integrate back in time to the initial time (or, if you prefer, integrate forward in time, but reverse the velocities of all bodies present in the simulation). The final point in this simulation should be (close to) your initial point in the original simulation. By comparing how close you actually get to your original initial point, you can judge how good python vs mathematica solver routines are. My guess is that the Mathematica routines are much better, so all this Python thing is a waste of time.
Another way could be this:
With Mathematica what you get is an interpolating function which you can symbolically derive and then numerically evaluate its derivatives. Plug these values in the ODE at different points and see if they satisfy the ODE. Do something similar in python and compare the results.
How can I put a sphere of radius 1737 at the location of (384400,0,0)?
This sphere would be the moon in my trajectory.
Everything else with the code is fine, I just don't know how to add a sphere in that location with that radius.
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
me = 5.974 * 10 ** (24) # mass of the earth
mm = 7.348 * 10 ** (22) # mass of the moon
G = 6.67259 * 10 ** (-20) # gravitational parameter
re = 6378.0 # radius of the earth in km
rm = 1737.0 # radius of the moon in km
r12 = 384400.0 # distance between the CoM of the earth and moon
M = me + mm
pi1 = me / M
pi2 = mm / M
mue = 398600.0 # gravitational parameter of earth km^3/sec^2
mum = G * mm # grav param of the moon
mu = mue + mum
omega = np.sqrt(mu / r12 ** 3)
nu = -129.21 * np.pi / 180 # true anomaly angle in radian
x = 327156.0 - 4671
# x location where the moon's SOI effects the spacecraft with the offset of the
# Earth not being at (0,0) in the Earth-Moon system
y = 33050.0 # y location
vbo = 10.85 # velocity at burnout
gamma = 0 * np.pi / 180 # angle in radians of the flight path
vx = vbo * (np.sin(gamma) * np.cos(nu) - np.cos(gamma) * np.sin(nu))
# velocity of the bo in the x direction
vy = vbo * (np.sin(gamma) * np.sin(nu) + np.cos(gamma) * np.cos(nu))
# velocity of the bo in the y direction
xrel = (re + 300.0) * np.cos(nu) - pi2 * r12
# spacecraft x location relative to the earth
yrel = (re + 300.0) * np.sin(nu)
# r0 = [xrel, yrel, 0]
# v0 = [vx, vy, 0]
u0 = [xrel, yrel, 0, vx, vy, 0]
def deriv(u, dt):
n1 = -((mue * (u[0] + pi2 * r12) / np.sqrt((u[0] + pi2 * r12) ** 2
+ u[1] ** 2) ** 3)
- (mum * (u[0] - pi1 * r12) / np.sqrt((u[0] - pi1 * r12) ** 2
+ u[1] ** 2) ** 3))
n2 = -((mue * u[1] / np.sqrt((u[0] + pi2 * r12) ** 2 + u[1] ** 2) ** 3)
- (mum * u[1] / np.sqrt((u[0] - pi1 * r12) ** 2 + u[1] ** 2) ** 3))
return [u[3], # dotu[0] = u[3]
u[4], # dotu[1] = u[4]
u[5], # dotu[2] = u[5]
2 * omega * u[5] + omega ** 2 * u[0] + n1, # dotu[3] = that
omega ** 2 * u[1] - 2 * omega * u[4] + n2, # dotu[4] = that
0] # dotu[5] = 0
dt = np.arange(0.0, 320000.0, 1) # 200000 secs to run the simulation
u = odeint(deriv, u0, dt)
x, y, z, x2, y2, z2 = u.T
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot(x, y, z)
plt.show()
You can add the following code to draw the sphere, before the plt.show():
phi = np.linspace(0, 2 * np.pi, 100)
theta = np.linspace(0, np.pi, 100)
xm = rm * np.outer(np.cos(phi), np.sin(theta)) + r12
ym = rm * np.outer(np.sin(phi), np.sin(theta))
zm = rm * np.outer(np.ones(np.size(phi)), np.cos(theta))
ax.plot_surface(xm, ym, zm)
However, your moon will look all stretched out because the scale is not equal for all axes. In order to change the scales of the axes, you can add something like
ax.auto_scale_xyz([-50000, 400000], [0, 160000], [-130000, 130000])
before plt.show(). The result is still not completely right, but I leave it up to you to play around to get better results -- I just picked some numbers that make it look somewhat better.
All the code for a sphere is here:
http://matplotlib.org/examples/mplot3d/surface3d_demo2.html
From there you just have to shift in whichever direction you want and change the 10 to whatever radius you desire.
Edit
The only part that I would still like a solution for is getting the spheres to look spherical. If I scale the axis the same, the spheres are too small but spherical. Is there a way to then clip the unused portion off so it is zoomed in? Or can we need set the axis so big and get the desired effect?
End Edit
From plotting a sphere in python, I was able to two add a spheres to my plot.
However, I can't get them to look spherical no matter how I adjust the parameters. Additionally, adding 'g' or any other color to the end of there plots doesn't change there color.
I also tried color='g' but that didn't work either.
How can I get a spherical look (ax autoscale) and change the color?
#!/usr/bin/env python
# This porgram numerically solves the trajectory from the Earth to the moon
# with the specified flight path, true anomaly, and initial conditions.
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from numpy import linspace
me = 5.974 * 10 ** (24) # mass of the earth
mm = 7.348 * 10 ** (22) # mass of the moon
G = 6.67259 * 10 ** (-20) # gravitational parameter
re = 6378.0 # radius of the earth in km
rm = 1737.0 # radius of the moon in km
r12 = 384400.0 # distance between the CoM of the earth and moon
M = me + mm
pi1 = me / M
pi2 = mm / M
mue = 398600.0 # gravitational parameter of earth km^3/sec^2
mum = G * mm # grav param of the moon
mu = mue + mum
omega = np.sqrt(mu / r12 ** 3)
nu = -134.979 * np.pi / 180 # true anomaly angle in radian
x = 327156.0 - 4671
# x location where the moon's SOI effects the spacecraft with the offset of the
# Earth not being at (0,0) in the Earth-Moon system
y = 33050.0 # y location
vbo = 10.85 # velocity at burnout
gamma = 0 * np.pi / 180 # angle in radians of the flight path
vx = vbo * (np.sin(gamma) * np.cos(nu) - np.cos(gamma) * np.sin(nu))
# velocity of the bo in the x direction
vy = vbo * (np.sin(gamma) * np.sin(nu) + np.cos(gamma) * np.cos(nu))
# velocity of the bo in the y direction
xrel = (re + 300.0) * np.cos(nu) - pi2 * r12
yrel = (re + 300.0) * np.sin(nu)
# r0 = [xrel, yrel, 0]
# v0 = [vx, vy, 0]
u0 = [xrel, yrel, 0, vx, vy, 0]
def deriv(u, dt):
n1 = -((mue * (u[0] + pi2 * r12) / np.sqrt((u[0] + pi2 * r12) ** 2
+ u[1] ** 2) ** 3)
- (mum * (u[0] - pi1 * r12) / np.sqrt((u[0] - pi1 * r12) ** 2
+ u[1] ** 2) ** 3))
n2 = -((mue * u[1] / np.sqrt((u[0] + pi2 * r12) ** 2 + u[1] ** 2) ** 3)
- (mum * u[1] / np.sqrt((u[0] - pi1 * r12) ** 2 + u[1] ** 2) ** 3))
return [u[3], # dotu[0] = u[3]
u[4], # dotu[1] = u[4]
u[5], # dotu[2] = u[5]
2 * omega * u[4] + omega ** 2 * u[0] + n1, # dotu[3] = that
omega ** 2 * u[1] - 2 * omega * u[3] + n2, # dotu[4] = that
0] # dotu[5] = 0
dt = np.linspace(0.0, 320000.0, 1000000.0) # 200000 secs to run the simulation
u = odeint(deriv, u0, dt)
x, y, z, x2, y2, z2 = u.T
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot(x, y, z)
# adding the moon
phi = np.linspace(0, 2 * np.pi, 100)
theta = np.linspace(0, np.pi, 100)
xm = rm * np.outer(np.cos(phi), np.sin(theta)) + r12
ym = rm * np.outer(np.sin(phi), np.sin(theta))
zm = rm * np.outer(np.ones(np.size(phi)), np.cos(theta))
ax.plot_surface(xm, ym, zm)
ax.auto_scale_xyz([-50000, 400000], [0, 160000], [-130000, 130000])
# adding the earth
xe = re * np.outer(np.cos(phi), np.sin(theta)) - 4671
ye = re * np.outer(np.sin(phi), np.sin(theta))
ze = re * np.outer(np.ones(np.size(phi)), np.cos(theta))
ax.plot_surface(xe, ye, ze, 'g')
ax.auto_scale_xyz([-50000, 400000], [0, 160000], [-130000, 130000])
#
plt.show()
# The code below finds the distance between path and the moon
my_x, my_y, my_z = (384400,0,0)
delta_x = x - my_x
delta_y = y - my_y
delta_z = z - my_z
distance = np.array([np.sqrt(delta_x ** 2 + delta_y ** 2 + delta_z ** 2)])
minimum = np.amin(distance)
print(minimum)