''' '''
'''
ISC License
Copyright (c) 2016, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
'''
# import required modules:
import sys
import math
import numpy as np
from numpy import linalg as la
class ClassicElements(object):
a = None
e = None
i = None
Omega = None
omega = None
f = None
rmag = None
alpha = None
rPeriap = None
rApoap = None
N_DEBYE_PARAMETERS = 37 # orbitalMotion.h #
DB0_EPS = 1e-30
eps = 1e-13
maxIteration = 200
tolerance = 1e-15
AU = 149597870.693 # astronomical unit in units of kilometers #
D2R = (np.pi / 180.)
"""
Gravitational Constants mu = G*m, where m is the planet of the attracting planet. All units are km^3/s^2.
Values are obtained from SPICE kernels in http://naif.jpl.nasa.gov/pub/naif/generic_kernels/
"""
MU_SUN = 132712440023.310
MU_MERCURY = 22032.080
MU_VENUS = 324858.599
MU_EARTH = 398600.436
MU_MOON = 4902.799
MU_MARS = 42828.314
MU_JUPITER = 126712767.881
MU_SATURN = 37940626.068
MU_URANUS = 5794559.128
MU_NEPTUNE = 6836534.065
MU_PLUTO = 983.055
"""
planet information for major solar system bodies. Units are in km.
data taken from http://nssdc.gsfc.nasa.gov/planetary/planets.html
"""
# Sun #
REQ_SUN = 695000. # km #
# Mercury #
REQ_MERCURY = 2439.7 # km #
J2_MERCURY = 60.0e-6
SMA_MERCURY = 0.38709893 * AU
I_MERCURY = 7.00487 * D2R
E_MERCURY = 0.20563069
# Venus #
REQ_VENUS = 6051.8 # km #
J2_VENUS = 4.458e-6
SMA_VENUS = 0.72333199 * AU
I_VENUS = 3.39471 * D2R
E_VENUS = 0.00677323
# Earth #
REQ_EARTH = 6378.1366 # km, from SPICE #
RP_EARTH = 6356.7519 # km, from SPICE #
J2_EARTH = 1082.616e-6
J3_EARTH = -2.53881e-6
J4_EARTH = -1.65597e-6
J5_EARTH = -0.15e-6
J6_EARTH = 0.57e-6
SMA_EARTH = 1.00000011 * AU
I_EARTH = 0.00005 * D2R
E_EARTH = 0.01671022
OMEGA_EARTH = 0.00007292115 # Earth's planetary rotation rate, rad/sec #
# Moon #
REQ_MOON = 1737.4
J2_MOON = 202.7e-6
SMA_MOON = 0.3844e6
E_MOON = 0.0549
# Mars #
REQ_MARS = 3396.19 # km #
RP_MARS = 3376.2 # km #
J2_MARS = 1960.45e-6
SMA_MARS = 1.52366231 * AU
I_MARS = 1.85061 * D2R
E_MARS = 0.09341233
OMEGA_MARS = 0.00007262203 # Mars' polar rotation rate, rad/sec #
# Phobos #
REQ_PHOBOS = 11.2 # km #
# Deimos #
REQ_DEIMOS = 6.1 # km #
# Jupiter #
REQ_JUPITER = 71492.
J2_JUPITER = 14736.e-6
SMA_JUPITER = 5.20336301 * AU
I_JUPITER = 1.30530 * D2R
E_JUPITER = 0.04839266
# Saturn #
REQ_SATURN = 60268.
J2_SATURN = 16298.e-6
SMA_SATURN = 9.53707032 * AU
I_SATURN = 2.48446 * D2R
E_SATURN = 0.05415060
# Uranus #
REQ_URANUS = 25559.
J2_URANUS = 3343.43e-6
SMA_URANUS = 19.19126393 * AU
I_URANUS = 0.76986 * D2R
E_URANUS = 0.04716771
# Neptune #
REQ_NEPTUNE = 24746.
J2_NEPTUNE = 3411.e-6
SMA_NEPTUNE = 30.06896348 * AU
I_NEPTUNE = 1.76917 * D2R
E_NEPTUNE = 0.00858587
# Pluto #
REQ_PLUTO = 1137.
SMA_PLUTO = 39.48168677 * AU
I_PLUTO = 17.14175 * D2R
E_PLUTO = 0.24880766
[docs]def E2f(Ecc, e):
"""
Function: E2f
Purpose: Maps eccentric anomaly angles into true anomaly angles
This function requires the orbit to be either circular or
non-rectilinar elliptic orbit
Inputs:
Ecc = eccentric anomaly (rad)
e = eccentric (0 <= e < 1)
Outputs:
f = true anomaly (rad)
"""
if e >= 0.0 and e < 1.0:
f = 2.0 * math.atan2(math.sqrt(1.0 + e) * math.sin(Ecc / 2.0), math.sqrt(1.0 - e) * math.cos(Ecc / 2.0))
return f
raise ValueError('Error: E2f() received e = {}, the value of e should be 0 <= e < 1'.format(str(e)))
[docs]def E2M(Ecc, e):
"""
Function: E2M
Purpose: Maps the eccentric anomaly angle into the corresponding
mean elliptic anomaly angle. Both 2D and 1D elliptic
orbit are allowed.
Inputs:
Ecc = eccentric anomaly (rad)
e = eccentricity (0 <= e < 1)
Outputs:
M = mean elliptic anomaly (rad)
"""
if e >= 0.0 and e < 1.0:
M = Ecc - e * math.sin(Ecc)
return M
raise ValueError('Error: E2M() received e = {}, the value of e should be 0 <= e < 1'.format(str(e)))
[docs]def f2E(f, e):
"""
Function: f2E
Purpose: Maps true anomaly angles into eccentric anomaly angles.
This function requires the orbit to be either circular or
non-rectilinar elliptic orbit.
Inputs:
f = true anomaly angle (rad)
e = eccentricity (0 <= e < 1)
Outputs:
Ecc = eccentric anomaly (rad)
"""
if e >= 0.0 and e < 1.0:
Ecc = 2.0 * math.atan2(math.sqrt(1.0 - e) * math.sin(f / 2.0), math.sqrt(1.0 + e) * math.cos(f / 2.0))
return Ecc
raise ValueError('Error: f2E() received e = {}, the value of e should be 0 <= e < 1'.format(str(e)))
[docs]def f2H(f, e):
"""
Function: f2H
Purpose: Maps true anomaly angles into hyperbolic anomaly angles.
This function requires the orbit to be hyperbolic
Inputs:
f = true anomaly angle (rad)
e = eccentricity (e > 1)
Outputs:
H = hyperbolic anomaly (rad)
"""
if e > 1.0:
H = 2.0 * math.atanh(math.sqrt((e - 1.0) / (e + 1.0)) * math.tan(f / 2.0))
return H
raise ValueError('Error: f2H() received e = {}, the value of e should be 0 <= e < 1'.format(str(e)))
[docs]def H2f(H, e):
"""
Function: H2f
Purpose: Maps hyperbolic anomaly angles into true anomaly angles.
This function requires the orbit to be hyperbolic
Inputs:
H = hyperbolic anomaly (rad)
e = eccentricity (e > 1)
Outputs:
f = true anomaly angle (rad)
"""
if e > 1.0:
f = 2.0 * math.atan(math.sqrt((e + 1.0) / (e - 1.0)) * math.tanh(H / 2.0))
return f
raise ValueError('Error: H2f() received e = {}, the value of e should be 0 <= e < 1'.format(str(e)))
[docs]def H2N(H, e):
"""
Function: H2N
Purpose: Maps the hyperbolic anomaly angle H into the corresponding
mean hyperbolic anomaly angle N.
Inputs:
H = hyperbolic anomaly (rad)
e = eccentricity (e > 1)
Outputs:
N = mean hyperbolic anomaly (rad)
"""
if e > 1.0:
N = e * math.sinh(H) - H
return N
raise ValueError('Error: H2N() received e = {}, the value of e should be 0 <= e < 1'.format(str(e)))
[docs]def M2E(M, e):
"""
Purpose: Maps the mean elliptic anomaly angle into the corresponding
eccentric anomaly angle. Both 2D and 1D elliptic
orbit are allowed.
Inputs:
M = mean elliptic anomaly (rad)
e = eccentricity (0 <= e < 1)
Outputs:
Ecc = eccentric anomaly (rad)
"""
dE = 10.0 * eps
E1 = M
count = 0
if e >= 0.0 and e < 1.0:
while math.fabs(dE) > eps:
dE = (E1 - e * math.sin(E1) - M) / (1 - e * math.cos(E1))
E1 -= dE
count += 1
if count > maxIteration:
print('Iteration error in M2E({},{})'.format(str(M), str(e)))
dE = 0.0
return E1
raise ValueError('Error: M2E() received e = {}, the value of e should be 0 <= e < 1'.format(str(e)))
[docs]def N2H(N, e):
"""
Function: N2H
Purpose: Maps the mean hyperbolic anomaly angle N into the corresponding
hyperbolic anomaly angle H.
Inputs:
N = mean hyperbolic anomaly (rad)
e = eccentricity (e > 1)
Outputs:
H = hyperbolic anomaly (rad)
"""
dH = 10.0 * eps
H1 = N
count = 0
if e > 1.0:
while math.fabs(dH) > eps:
dH = (e * math.sinh(H1) - H1 - N) / (e * math.cosh(H1) - 1.0)
H1 -= dH
count += 1
if count > maxIteration:
print('Iteration error in M2E({},{})'.format(str(N), str(e)))
dH = 0.
return H1
raise ValueError('Error: N2H() received e = {}, the value of e should be 0 <= e < 1'.format(str(e)))
[docs]def elem2rv_parab(mu, elements):
"""
Function: elem2rv
Purpose: Translates the orbit elements
a - semi-major axis (km)
e - eccentricity
i - inclination (rad)
AN - ascending node (rad)
AP - argument of periapses (rad)
f - true anomaly angle (rad)
to the inertial Cartesian position and velocity vectors.
The attracting body is specified through the supplied
gravitational constant mu (units of km^3/s^2).
The code can handle the following cases:
circular: e = 0 a > 0
elliptical-2D: 0 < e < 1 a > 0
elliptical-1D: e = 1 a > 0 f = Ecc. Anom. here
parabolic: e = 1 rp = -a
hyperbolic: e > 1 a < 0
Note: to handle the parabolic case and distinguish it form the
rectilinear elliptical case, instead of passing along the
semi-major axis a in the "a" input slot, the negative radius
at periapses is supplied. Having "a" be negative and e = 1
is a then a unique identified for the code for the parabolic
case.
Inputs:
mu = gravitational parameter
elements = orbital elements
Outputs:
rVec = position vector
vVec = velocity vector
"""
# map classical elements structure into local variables #
a = elements.a
e = elements.e
i = elements.i
AN = elements.Omega
AP = elements.omega
f = elements.f
ir = np.zeros(3)
rVec = np.zeros(3)
vVec = np.zeros(3)
# TODO: Might want to have an error band on this equality #
if e == 1.0 and a > 0.0: # rectilinear elliptic orbit case #
Ecc = f # f is treated as ecc. anomaly #
r = a * (1.0 - e * math.cos(Ecc)) # orbit radius #
v = math.sqrt(2.0 * mu / r - mu / a)
ir[0] = math.cos(AN) * math.cos(AP) - math.sin(AN) * math.sin(AP) * math.cos(i)
ir[1] = math.sin(AN) * math.cos(AP) + math.cos(AN) * math.sin(AP) * math.cos(i)
ir[2] = math.sin(AP) * math.sin(i)
rVec = r * ir
if math.sin(Ecc) > 0.0:
vVec = -v * ir
else:
vVec = v * ir
else:
if e == 1.0 and a < 0.0: # parabolic case #
rp = -a # radius at periapses #
p = 2.0 * rp # semi-latus rectum #
else: # elliptic and hyperbolic cases #
p = a * (1.0 - e * e) # semi-latus rectum #
r = p / (1.0 + e * math.cos(f)) # orbit radius #
theta = AP + f # true latitude angle #
h = math.sqrt(mu * p) # orbit ang. momentum mag.
rVec[0] = r * (math.cos(AN) * math.cos(theta) - math.sin(AN) * math.sin(theta) * math.cos(i))
rVec[1] = r * (math.sin(AN) * math.cos(theta) + math.cos(AN) * math.sin(theta) * math.cos(i))
rVec[2] = r * (math.sin(theta) * math.sin(i))
vVec[0] = -mu / h * (math.cos(AN) * (math.sin(theta) + e * math.sin(AP)) + math.sin(AN) * (math.cos(
theta) + e * math.cos(AP)) * math.cos(i))
vVec[1] = -mu / h * (math.sin(AN) * (math.sin(theta) + e * math.sin(AP)) - math.cos(AN) * (math.cos(
theta) + e * math.cos(AP)) * math.cos(i))
vVec[2] = -mu / h * (-(math.cos(theta) + e * math.cos(AP)) * math.sin(i))
return rVec, vVec
[docs]def elem2rv(mu, elements):
"""
Function: elem2rv
Purpose: Translates the orbit elements
a - semi-major axis (km)
e - eccentricity
i - inclination (rad)
AN - ascending node (rad)
AP - argument of periapses (rad)
f - true anomaly angle (rad)
to the inertial Cartesian position and velocity vectors.
The attracting body is specified through the supplied
gravitational constant mu (units of km^3/s^2).
Inputs:
mu = gravitational parameter
elements = orbital elements
Outputs:
rVec = position vector
vVec = velocity vector
"""
rVec = np.zeros(3)
vVec = np.zeros(3)
if 1.0 + elements.e * math.cos(elements.f) < tolerance:
print('WARNING: Radius is near infinite in elem2rv conversion.')
# Calculate the semilatus rectum and the radius #
p = elements.a * (1.0 - elements.e * elements.e)
r = p / (1.0 + elements.e * math.cos(elements.f))
theta = elements.omega + elements.f
rVec[0] = r * (
math.cos(theta) * math.cos(elements.Omega) - math.cos(elements.i) * math.sin(theta) * math.sin(
elements.Omega))
rVec[1] = r * (
math.cos(theta) * math.sin(elements.Omega) + math.cos(elements.i) * math.sin(theta) * math.cos(
elements.Omega))
rVec[2] = r * (math.sin(theta) * math.sin(elements.i))
if math.fabs(p) < tolerance:
if math.fabs(1.0 - elements.e) < tolerance:
# Rectilinear orbit #
raise ValueError('elem2rv does not support rectilinear orbits')
# Parabola #
rp = -elements.a
p = 2.0 * rp
h = math.sqrt(mu * p)
vVec[0] = -mu / h * (math.cos(elements.Omega) * (elements.e * math.sin(elements.omega) + math.sin(theta)) +
math.cos(elements.i) * (
elements.e * math.cos(elements.omega) + math.cos(theta)) * math.sin(elements.Omega))
vVec[1] = -mu / h * (math.sin(elements.Omega) * (elements.e * math.sin(elements.omega) + math.sin(theta)) -
math.cos(elements.i) * (elements.e * math.cos(elements.omega) + math.cos(theta)) *
math.cos(elements.Omega))
vVec[2] = mu / h * (elements.e * math.cos(elements.omega) + math.cos(theta)) * math.sin(elements.i)
return rVec, vVec
[docs]def rv2elem_parab(mu, rVec, vVec):
"""
Function: rv2elem
Purpose: Translates the orbit elements inertial Cartesian position
vector rVec and velocity vector vVec into the corresponding
classical orbit elements where
a - semi-major axis (km)
e - eccentricity
i - inclination (rad)
AN - ascending node (rad)
AP - argument of periapses (rad)
f - true anomaly angle (rad)
if the orbit is rectilinear, then this will be the
eccentric or hyperbolic anomaly
The attracting body is specified through the supplied
gravitational constant mu (units of km^3/s^2).
The code can handle the following cases:
circular: e = 0 a > 0
elliptical-2D: 0 < e < 1 a > 0
elliptical-1D: e = 1 a > 0
parabolic: e = 1 a = -rp
hyperbolic: e > 1 a < 0
For the parabolic case the semi-major axis is not defined.
In this case -rp (radius at periapses) is returned instead
of a. For the circular case, the AN and AP are ill-defined,
along with the associated ie and ip unit direction vectors
of the perifocal frame. In this circular orbit case, the
unit vector ie is set equal to the normalized inertial
position vector ir.
Inputs:
mu = gravitational parameter
rVec = position vector
vVec = velocity vector
Outputs:
elements = orbital elements
TODO: (SAO) Modify this code to return true longitude of periapsis
(non-circular, equatorial), argument of latitude (circular, inclined),
and true longitude (circular, equatorial) when appropriate instead of
simply zeroing out omega and Omega
"""
dum = np.zeros(3)
dum2 = np.zeros(3)
ie = np.zeros(3)
ip = np.zeros(3)
ih = np.zeros(3)
cVec = np.zeros(3)
hVec = np.zeros(3)
ir = np.zeros(3)
elements = ClassicElements()
# compute orbit radius #
r = la.norm(rVec)
elements.rmag = r
ir = v3Normalize(rVec)
# compute the angular momentum vector #
hVec = np.cross(rVec, vVec)
h = la.norm(hVec)
# compute the eccentricity vector #
cVec = np.cross(vVec, hVec)
dum = (-mu / r) * rVec
cVec = cVec + dum
elements.e = la.norm(cVec) / mu
# compute semi-major axis #
elements.alpha = 2.0 / r - np.dot(vVec, vVec) / mu
if math.fabs(elements.alpha) > eps:
# elliptic or hyperbolic case #
elements.a = 1.0 / elements.alpha
elements.rPeriap = elements.a * (1.0 - elements.e)
elements.rApoap = elements.a * (1.0 + elements.e)
else:
# parabolic case #
elements.alpha = 0.
p = h * h / mu
rp = p / 2.0
elements.a = -rp # a is not defined for parabola, so - rp is returned instead #
elements.e = 1.0
elements.rPeriap = rp
elements.rApoap = -rp # periapses radius doesn't exist, returning -rp instead #
if h < eps: # rectilinear motion case #
dum = np.array([0.0, 0.0, 1.0])
dum2 = np.array([0.0, 1.0, 0.0])
ih = np.cross(ie, dum)
ip = np.cross(ie, dum2)
if la.norm(ih) > la.norm(ip):
ih = v3Normalize(ih)
else:
ih = v3Normalize(ip)
ip = np.cross(ih, ie)
else:
ih = v3Normalize(hVec)
if math.fabs(elements.e) > eps:
ie = (1.0 / mu / elements.e) * cVec
else:
ie = ir
# circular orbit case. Here ie, ip are arbitrary, as long as they #
# are perpenticular to the ih vector. #
ip = np.cross(ih, ie)
# compute the 3-1-3 orbit plane orientation angles #
elements.i = math.acos(ih[2])
if elements.i > eps and elements.i < np.pi - eps:
elements.Omega = math.atan2(ih[0], -ih[1])
elements.omega = math.atan2(ie[2], ip[2])
else:
elements.Omega = 0.
elements.omega = math.atan2(ie[1], ie[0])
if h < eps: # rectilinear motion case #
if elements.alpha > 0: # elliptic case #
Ecc = math.acos(1 - r * elements.alpha)
if np.dot(rVec, vVec) > 0:
Ecc = 2.0 * np.pi - Ecc
elements.f = Ecc # for this mode the eccentric anomaly is returned #
else: # hyperbolic case #
H = math.acosh(r * elements.alpha + 1)
if np.dot(rVec, vVec) < 0:
H = 2.0 * np.pi - H
elements.f = H # for this mode the hyperbolic anomaly is returned #
else:
# compute true anomaly #
dum = np.cross(ie, ir)
elements.f = math.atan2(np.dot(dum, ih), np.dot(ie, ir))
return elements
[docs]def rv2elem(mu, rVec, vVec):
"""
Function: rv2elem
Purpose: Translates the orbit elements inertial Cartesian position
vector rVec and velocity vector vVec into the corresponding
classical orbit elements where
a - semi-major axis (km)
e - eccentricity
i - inclination (rad)
AN - ascending node (rad)
AP - argument of periapses (rad)
f - true anomaly angle (rad)
if the orbit is rectilinear, then this will be the
eccentric or hyperbolic anomaly
The attracting body is specified through the supplied
gravitational constant mu (units of km^3/s^2).
Inputs:
mu = gravitational parameter
rVec = position vector
vVec = velocity vector
Outputs:
elements = orbital elements
"""
hVec = [0.0] * 3
v3 = [0.0] * 3
nVec = [0.0] * 3
eVec = [0.0] * 3
eVec2 = [0.0] * 3
elements = ClassicElements()
if (np.isnan(np.sum(rVec)) or np.isnan(np.sum(vVec))):
print("ERROR: received NAN rVec or vVec values.")
elements.a = np.NaN
elements.alpha = np.NaN
elements.e = np.NaN
elements.i = np.NaN
elements.AN = np.NaN
elements.AP = np.NaN
elements.f = np.NaN
elements.rmag = np.NaN
return
# Calculate the specific angular momentum and its magnitude #
hVec = np.cross(rVec, vVec)
h = la.norm(hVec)
p = h * h / mu
# Calculate the line of nodes #
v3 = np.array([0.0, 0.0, 1.0])
nVec = np.cross(v3, hVec)
n = la.norm(nVec)
# Orbit eccentricity and energy #
r = la.norm(rVec)
v = la.norm(vVec)
eVec = (v * v / mu - 1.0 / r) * rVec
v3 = (np.dot(rVec, vVec) / mu) * vVec
eVec = eVec - v3
elements.e = la.norm(eVec)
elements.rmag = r
elements.rPeriap = p / (1.0 + elements.e)
# compute semi-major axis #
elements.alpha = 2.0 / r - v * v / mu
if math.fabs(elements.alpha) > eps:
# elliptic or hyperbolic case #
elements.a = 1.0 / elements.alpha
elements.rApoap = p / (1.0 - elements.e)
else:
rp = p / 2.
elements.a = -rp
elements.rApoap = -1.0
# Calculate the inclination #
elements.i = math.acos(hVec[2] / h)
if elements.e >= 1e-11 and elements.i >= 1e-11:
# Case 1: Non-circular, inclined orbit #
elements.Omega = math.acos(nVec[0] / n)
if nVec[1] < 0.0:
elements.Omega = 2.0 * np.pi - elements.Omega
elements.omega = math.acos(np.dot(nVec, eVec) / n / elements.e)
if eVec[2] < 0.0:
elements.omega = 2.0 * np.pi - elements.omega
elements.f = math.acos(np.dot(eVec, rVec) / elements.e / r)
if np.dot(rVec, vVec) < 0.0:
elements.f = 2.0 * np.pi - elements.f
elif elements.e >= 1e-11 and elements.i < 1e-11:
# Case 2: Non-circular, equatorial orbit #
# Equatorial orbit has no ascending node #
elements.Omega = 0.0
# True longitude of periapsis, omegatilde_true #
elements.omega = math.acos(eVec[0] / elements.e)
if eVec[1] < 0.0:
elements.omega = 2.0 * np.pi - elements.omega
elements.f = math.acos(np.dot(eVec, rVec) / elements.e / r)
if np.dot(rVec, vVec) < 0.0:
elements.f = 2.0 * np.pi - elements.f
elif elements.e < 1e-11 and elements.i >= 1e-11:
# Case 3: Circular, inclined orbit #
elements.Omega = math.acos(nVec[0] / n)
if nVec[1] < 0.0:
elements.Omega = 2.0 * np.pi - elements.Omega
elements.omega = 0.0
# Argument of latitude, u = omega + f #
elements.f = math.acos(np.dot(nVec, rVec) / n / r)
if rVec[2] < 0.0:
elements.f = 2.0 * np.pi - elements.f
elif elements.e < 1e-11 and elements.i < 1e-11:
# Case 4: Circular, equatorial orbit #
elements.Omega = 0.0
elements.omega = 0.0
# True longitude, lambda_true #
elements.f = math.acos(rVec[0] / r)
if rVec[1] < 0:
elements.f = 2.0 * np.pi - elements.f
else:
print("Error: rv2elem couldn't identify orbit type.")
if elements.e > 1.0 and math.fabs(elements.f) > np.pi:
twopiSigned = math.copysign(2.0 * np.pi, elements.f)
elements.f -= twopiSigned
return elements
[docs]def atmosphericDensity(alt):
"""
* Function: atmosphericDensity
* Purpose: This program computes the atmospheric density based on altitude
* supplied by user. This function uses a curve fit based on
* atmospheric data from the Standard Atmosphere 1976 Data. This
* function is valid for altitudes ranging from 100km to 1000km.
*
* Note: This code can only be applied to spacecraft orbiting the Earth
* Inputs:
* alt = altitude in km
* Outputs:
* density = density at the given altitude in kg/m^3
"""
# Smooth exponential drop-off after 1000 km #
if alt > 1000.:
logdensity = (-7E-05) * alt - 14.464
density = math.pow(10., logdensity)
return density
# Calculating the density based on a scaled 6th order polynomial fit to the log of density #
val = (alt - 526.8000) / 292.8563
logdensity = 0.34047 * math.pow(val, 6) - 0.5889 * math.pow(val, 5) - 0.5269 * math.pow(val, 4) \
+ 1.0036 * math.pow(val, 3) + 0.60713 * math.pow(val, 2) - 2.3024 * val - 12.575
# Calculating density by raising 10 to the log of density #
density = math.pow(10., logdensity)
return density
[docs]def debyeLength(alt):
"""
* Function: debyeLength
* Purpose: This program computes the debyeLength length for a given
* altitude and is valid for altitudes ranging
* from 200 km to GEO (35000km). However, all values above
* 1000 km are HIGHLY speculative at this point.
* Inputs:
* alt = altitude in km
* Outputs:
* debye = debye length given in m
"""
X = [200.0, 250.0, 300.0, 350.0, 400., 450., 500., 550., 600., 650., 700., 750., 800., 850.,
900., 950., 1000., 1050., 1100., 1150., 1200., 1250., 1300., 1350., 1400., 1450.,
1500., 1550., 1600., 1650., 1700., 1750., 1800., 1850., 1900., 1950., 2000.]
Y = [5.64E-03, 3.92E-03, 3.24E-03, 3.59E-03, 4.04E-03, 4.28E-03, 4.54E-03, 5.30E-03, 6.55E-03,
7.30E-03, 8.31E-03, 8.38E-03, 8.45E-03, 9.84E-03, 1.22E-02, 1.37E-02, 1.59E-02, 1.75E-02,
1.95E-02, 2.09E-02, 2.25E-02, 2.25E-02, 2.25E-02, 2.47E-02, 2.76E-02, 2.76E-02, 2.76E-02,
2.76E-02, 2.76E-02, 2.76E-02, 2.76E-02, 3.21E-02, 3.96E-02, 3.96E-02, 3.96E-02, 3.96E-02, 3.96E-02]
# Flat debyeLength length for altitudes above 2000 km #
if alt > 2000.0 and alt <= 30000.0:
alt = 2000.0
elif alt > 30000.0 and alt <= 35000.0:
debyedist = 0.1 * alt - 2999.7
return debyedist
elif alt < 200.0 or alt > 35000.0:
raise ValueError("ERROR: debyeLength() received alt = {}, the value of alt should be in the range "
"of [200 35000].".format(str(alt)))
# Interpolation of data #
i = 0
for i in range(0, N_DEBYE_PARAMETERS - 1):
if X[i + 1] > alt:
break
a = (alt - X[i]) / (X[i + 1] - X[i])
debyedist = Y[i] + a * (Y[i + 1] - Y[i])
return debyedist
[docs]def atmosphericDrag(Cd, A, m, rvec, vvec):
"""
* Function: atmosphericDrag
* Purpose: This program computes the atmospheric drag acceleration
* vector acting on a spacecraft.
* Note the acceleration vector output is inertial, and is
* only valid for altitudes up to 1000 km.
* Afterwards the drag force is zero. Only valid for Earth.
* Inputs:
* Cd = drag coefficient of the spacecraft
* A = cross-sectional area of the spacecraft in m^2
* m = mass of the spacecraft in kg
* rvec = Inertial position vector of the spacecraft in km [x;y;z]
* vvec = Inertial velocity vector of the spacecraft in km/s [vx;vy;vz]
* Outputs:
* advec = The inertial acceleration vector due to atmospheric
* drag in km/sec^2
"""
# find the altitude and velocity #
r = la.norm(rvec)
v = la.norm(vvec)
alt = r - REQ_EARTH
advec = np.zeros(3)
# Checking if user supplied a orbital position is insede the earth #
if alt <= 0.:
print("ERROR: atmosphericDrag() received rvec = [{} {} {}].". \
format(str(rvec[1]), str(rvec[2]), str(rvec[3])))
print('The value of rvec should produce a positive altitude for the Earth.')
advec.fill(np.NaN)
return
# get the Atmospheric density at the given altitude in kg/m^3 #
density = atmosphericDensity(alt)
# compute the magnitude of the drag acceleration #
ad = ((-0.5) * density * (Cd * A / m) * (math.pow(v * 1000., 2))) / 1000.
# computing the vector for drag acceleration #
advec = (ad / v) * vvec
return advec
[docs]def jPerturb(rvec, num, planet):
"""
* Function: jPerturb
* Purpose: Computes the J2_EARTH-J6_EARTH zonal graviational perturbation
* accelerations.
* Inputs:
* rvec = Cartesian Position vector in kilometers [x;y;z].
* num = Corresponds to which J components to use,
* must be an integer between 2 and 6.
* (note: Additive- 2 corresponds to J2_EARTH while 3 will
* correspond to J2_EARTH + J3_EARTH)
* planet:
CELESTIAL_MERCURY
CELESTIAL_VENUS
CELESTIAL_EARTH
CELESTIAL_MOON
CELESTIAL_MARS
*CELESTIAL_PHOBOS
*CELESTIAL_DEIMOS
CELESTIAL_JUPITER
*CELESTIAL_SATURN
CELESTIAL_URANUS
CELESTIAL_NEPTUNE
*CELESTIAL_PLUTO
*CELESTIAL_SUN
* Outputs:
* ajtot = The total acceleration vector due to the J
* perturbations in km/sec^2 [accelx;accely;accelz]
"""
ajtot = np.zeros(3)
# default #
mu = MU_EARTH
req = REQ_EARTH
J2 = J2_EARTH
J3 = J3_EARTH
J4 = J4_EARTH
J5 = J5_EARTH
J6 = J6_EARTH
if planet == 'CELESTIAL_MERCURY':
mu = MU_MERCURY
req = REQ_MERCURY
J2 = J2_MERCURY
J3 = 0.0
J4 = 0.0
J5 = 0.0
J6 = 0.0
elif planet == 'CELESTIAL_VENUS':
mu = MU_VENUS
req = REQ_VENUS
J2 = J2_VENUS
J3 = 0.0
J4 = 0.0
J5 = 0.0
J6 = 0.0
elif planet == 'CELESTIAL_MOON':
mu = MU_MOON
req = REQ_MOON
J2 = J2_MOON
J3 = 0.0
J4 = 0.0
J5 = 0.0
J6 = 0.0
elif planet == 'CELESTIAL_MARS':
mu = MU_MARS
req = REQ_MARS
J2 = J2_MARS
J3 = 0.0
J4 = 0.0
J5 = 0.0
J6 = 0.0
elif planet == 'CELESTIAL_JUPITER':
mu = MU_JUPITER
req = REQ_JUPITER
J2 = J2_JUPITER
J3 = 0.0
J4 = 0.0
J5 = 0.0
J6 = 0.0
elif planet == 'CELESTIAL_URANUS':
mu = MU_URANUS
req = REQ_URANUS
J2 = J2_URANUS
J3 = 0.0
J4 = 0.0
J5 = 0.0
J6 = 0.0
elif planet == 'CELESTIAL_NEPTUNE':
mu = MU_NEPTUNE
req = REQ_NEPTUNE
J2 = J2_NEPTUNE
J3 = 0.0
J4 = 0.0
J5 = 0.0
J6 = 0.0
elif planet == 'CELESTIAL_PLUTO':
return
elif planet == 'CELESTIAL_SUN':
return
# Calculate the J perturbations #
x = rvec[0]
y = rvec[1]
z = rvec[2]
r = la.norm(rvec)
# Error Checking #
if num < 2 or num > 6:
raise ValueError("ERROR: jPerturb() received num = {}.The value of num should be 2 <= num <= 6.".format(str(num)))
# Calculating the total acceleration based on user input #
if num >= 2:
ajtot = np.array([(1.0 - 5.0 * math.pow(z / r, 2.0)) * (x / r),
(1.0 - 5.0 * math.pow(z / r, 2.0)) * (y / r),
(3.0 - 5.0 * math.pow(z / r, 2.0)) * (z / r)])
ajtot = (-3.0 / 2.0 * J2 * (mu / math.pow(r, 2.0)) * math.pow(req / r, 2.0)) * ajtot
if num >= 3:
temp = np.array([5.0 * (7.0 * math.pow(z / r, 3.0) - 3.0 * (z / r)) * (x / r),
5.0 * (7.0 * math.pow(z / r, 3.0) - 3.0 * (z / r)) * (y / r),
-3.0 * (10.0 * math.pow(z / r, 2.0) - (35.0 / 3.0) * math.pow(z / r, 4.0) - 1.0)])
temp2 = (1.0 / 2.0 * J3 * (mu / math.pow(r, 2.0)) * math.pow(req / r, 3.0)) * temp
ajtot = ajtot + temp2
if num >= 4:
temp = np.array([(3.0 - 42.0 * math.pow(z / r, 2.0) + 63.0 * math.pow(z / r, 4.0)) * (x / r),
(3.0 - 42.0 * math.pow(z / r, 2.0) + 63.0 * math.pow(z / r, 4.0)) * (y / r),
(15.0 - 70.0 * math.pow(z / r, 2) + 63.0 * math.pow(z / r, 4.0)) * (z / r)])
temp2 = (5.0 / 8.0 * J4 * (mu / math.pow(r, 2.0)) * math.pow(req / r, 4.0)) * temp
ajtot = ajtot + temp2
if num >= 5:
temp = np.array([3.0 * (35.0 * (z / r) - 210.0 * math.pow(z / r, 3.0) + 231.0 * math.pow(z / r, 5.0)) * (x / r),
3.0 * (35.0 * (z / r) - 210.0 * math.pow(z / r, 3.0) + 231.0 * math.pow(z / r, 5.0)) * (y / r),
-(15.0 - 315.0 * math.pow(z / r, 2.0) + 945.0 * math.pow(z / r, 4.0) - 693.0 * math.pow(z / r,
6.0))])
temp2 = (1.0 / 8.0 * J5 * (mu / math.pow(r, 2.0)) * math.pow(req / r, 5.0)) * temp
ajtot = ajtot + temp2
if num >= 6:
temp = np.array([(35.0 - 945.0 * math.pow(z / r, 2) + 3465.0 * math.pow(z / r, 4.0) - 3003.0 * math.pow(z / r,
6.0)) * (
x / r),
(35.0 - 945.0 * math.pow(z / r, 2.0) + 3465.0 * math.pow(z / r, 4.0) - 3003.0 * math.pow(z / r,
6.0)) * (
y / r),
-(3003.0 * math.pow(z / r, 6.0) - 4851.0 * math.pow(z / r, 4.0) + 2205.0 * math.pow(z / r,
2.0) - 245.0) * (
z / r)])
temp2 = (-1.0 / 16.0 * J6 * (mu / math.pow(r, 2.0)) * math.pow(req / r, 6.0)) * temp
ajtot = ajtot + temp2
return ajtot
[docs]def solarRad(A, m, sunvec):
"""
* Function: solarRad
* Purpose: Computes the inertial solar radiation force vectors
* based on cross-sectional Area and mass of the spacecraft
* and the position vector of the planet to the sun.
* Note: It is assumed that the solar radiation pressure decreases
* quadratically with distance from sun (in AU)
*
* Solar Radiation Equations obtained from
* Earth Space and Planets Journal Vol. 51, 1999 pp. 979-986
* Inputs:
* A = Cross-sectional area of the spacecraft that is facing
* the sun in m^2.
* m = The mass of the spacecraft in kg.
* sunvec = Position vector to the Sun in units of AU.
* Earth has a distance of 1 AU.
* Outputs:
* arvec = The inertial acceleration vector due to the effects
* of Solar Radiation pressure in km/sec^2. The vector
* components of the output are the same as the vector
* components of the sunvec input vector.
"""
# Solar Radiation Flux #
flux = 1372.5398
# Speed of light #
c = 299792458.
# Radiation pressure coefficient #
Cr = 1.3
# Magnitude of position vector #
sundist = la.norm(sunvec)
# Computing the acceleration vector #
arvec = ((-Cr * A * flux) / (m * c * math.pow(sundist, 3)) / 1000.) * sunvec
return arvec
def v3Normalize(v):
result = np.zeros(3)
norm = la.norm(v)
if norm > DB0_EPS:
result = (1. / norm) * v
return result