Module odlib
Expand source code
import numpy as np
import math
import time
import numpy.polynomial.polynomial as poly
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from astropy.io import fits
from odlib import *
# angles/radians (declination and right ascension)
def decToRad(dec:float)->float:
""" Given decimal value return in radians
Args:
dec (float): decimal value
Returns:
float: value in radians
"""
return float(dec)/180*math.pi
def HMStoDeg(h:float,m:float,s:float,convert=False)->float:
""" Given HMS, return in degrees or radians
Args:
h (float): hours
m (float): minutes
s (float): seconds
convert (bool): True to convert to radians
Returns:
float: value in degrees or radians
"""
try: h,m,s=float(h),float(m),float(s)
except: raise Exception("Values must be floats")
res = h*15 + m/60*15 + s/3600*15
res%=360
return np.deg2rad(res) if convert else res
def DMStoDeg(d:float,arcm:float,arcs:float,convert=False)->float:
""" Given DMS, return in degrees or radians
Args:
d (float): degrees
m (float): minutes
s (float): seconds
convert (bool): True to convert to radians
Returns:
float: value in degrees or radians
"""
return np.deg2rad(math.copysign(abs(d)+arcm/60+arcs/3600,d)) if convert else math.copysign(abs(d)+arcm/60+arcs/3600,d)
def RAdecimalToHMS(dec:float)->tuple:
""" Converts from decimal to HMS (right ascension)
Args:
dec (float): decimal value
Returns:
tuple: (hours, minutes, seconds)
"""
dec%=360
h=dec/15
m=(dec%15)/15*60
s=m%1*60
h//=1
m//=1
return (h,m,s)
def DECdecimalToDMS(dec:float)->tuple:
""" Converts from decimal to DMS (declination)
Args:
dec (float): decimal value
Returns:
tuple: (degrees, minutes, seconds)
"""
d=math.trunc(dec)
dec=abs(dec)-abs(d)
m=math.trunc(dec*60)
dec-=m/60
s=dec*3600
return (d,m,s)
def getAngle(sin:float,cos:float)->float:
""" Returns the angle (in radians) in the correct quadrant given sin and cos
Args:
sin (float): sin value
cos (float): cos value
Returns:
float: resulting angle in radians
"""
return math.atan2(sin,cos) % (math.pi*2)
# vector stuff
def cross(v1:list, v2:list)->list:
""" Returns the cross product of two 3D vectors
Args:
v1 (list): vector1
v2 (list): vector2
Returns:
list: resulting cross product
"""
return [(v1[1]*v2[2] - v1[2]*v2[1]),-(v1[0]*v2[2] - v1[2]*v2[0]),(v1[0]*v2[1] - v1[1]*v2[0])]
def dot(v1:list,v2:list)->float:
""" Returns the dot product of two vectors
Args:
v1 (list): vector1
v2 (list): vector2
Returns:
float: resulting dot product
"""
return sum([v1[i]*v2[i] for i in range(len(v1))])
def det2x2(m)->float:
""" Returns the determinant of a 2x2 matrix
Args:
m (list): matrix
Returns:
float: resulting determinant
"""
return m[0][0]*m[1][1]-m[0][1]*m[1][0]
def getMag(vec:list)->float:
""" Returns the magnitude given a vector
Args:
vec (list): vector
Returns:
float: the magnitude of the vector
"""
return math.sqrt(sum([vec[i]**2 for i in range(len(vec))]))
def rotZX(v:list,alpha:float,beta:float)->list:
""" Rotates a vector around z axis with alpha, then rotates around x axis with beta'''
Args:
v (list): original vector
alpha (float): angle to rotate around z axis (in radians)
beta (float): angle to rotate around x axis (in radians)
Returns:
list: rotated vector
"""
z=np.array([[np.cos(alpha),-np.sin(alpha),0],
[np.sin(alpha),np.cos(alpha),0],
[0,0,1]])
x=np.array([[1,0,0],
[0,np.cos(beta),-np.sin(beta)],
[0,np.sin(beta),np.cos(beta)]])
return np.dot(np.matmul(x,z),v)
def rotX(v:list,omega:float)->list:
""" Rotates a vector around x axis with omega (in radians)'''
Args:
v (list): original vector
omega (float): angle to rotate around x axis (in radians)
Returns:
list: rotated vector
"""
x=np.array([[1,0,0],
[0,np.cos(omega),-np.sin(omega)],
[0,np.sin(omega),np.cos(omega)]])
return np.matmul(x,v)
# assorted other functions
def newton(func,der,init:float,err:float)->float: #function, derivative, inital_guess, error_tolerance
""" Performs the Newton-Rhapson method
Args:
func (function): function
der (function): derivative of function
init (float): initial guess
err (float): tolerance
Returns:
float: the answer
"""
prev=-1e10
while abs(init-prev)>=err:
prev,init=init,init-func(init)/der(init)
return init
def error(acc:float,exp:float)->float:
""" Returns the error given accepted and experimental values
Args:
acc (float): accepted value
exp (float): experimental value
Returns:
float: the error in percent
"""
return math.copysign(abs(acc-exp)/acc*100,1)
# OD code
class ODElements:
'''Class that represents and calculates orbital elements'''
def __init__(self, pos:list, vel:list, time:float):
""" Initializes ODElements class
Args:
pos (list): position of asteroid
vel (list): velocity of asteroid (non Gaussian)
time (float): given time in julian days
Returns:
None
"""
# constants
self.k = 0.0172020989484 # au^(3/2)/day
self.time=time
self.vel=vel
self.vel=np.array([self.vel[i]/(2*math.pi)*365.2568983 for i in range(3)]) # convert to Gaussian
self.pos=pos
self.angMoment=self.getAngMoment()
self.mu=1
self.a=self.getSemiMajor() # semi-major axis
self.e= self.getEcc() # eccentricity
self.i=self.getInc() # inclination
self.o=self.getLongAsc() # longitude of ascending node
self.v=self.getTrueAnom() # true anomaly
self.w=self.getArgPer() # argument of perihelion
self.M=self.getMeanAnomaly() # mean anomaly
self.T=self.getPeriT() # time of perihelion passage T
def getInfo(self)->list:
""" Returns info from given day from initialization
Args:
None
Returns:
list: all info
"""
return self.info
def getPos(self)->list:
""" Returns the position of the asteroid
Args:
None
Returns:
list: the position
"""
return self.pos
def getVel(self)->list:
""" Returns the velocity of the asteroid
Args:
None
Returns:
list: the velocity
"""
return self.vel
def getPosMag(self)->float:
""" Returns the magnitude of the position vector
Args:
None
Returns:
float: magnitude of position
"""
return math.sqrt(sum([self.pos[i]**2 for i in range(3)]))
def getVelMag(self)->float:
""" Returns the magnitude of the velocity vector
Args:
None
Returns:
float: magnitude of velocity
"""
return math.sqrt(sum([self.vel[i]**2 for i in range(3)]))
def getAngMoment(self)->list:
""" Calculates and returns the specific angular momentum
Args:
None
Returns:
list: specific angular momentum components
"""
pos,vel=self.getPos(),self.getVel()
res=cross(pos,vel)
return np.array([res[0], res[1], res[2]])
def getAngMomentMag(self)->float:
""" Returns the magnitude of the specific angular momentum
Args:
None
Returns:
float: the magnitude of specific angular momentum
"""
return math.sqrt(sum([self.angMoment[i]**2 for i in range(3)]))
def getSemiMajor(self)->float:
""" Calculates and returns the semi major axis using vis-viva
Args:
None
Returns:
float: the semi major axis
"""
return 1/(2/self.getPosMag() - dot(self.vel,self.vel)/self.mu)
def getEcc(self)->float:
""" Calculates and returns the eccentricity
Args:
None
Returns:
float: eccentricity
"""
return math.sqrt(1-dot(self.angMoment, self.angMoment)/(self.mu*self.a))
def getInc(self, rad:bool=False)->float:
""" Calculates and returns the inclination in degrees
Args:
rad (bool): True if return in radians
Returns:
float: the inclination in degrees or radians
"""
return math.acos(self.angMoment[2]/self.getAngMomentMag()) if rad else np.rad2deg(math.acos(self.angMoment[2]/self.getAngMomentMag()))
def getLongAsc(self, rad:bool=False):
""" Calculates and returns the longitude of ascending node in degrees
Args:
rad (bool): True if return in radians
Returns:
float: the longitude of ascending node in degrees or radians
"""
s=self.angMoment[0]/(self.getAngMomentMag()*math.sin(np.deg2rad(self.i)))
c=-self.angMoment[1]/(self.getAngMomentMag()*math.sin(np.deg2rad(self.i)))
return getAngle(s,c) if rad else np.rad2deg(getAngle(s,c))
def getArgPer(self, rad:bool=False)->float:
""" Calculates and returns the argument of perihelion in degrees
Args:
rad (bool): True if return in radians
Returns:
float: the longitude of ascending node in degrees or radians
"""
x,y,z=self.pos[0],self.pos[1],self.pos[2]
s=z/(self.getPosMag()*math.sin(np.deg2rad(self.i)))
c=(x*math.cos(np.deg2rad(self.o)) + y*math.sin(np.deg2rad(self.o)))/self.getPosMag()
U=np.rad2deg(getAngle(s,c))
return np.deg2rad((U-self.v)%360) if rad else (U-self.v)%360
def getTrueAnom(self, rad:bool=False)->float:
""" Calculates and returns the true anomaly
Args:
rad (bool): True if return in radians
Returns:
float: the true anomaly in degrees or radians
"""
c=1/self.e*(self.a*(1-self.e**2)/self.getPosMag() - 1)
s=self.a*(1-self.e**2)/(self.getAngMomentMag() * self.e)*dot(self.pos,self.vel)/self.getPosMag()
return getAngle(s,c) if rad else np.rad2deg(getAngle(s,c))
def getPeriT(self)->float:
""" Calculates and returns the time of perihelion
Args:
None
Returns:
float: the time of perihelion
"""
n=self.k/(self.a**(3/2))
return self.time-np.deg2rad(self.M)/n
def getMeanAnomaly(self,rad:bool=False)->float:
""" Calculates and returns the mean anomaly
Args:
rad (bool): True if return in radians
Returns:
float: the mean anomaly in degrees or radians
"""
s=(self.getPosMag()*math.sin(np.deg2rad(self.v)))/(self.a*np.sqrt(1-self.e**2))
c=(self.e+np.cos(np.deg2rad(self.v)))/(1+self.e*np.cos(np.deg2rad(self.v)))
E=getAngle(s,c)
return E-self.e*np.sin(E) if rad else np.rad2deg(E-self.e*np.sin(E))
def getTimeMeanAnomaly(self, time:float, date:str)->float:
""" Calculates mean anomaly for given date
Args:
time (float): time in Julian days for the Mean Anomaly
date (str): date for Mean Anomaly
Returns:
float: mean anomaly in degrees
"""
n=self.k*np.sqrt(self.mu/(self.a**3))
M=np.rad2deg(n*(time-self.T))
return M
def printError(self, results:list):
""" Prints everything
Args:
None
Returns:
None
"""
# EC, QR, IN, OM, W, Tp, N, MA, TA, A, AD, PR,
print("Semi-major axis:", "\n\tactual:",results[9], "\n\tcalculated:",self.a, "\n\terror:",error(results[9],self.a))
print("Eccentricity:", "\n\tactual:",results[0], "\n\tcalculated:",self.e, "\n\terror:",error(results[0],self.e))
print("Inclination:","\n\tactual:",results[2],"\n\tcalculated:",self.i, "\n\terror:",error(results[2],self.i))
print("Longitude of Ascending Node:","\n\tactual:",results[3],"\n\tcalculated:",self.o, "\n\terror:",error(results[3],self.o))
#print("True anomaly:",results[8],self.v,error(results[8],self.v))
print("Argument of perihelion:","\n\tactual:",results[4],"\n\tcalculated:",self.w, "\n\terror:",error(results[4],self.w))
print("Time of Perihelion Passage T:","\n\tactual:",results[5],"\n\tcalculated:", self.T,"\n\terror:",error(results[5],self.T))
#print("Mean Anomaly:",results[7],self.M,error(results[7],self.M))
#print(od.a, od.e, od.i, od.o, od.v, od.w)
def getElements(self):
""" Returns all orbital elements
Args:
rad (bool): True if return in radians
Returns:
floats: a,e,i,o,v,w,T,M
"""
return self.a, self.e, self.i, self.o, self.v, self.w, self.T, self.M
# Data class
class Data:
'''Class that reads and interprets data from input file'''
def __init__(self):
""" Initializes the class
Args:
None
Returns:
None
"""
self.info=np.array([]) # non formatted data from input file
self.inputData=None # formatted data from input file (list)]
self.infoByTime={}
self.infoByDate={} # dictionary of values. date is key. format for key: 2018-Jul-14 00:00:00.0000
self.sunFileName=""
self.inputFileName=""
# constants
self.JDTIME=0
self.DATE=1
self.RA=2
self.DEC=3
self.R=4
def getInput(self, file:str)->list:
""" Formats and returns formatted input. Stores into dictionaries, and converts all RA and Dec
into decimals.
Args:
file (str): the input file name
Returns:
list: the formatted input [jdtime (float), date (str), ra (float), dec (float), [RX,RY,RZ] (np.array)]
"""
self.inputFileName=file
self.info=np.loadtxt(file,dtype=str,delimiter=",")
# store in dictionary for fast retrieval; also, formats all the dec and ra, converting to decimals
# [jdtime, date(str), ra, dec, [RX,RY,RZ] (np.array)]
self.inputData=[]
for line in range(1,len(self.info)):
data=self.info[line]
jdtime = float(data[0])
date = data[1].strip()
strRA,strDec = data[2].split(':'), data[3].split(':')
h,m,s=float(strRA[0]),float(strRA[1]),float(strRA[2])
ra=HMStoDeg(h,m,s)
d,m,s=float(strDec[0]),float(strDec[1]),float(strDec[2])
dec=DMStoDeg(d,m,s)
R=[float(data[4]),float(data[5]),float(data[6])]
self.inputData.append([jdtime, date, ra, dec, R])
self.infoByDate[date] = [jdtime, date, ra, dec, R]
self.infoByTime[jdtime] = [jdtime, date, ra, dec, R]
return self.inputData # nothing is formatted, just all information
def getSunInput(self,date:str=None,time:float=None)->list:
""" Returns the sun input for a given time
Args:
date (str): optional; the date for sun vector
time (float): optional; the time in Julian days for sun vector
Returns:
list: the sun vector
"""
if np.shape(self.info)==0: raise Exception("No input has been loaded up")
if not(date==None):
return self.infoByDate[date][self.R]
elif not(time==None):
return self.infoByTime[time][self.R]
else: # nothing has been inputted, throw an exception
raise Exception("No time has been given to find sun input")
def getRADECInput(self,date:str=None,time:float=None):
""" Returns the right ascension and declination for a given time
Args:
date (str): optional; the date for right ascension and declination
time (float): optional; the time in Julian days for right ascension and declination
Returns:
floats: ra, dec
"""
if np.shape(self.info)==0: raise Exception("No input has been loaded up")
if not(date==None):
d=self.infoByDate[date]
return d[self.RA], d[self.DEC]
elif not(time==None):
d=self.infoByTime[time]
return d[self.RA], d[self.DEC]
else: # nothing has been inputted, throw an exception
raise Exception("No time has been given to find ra")
def getJDTime(self,date:str)->float:
""" Returns Julian Days time given date
Args:
date (str): optional; the date for right ascension and declination
time (float): optional; the time in Julian days for right ascension and declination
Returns:
floats: ra, dec
"""
if np.shape(self.info)==0: raise Exception("No input has been loaded up")
d=self.infoByDate[date]
return d[self.JDTIME]
def formatTestInputInfo(self, file:str):
""" Returns re-formatted data from test input file (for testing purposes, specifically OD elements generation)
Args:
file (str): file name
Returns:
lists: time (in julian days), data [[x,y,z],[dx,dy,dz]], timestamps (strings)
"""
info=np.loadtxt(file,dtype=str,delimiter=",")
time=np.array([float(info[i,0]) for i in range(1,len(info))])
timestamps=np.copy(info[1:,1])
return time,np.array([([float(info[i][2]),float(info[i][3]),float(info[i][4])],
[float(info[i][5]),float(info[i][6]),float(info[i][7])]) for i in range(1,len(info))]), timestamps
def getTestInput(self, file:str, date:str):
""" Returns pos, vel, and times for testing asteroid (for testing purposes, specifically OD elements generation)
Args:
file (str): file name
date (str): date for testing
Returns:
lists: pos, vel, time
"""
times,data,timestamps=self.formatTestInputInfo(file)
line = 0
for i in range(len(timestamps)):
if date in timestamps[i]:
line = i
break
time,info=times[line],data[line]
pos,vel=info[0],info[1]
return pos, vel, time
def getSunPos(self, date:str, file:str)->list:
""" Gets the vector from the Earth to the Sun given the date
Args:
date (str): the date to use
file (str): file name
Returns:
list: sun vector R
"""
info=np.loadtxt(file,dtype=str,delimiter=",")
timestamps=info[:,1]
flag=False
for i in range(len(timestamps)):
if date in timestamps[i]:
line = i
flag=True
break
if not flag: return np.array([0,0,0]) # not found
stuff=info[line,:]
x,y,z=float(stuff[2]),float(stuff[3]),float(stuff[4])
return np.array([x,y,z])
def getAllSunPos(self, file:str)->list:
""" Gets all sun positions and returns as a list
Args:
file (str): file name
Returns:
list: list of lists [time, date, sunPos (np.array)]
"""
info=np.loadtxt(file,dtype=str,delimiter=",")
results=[]
for line in info[1:]:
time,date=float(line[0]),line[1].strip()
R=np.array([float(line[2]),float(line[3]),float(line[4])])
results.append([time,date,R])
return results
def exportEphemeris(self, fileName:str, results:list, actualEph:str):
""" Exports ephemeris to a file
Args:
fileName (str): file name for exported ephemeris
results (list): all results
actualEph (str): file path for actual ephemeris from JPL horizons
Returns:
None
"""
actualRA,actualDec=[],[]
if not(actualEph==""):
data=np.loadtxt(actualEph,dtype=str,delimiter=",")[1:,3:]
for i in range(len(data)):
ra=data[i][0].split()
dec=data[i][1].split()
actualRA.append(HMStoDeg(float(ra[0]),float(ra[1]),float(ra[2])))
actualDec.append(DMStoDeg(float(dec[0]),float(dec[1]),float(dec[2])))
with open(fileName, 'w') as file:
file.write(("Date\tTime\tRA\tDec\tRA error\tDec error\n").expandtabs(35))
counter=0
for date, time, ra, dec in results:
err=""
if not (actualEph==""):
cra=ra.split()
cdec=dec.split()
err="\t"
err+=str(error(actualRA[counter],HMStoDeg(float(cra[0]),float(cra[1]),float(cra[2]))))
err+="\t"
err+=str(error(actualDec[counter],DMStoDeg(float(cdec[0]),float(cdec[1]),float(cdec[2]))))
file.write((date+"\t"+time+"\t"+ra+"\t"+dec+err+"\n").expandtabs(35))
counter+=1
def exportMonteCarlo(self, fileName:str, vals:list, results):
""" Exports Monte Carlo to a file
Args:
fileName (str): file name for exported ephemeris
vals (list): all vals [num,a,e,i,o,w,T]
results (list): real values to compare vals [a,e,i,o,w,t]
Returns:
None
"""
labels=["a","e","i","o","w","T"]
with open(fileName, 'w') as file:
for j in range(len(vals)):
file.write("----"+str(vals[j][0])+"----\n")
for i in range(6):
file.write(labels[i]+": "+str(vals[j][i+1])+" \t error: " + str(error(results[i],vals[j][i+1])) + "\n")
def printResults(self, fileName:str, pos:list, vel:list, rho:list, a:float, e:float, i:float, o:float, T:float, w:float, date:str, M:float):
""" Gets the vector from the Earth to the Sun given the date
Args:
fileName (str): exported file name
pos (list): position from sun
vel (list): the velocity of the asteroid
rho (list): the position from Earth
a (float): semi major axis
e (float): eccentricity
i (float): inclination
o (float): longitude of ascending node (Degrees)
T (float): time of perihelion passage
w (float): argument of perihelion (Degrees)
date (str): date for calculated mean anomaly
M (float): mean anomaly
Returns:
None
"""
with open(fileName, 'w') as file:
file.write("1999 GJ2 Orbit Determination")
file.write("\n----------------------------------------------\n")
file.write("Position From Sun (r, AU):\n\t" + str(pos))
file.write("\nVelocity (AU/day):\n\t"+str(vel))
file.write("\nPosition From Earth (rho, AU):\n\t"+str(rho))
file.write("\n----------------------------------------------\n")
file.write("Orbital Elements:")
file.write("\n\tSemi-Major Axis (AU): " + str(a))
file.write("\n\tEccentricity: " + str(e))
file.write("\n\tInclination (deg): " + str(i))
file.write("\n\tLongitude of Ascending Node (deg): " + str(o))
file.write("\n\tTime of Perihelion Passage (Julian days): " + str(T))
file.write("\n\tArgument of Perihelion (deg): " + str(w))
file.write("\n----------------------------------------------\n")
file.write("Mean Anomaly (deg) for "+date+":")
file.write("\n\t"+str(M))
# Final OD Class
class OD:
'''Class that performs all orbital determination calculations'''
def __init__(self, inputFile:str):
""" Initializes OD class
Args:
inputFile (str): input file name
Returns:
None
"""
# constants
self.k = 0.0172020989484 #Gaussian gravitational constant
self.cAU = 173.144643267 #speed of light in au/(mean solar)day
self.mu = 1
self.eps = np.radians(23.4374) #Earth's obliquity
self.toGaussian=365.2568983
self.mu = 1
self.data=Data() # Data object
self.inputFile=inputFile
def genElements(self, pos:list, vel:list, time:float, update:bool=True):
""" Calculates and returns the orbital elements given position, velocity, time
Args:
pos (list): the position vector
vel (list): the velocity vector
time (float): the time in Julian days
update (bool): if True keeps the newly calculated orbital elements
Returns:
floats: the orbital elements; a,e,i,o,v,w,T,M
"""
if update:
self.pos,self.vel,self.time=pos,vel,time
self.od=ODElements(self.pos,self.vel,self.time)
self.a,self.e,self.i,self.o,self.v,self.w,self.T,self.M = self.od.getElements()
return self.getElements()
else:
od=ODElements(pos,vel,time)
return od.getElements()
def getElements(self):
""" Returns the orbital elements (already calculated)
Args:
rad (bool): True if return in radians
Returns:
floats: a,e,i,o,v,w,T,M
"""
return self.a, self.e, self.i, self.o, self.v, self.w, self.T, self.M
def SEL(self, taus:list, sunR2:list, rhohat2:list, coefD:list):
""" Scalar Equation of Lagrange to calculate the roots (r) and rhos corresponding to each r
Args:
taus (list): a list of floats of taus [T1,T3,T]
sunR2 (list): a list representing the sun vector R2
rhohat2 (list): a list representing the rhohat2 vector
coefD (list): a list of D coefficients [D0,D21,D22,D23]
Returns:
lists: roots (r's), rhos
"""
T1,T3,T=taus[0],taus[1],taus[2]
D0,D21,D22,D23=coefD[0],coefD[1],coefD[2],coefD[3]
A1=T3/T
B1=A1/6*(T**2-T3**2)
A3=-T1/T
B3=A3/6*(T**2-T1**2)
A=(A1*D21-D22+A3*D23)/(-D0)
B=(B1*D21+B3*D23)/(-D0)
E=-2*(dot(rhohat2, sunR2))
F=dot(sunR2,sunR2)
a=-(A**2+A*E+F)
b=-self.mu*(2*A*B+B*E)
c=-self.mu**2*B**2
coef=[c,0,0,b,0,0,a,0,1]
res=poly.polyroots(coef)
temproots=[]
for val in res:
if np.isreal(val) and np.real(val)>0: temproots.append(np.real(val))
temprhos=[A+B/temproots[i]**3 for i in range(len(temproots))]
# ignore pairs where rho magnitude is negative
roots=[]
rhos=[]
#print(temproots, temprhos)
for i in range(len(temproots)):
if temprhos[i]>0.0:
roots.append(temproots[i])
rhos.append(temprhos[i])
return roots,rhos
def ephemeris(self, time:float, date:str, file:str="", sunPos:list=np.array([])):
""" Calculates RA and Dec given time and date, using previously calculated orbital elements
Args:
time (float): time to determine ephemeris for in Julian Days
date (str): date for which to determine ephemeris
file (str): file name for sun positions; optional
Returns:
floats: ra, dec
"""
n=self.k*math.sqrt(self.mu/(self.a**3))
M=n*(time-self.T)
E=newton(lambda E:M - E + self.e*np.sin(E), lambda E: -1+self.e*np.cos(E), M, 1e-17)
pos=np.array([self.a*math.cos(E)-self.a*self.e, self.a*math.sqrt(1-self.e**2)*math.sin(E), 0])
# the four rotations
pos=rotZX(pos,np.deg2rad(self.w),np.deg2rad(self.i))
pos=rotZX(pos,np.deg2rad(self.o),self.eps)
if file=="": # no file given, use sunPos
R=sunPos
else: R=self.data.getSunPos(date, file)
if np.array_equal(R, np.array([0,0,0])): raise Exception("Sun Position Not Found in SunPos.txt")
rho=pos+R
rhohat=rho/getMag(rho)
dec=math.asin(rhohat[2])
cra=rhohat[0]/math.cos(dec)
sra=rhohat[1]/math.cos(dec)
ra=getAngle(sra,cra)
dec=np.rad2deg(dec)
ra=np.rad2deg(ra)
dec=DECdecimalToDMS(dec)
ra=RAdecimalToHMS(ra)
return ra,dec
def fg(self, tau:float, r2mag:float, r2dot:list, order:int, r2:list=[]):
""" Gets the f and g values given one time
Args:
tau (float): the time in Julian Days
r2mag (float): the magnitude of r2
r2dot (list): the velocity vector 2
order (int): order of f and g taylor series approximations
r2 (list): optional parameter, the position vector 2
Returns:
floats: f, g
"""
if len(r2)==0: u=self.mu/r2mag**3
else: u=self.mu/getMag(r2)**3
f=1-1/2*u*tau**2
g=tau
if order>=3:
z=dot(r2,r2dot)/(dot(r2,r2))
q=dot(r2dot,r2dot)/(dot(r2,r2))-u
f+=1/2*u*z*tau**3
g+=-1/6*u*tau**3
if order>=4:
f+=1/24*(3*u*q-15*u*z**2+u**2)*tau**4
g+=1/4*u*z*tau**4
return f, g
def getFGVals(self, tau1:float, tau3:float, r2mag:float, r2dot:list, order1:int, order2:int, r2:list=[]):
""" Gets all f and g values
Args:
tau1 (float): the time in Julian Days for observation 1 from obs 2(T1-T2)
tau3 (float): the time in Julian days for observation 3 from obs 2(T3-T2)
r2mag (float): the magnitude of r2
r2dot (list): the velocity vector 2
order1 (int): Order of Taylor Series expansion for f and g values for observation 1
order2 (int): Order of Taylor Series expansion for f and g values for observation 3
r2 (list): optional parameter, the position vector 2
Returns:
lists: [f1,f3], [g1,g3]
"""
f1,g1=self.fg(tau1,r2mag,r2dot,order1,r2)
f3,g3=self.fg(tau3,r2mag,r2dot,order2,r2)
return [f1,f3], [g1,g3]
def getDCoef(self, ra:list, dec:list, R1:list, R2:list, R3:list):
""" Gets the D coefficients given the ra and dec for three observations (in radians)
Args:
ra (list): the right ascensions for three observations (radians)
dec (list): the declinations for three observations (radians)
R1 (list): the sun vector for observation 1
R2 (list): the sun vector for observation 2
R3 (list): the sun vector for observation 3
Returns:
list: [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33], [rhohat1, rhohat2, rhohat3]
"""
ra1,ra2,ra3=ra[0],ra[1],ra[2]
dec1,dec2,dec3=dec[0],dec[1],dec[2]
rhohat1=np.array([np.cos(ra1)*np.cos(dec1), np.sin(ra1)*np.cos(dec1), np.sin(dec1)])
rhohat2=np.array([np.cos(ra2)*np.cos(dec2), np.sin(ra2)*np.cos(dec2), np.sin(dec2)])
rhohat3=np.array([np.cos(ra3)*np.cos(dec3), np.sin(ra3)*np.cos(dec3), np.sin(dec3)])
D0=dot(rhohat1, cross(rhohat2,rhohat3))
D11=dot(cross(R1, rhohat2),rhohat3)
D12=dot(cross(R2, rhohat2),rhohat3)
D13=dot(cross(R3, rhohat2),rhohat3)
D21=dot(cross(rhohat1,R1), rhohat3)
D22=dot(cross(rhohat1,R2), rhohat3)
D23=dot(cross(rhohat1,R3), rhohat3)
D31=dot(rhohat1, cross(rhohat2,R1))
D32=dot(rhohat1, cross(rhohat2,R2))
D33=dot(rhohat1, cross(rhohat2,R3))
return [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33], np.array([rhohat1, rhohat2, rhohat3])
def MoGGenData(self,loaded:bool,selTime:list=[],selDate:list=[],ra:list=[],dec:list=[]):
""" Generates the data for Method of Gauss calculations
Args:
loaded (bool): True if data is already loaded
selTime (list): list of chosen times (in Julian days) for observations 1,2,3
selDate (list): list of chosen datesfor observations 1,2,3
ra (list): list of right ascensions
dec (list): list of declinations
Returns:
lists: ra, dec, R1, R2, R3, taus, ts (original times in Julian days)
"""
if not loaded:
self.data.getInput(self.inputFile) # formats and generates info
# generate data
if not(selTime==[]): # using Julian day times
if ra==[] and dec==[]:
ra,dec=[],[]
for time in selTime:
r,d=self.data.getRADECInput(time=time)
ra.append(np.deg2rad(r))
dec.append(np.deg2rad(d))
R1=self.data.getSunInput(time=selTime[0])
R2=self.data.getSunInput(time=selTime[1])
R3=self.data.getSunInput(time=selTime[2])
# calculate the taus
t1,t2,t3=selTime[0],selTime[1],selTime[2]
ts=[t1,t2,t3]
T1=t1-t2
T3=t3-t2
T=t3-t1
taus = [T1,T3,T] # in Gaussian days
elif not(selDate==[]):
if ra==[] and dec==[]:
ra,dec=[],[]
for date in selDate:
r,d=self.data.getRADECInput(date=date)
ra.append(np.deg2rad(r))
dec.append(np.deg2rad(d))
R1=self.data.getSunInput(date=selDate[0])
R2=self.data.getSunInput(date=selDate[1])
R3=self.data.getSunInput(date=selDate[2])
# calculate the taus given the dates
t1,t2,t3=self.data.getJDTime(selDate[0]),self.data.getJDTime(selDate[1]),self.data.getJDTime(selDate[2])
ts=[t1,t2,t3]
T1=t1-t2
T3=t3-t2
T=t3-t1
taus = [T1*self.k,T3*self.k,T*self.k]
else: raise Exception("No data given")
self.loadedValues=[ra,dec,np.array(R1),np.array(R2),np.array(R3),taus,np.array(ts)]
else:
if not(ra==[] and dec==[]):
self.loadedValues[0] = ra
self.loadedValues[1] = dec
return self.loadedValues
def MoGCalcRhos(self, rhohats:list, coefD:list, fvals:list, gvals:list):
""" Generates the rhos (and their magnitudes) for Method of Gauss calculations
Args:
rhohats (list): the direction vectors for rhos
coefD (list): the D coefficients
fvals (list): the f values
gvals (list): the g values
Returns:
lists: rhos, rhomags
"""
f1,f3=fvals
g1,g3=gvals
D0,D11,D12,D13,D21,D22,D23,D31,D32,D33=coefD
C1=g3/(f1*g3-g1*f3)
C2=-1
C3=-g1/(f1*g3-g1*f3)
rho1=(C1*D11+C2*D12+C3*D13)/(C1*D0)
rho2=(C1*D21+C2*D22+C3*D23)/(C2*D0)
rho3=(C1*D31+C2*D32+C3*D33)/(C3*D0)
rhomags=np.array([rho1,rho2,rho3])
rhos=np.array([rhohats[0]*rho1, rhohats[1]*rho2, rhohats[2]*rho3])
return rhos, rhomags
def MoGCalcPos(self, rhos:list, Rs:list)->list:
""" Calculates the r vectors (position of asteroid from sun) for Method of Gauss calculations
Args:
rhos (list): the position from Earth vectors
Rs (list): the position from Sun vectors
Returns:
lists: rs
"""
return rhos-Rs
def MoGGetErr(self, prev:list, cur:list, tolerance:float)->bool:
""" Calculates the r vectors (position of asteroid from sun) for Method of Gauss calculations
Args:
prev (list): previous r vector
cur (list): the newly calculated r vector
tolerance (float): the tolerance
Returns:
bool: True if error is good, False if no within tolerance
"""
for i in range(len(prev)):
if abs(prev[i]-cur[i])>tolerance: return False
return True
def MoGGetAdjustedTaus(self, origt:list, rhomags:float)->list:
""" Returns adjusted taus for Method of Gauss calculations
Args:
origt (list): original observation times in Julian days
rhomags (list): the magnitude of the rho vectors
Returns:
list: the adjusted taus
"""
ts=np.copy(origt)-rhomags/self.cAU
t1,t2,t3=ts
return [(t1-t2)*self.k,(t3-t2)*self.k,(t3-t1)*self.k]
def MoG(self,selTime:list=[],selDate:list=[],override:bool=True,ra:list=[],dec:list=[],loaded:bool=False):
""" Performs Method of Gauss calculations to determine orbital elements
Args:
selTime (list): list of chosen times (in Julian days) for observations 1,2,3
selDate (list): list of chosen datesfor observations 1,2,3
override (bool): boolean, True if override info
ra (list): optional list of right ascensions
dec (list): optional list of declinations
loaded (bool): optional, True if all data is already loaded (speeds up run time)
Returns:
None
"""
# generate data
ra,dec,R1,R2,R3,taus,ts=self.MoGGenData(False,selTime,selDate,ra,dec)
# calculate the initial estimate for r2 DONE
coefD,rhohats=self.getDCoef(ra, dec, R1, R2, R3)
SELcoefD=[coefD[0],coefD[4],coefD[5],coefD[6]] # [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33] -> [D0,D21,D22,D23]
r2MagGuesses,rhoMagGuesses=self.SEL(taus, R2, rhohats[1], SELcoefD)
results=[]
# calculate the f and g values to second order
for r2mag in r2MagGuesses:
# initial guesses
fvals,gvals=self.getFGVals(taus[0], taus[1], r2mag, [], 2, 2) # using r2mag
rhos,rhomags=self.MoGCalcRhos(rhohats, coefD, fvals, gvals)
rs=self.MoGCalcPos(rhos, np.array([R1,R2,R3]))
# calculate the central velocity vector
f1,f3=fvals
g1,g3=gvals
r2dot = (-f3/(-f3*g1+g3*f1))*rs[0] + (f1/(f1*g3-f3*g1))*rs[2]
counter=0
timedOut=True
timeout=time.time() + 60*3 # timeout after 3 minutes
while counter<5000 and time.time()<timeout: # timeout
prev=rs[1]
# time adjustment
newtaus=self.MoGGetAdjustedTaus(ts, rhomags)
fvals,gvals=self.getFGVals(newtaus[0], newtaus[1], r2mag, r2dot, 4, 4, rs[1]) # using r2mag
rhos,rhomags=self.MoGCalcRhos(rhohats, coefD, fvals, gvals)
rs=self.MoGCalcPos(rhos, np.array([R1,R2,R3]))
f1,f3=fvals
g1,g3=gvals
r2dot = (-f3/(-f3*g1+g3*f1))*rs[0] + (f1/(f1*g3-f3*g1))*rs[2]
counter+=1
if (self.MoGGetErr(prev, rs[1], 1e-23)):
timedOut=False
break
if not timedOut:
# rotate around x axis to get to ecliptic
r2=rotX(np.array(rs[1]),-self.eps)
r2dot=rotX(r2dot,-self.eps)
rho2=rotX(rhos[1],-self.eps)
results.append([r2, r2dot, rho2]) # position and velocity
resIndex=0
if len(results)>1: # get user input to choose which result to use
for i in range(len(results)):
print("Result",str(i)+":")
print("\tPos from Sun:",r2,"\n\tVel:",r2dot,"\n\tPos from Earth:",rho2,"\n")
while True:
try:
resIndex=int(input("Choose which result to use (int): "))
if not(0<=resIndex<len(results)): resIndex/=0 # throw an error
break
except:
print("Enter a valid integer")
if len(results)==0: return False, 0, 0, 0, 0, 0, 0
# get final answers
pos,vel,rho=results[resIndex]
if override:
self.genElements(pos,np.array(vel)*(2*math.pi)/365.2568983,ts[1])
self.vel = vel
self.pos = pos
self.rho = rho
else:
a,e,i,o,v,w,T,M=self.genElements(pos,np.array(vel)*(2*math.pi)/365.2568983,ts[1], update=False)
return True, a,e,i,o,w,T
def getError(self, results:list):
""" Prints error and results from orbital elements determination after Method of Gauss calculations
Args:
results (list): [e,0,i,o,w,T,0,0,0,a]
Returns:
None
"""
# adjust time of perihelion from results to match period of calculated time of perihelion
period=results[9]**(3/2)*self.toGaussian
results[5]=self.time-((self.time-results[5])%period) # subtract the change in time since the last perihelion from the obs 2 time
self.od.printError(results)
def exportResults(self, time:float, date:str, fileName:str):
""" Exports results to a file
Args:
time (float): time in Julian days for the Mean Anomaly
date (str): date for Mean Anomaly
fileName (str): the exported file name
Returns:
None
"""
M=self.od.getTimeMeanAnomaly(time, date)
self.data.printResults(fileName, self.pos, self.vel/self.toGaussian*(2*math.pi), self.rho, self.a, self.e, self.i, self.o, self.T, self.w, date, M)
def genEphemeris(self, inputFile:str, outputFile:str, actualEph:str):
""" Generates ephemeris for all times in a given sun position file. Exports to an output file
Args:
inputFile (str): input file name
outputFile (str): output file name
actualEph (str): file with real values from JPL horizons
Returns:
None
"""
results=[]
for time,date,R in self.data.getAllSunPos(inputFile):
ra,dec=self.ephemeris(time, date, sunPos=R)
ra=str(ra[0]) + " " + str(ra[1]) + " " + str(ra[2])
if dec[0]<0: sign="-"
else: sign="+"
dec=sign+str(dec[0]) + " " + str(dec[1]) + " " + str(dec[2])
results.append([date,str(time),ra,dec])
self.data.exportEphemeris(outputFile, results, actualEph)
def monteCarlo(self, n:int, files:str, outputFile:str, results:list, selTime:list=[], selDate:list=[]):
""" Using uncertainty, runs through Method of Gauss n-times to generate orbital elements.
Outputs to file.
Args:
n (int): number of times to run monteCarlo
files (list): list of fits file names
outputFile (str): the output file name
results (list): the actual values for error analysis [a, e, i, o, w, T]
selTime (list): optional; the selected times for which to run Method of Gauss
selDate (list): optional; the selected dates for which to run Method of Gauss
Returns:
None
"""
rmsDec,rmsRA=[],[]
for file in files:
data = fits.open(file)[1].data
fieldDec, indexDec = data.field_dec[:], data.index_dec[:]
fieldRA, indexRA = data.field_ra[:], data.index_ra[:]
dec = np.sqrt(1/int(fieldDec.shape[0]) * sum([(indexDec[i]-fieldDec[i])**2 for i in range(int(fieldDec.shape[0]))]))
ra = np.sqrt(1/int(fieldRA.shape[0]) * sum([(indexRA[i]-fieldRA[i])**2 for i in range(int(fieldDec.shape[0]))]))
rmsDec.append(dec)
rmsRA.append(ra)
ora, odec, R1, R2, R3, taus, ts = self.MoGGenData(False,selTime=selTime,selDate=selDate)
vals=[]
num=0
for trial in range(n):
newdec = [np.deg2rad(np.random.normal()*rmsDec[i] + np.rad2deg(odec[i])) for i in range(3)]
newra = [np.deg2rad(np.random.normal()*rmsRA[i] + np.rad2deg(ora[i])) for i in range(3)]
works, a, e, i, o, w, T=self.MoG(selTime=selTime,selDate=selDate,override=False,ra=newra,dec=newdec,loaded=True)
if works:
num+=1
calcVals=[num,a,e,i,o,w,T] # sus
vals.append(calcVals)
vals.append(calcVals)
# adjust time of perihelion for results
# for time of perihelion, need to adjust to be close to the given observation time
# because our calculated time of perihelion is closest to the given observation time
period=results[0]**(3/2)*self.toGaussian
results[5]=ts[1]-((ts[1]-results[5])%period) # subtract the change in time since the last perihelion from the obs 2 time
# write to output file
self.data.exportMonteCarlo(outputFile, vals.copy(), results)
# histograms
labels=["","Semi-Major Axis","Eccentricity", "Inclination", "Longitude of Ascending Node", "Argument of Perihelion", "Time of Perihelion Passage"]
xlabels=["AU","Value","Degrees","Degrees","Degrees","Julian Day Number"]
custom_lines = [Line2D([0], [0], color='black', linestyle='dashed',lw=1), Line2D([0], [0], color='blue', lw=1)]
for j in range(5):
mean=sum([vals[i][1+j] for i in range(len(vals))])/len(vals)
sdom=np.sqrt(sum([(vals[i][j+1]-mean)**2 for i in range(len(vals))])/len(vals))
plt.hist([vals[i][1+j] for i in range(len(vals))],bins=20)
plt.axvline(results[j], color='k', linestyle='dashed', linewidth=1)
plt.axvline(mean, color='b', linewidth=1)
plt.title(labels[1+j])
plt.legend(custom_lines, ['True Value', 'Mean'])
plt.xlabel(xlabels[j])
plt.ylabel("Frequency")
plt.show()
print(labels[j+1]+": \n\tmean: "+str(mean)+"\n\terror: " + str(error(results[j],mean)) + "\n\tstandard deviation of mean: " + str(sdom) + "\n")
# special case for time of perihelion
mean=sum([vals[i][6] for i in range(len(vals))])/len(vals)
sdom=np.sqrt(sum([(vals[i][6]-mean)**2 for i in range(len(vals))])/len(vals))
plt.hist([vals[i][6] for i in range(len(vals))],bins=20)
plt.axvline(results[5], color='k', linestyle='dashed', linewidth=1)
plt.axvline(mean, color='b', linewidth=1)
plt.title(labels[6])
plt.legend(custom_lines, ['True Value', 'Mean'])
plt.xlabel(xlabels[5])
plt.ylabel("Frequency")
plt.show()
print(labels[6]+": \n\tmean: "+str(mean)+ "\n\terror: " + str(error(results[5],mean)) + "\n\tstandard deviation of mean: " + str(sdom) + "\n")
# final functions
def RunCompleteOD(iterations:int, inputFile:str, fitsFiles:list, sunFile:str, dates:list, results:list, actualEph:str):
""" Runs complete orbit determination code. Generates three files:
SoongODResults.txt (the results of orbital determination),
SoongMonteCarloOutput.txt (the results from the Monte Carlo simulation),
SoongGeneratedEphemeris.txt (the results from the ephemeris generation)
Args:
iterations (int): the number of iteration for which to run the Monte Carlo simulation
inputFile (str): path for file containing observations to determine orbital elements
fitsFiles (list): list of strings describing paths for the three fits files for Monte Carlo sim
sunFile (str): path for file containing times and sun positions for ephemeris generation
dates (list): list of dates for orbital elements determination
results (list): the actual values for orbital elements in the format [a,e,i,o,w,m,T]
actualEph (str): the file path for the actual ephemeris from JPL Horizons
Returns:
None
"""
# generate orbital elements
data=Data()
od=OD(inputFile)
od.MoG(selDate=dates)
# determine error
a,e,i,o,w,m,T = results
print("--- Results from orbital elements determination ---")
od.getError([e,0,i,o,w,T,0,0,0,a])
od.exportResults(2459784.7916667, "July 24, 2022 7:00 UT", "SoongODResults.txt")
# Monte Carlo
results = [a,e,i,o,w,T]
print("\n--- Monte Carlo Simulation ---")
od.monteCarlo(iterations, fitsFiles, "SoongMonteCarloOutput.txt", results, selDate=dates)
# generate the ephemeris
od.genEphemeris(sunFile, "SoongGeneratedEphemeris.txt", actualEph)
print("--- Ephemeris generation completed ---")
def GenerateEphemeris(inputFile:str, ODdates:list, sunFile:str, actualEph:str):
""" Outputs ephemeris to file. Generates one file:
GeneratedEphemeris.txt (the results of ephemeris generation)
Args:
inputFile (str): path for file containing observations to determine orbital elements
ODdates (list): list of dates for orbital elements determination
sunFile (str): path for file containing times and sun positions for ephemeris generation
actualEph (str): path for file containing real ephemeris results from JPL Horizons
Returns:
None
"""
# calculating orbital elements
data=Data()
od=OD(inputFile)
od.MoG(selDate=ODdates)
# generate ephemeris
od.genEphemeris(sunFile, "GeneratedEphemeris.txt", actualEph)
print("Finished generating ephemeris")
Functions
def DECdecimalToDMS(dec: float) ‑> tuple
-
Converts from decimal to DMS (declination)
Args
dec
:float
- decimal value
Returns
tuple
- (degrees, minutes, seconds)
Expand source code
def DECdecimalToDMS(dec:float)->tuple: """ Converts from decimal to DMS (declination) Args: dec (float): decimal value Returns: tuple: (degrees, minutes, seconds) """ d=math.trunc(dec) dec=abs(dec)-abs(d) m=math.trunc(dec*60) dec-=m/60 s=dec*3600 return (d,m,s)
def DMStoDeg(d: float, arcm: float, arcs: float, convert=False) ‑> float
-
Given DMS, return in degrees or radians
Args
d
:float
- degrees
m
:float
- minutes
s
:float
- seconds
convert
:bool
- True to convert to radians
Returns
float
- value in degrees or radians
Expand source code
def DMStoDeg(d:float,arcm:float,arcs:float,convert=False)->float: """ Given DMS, return in degrees or radians Args: d (float): degrees m (float): minutes s (float): seconds convert (bool): True to convert to radians Returns: float: value in degrees or radians """ return np.deg2rad(math.copysign(abs(d)+arcm/60+arcs/3600,d)) if convert else math.copysign(abs(d)+arcm/60+arcs/3600,d)
def GenerateEphemeris(inputFile: str, ODdates: list, sunFile: str, actualEph: str)
-
Outputs ephemeris to file. Generates one file: GeneratedEphemeris.txt (the results of ephemeris generation)
Args
inputFile
:str
- path for file containing observations to determine orbital elements
ODdates
:list
- list of dates for orbital elements determination
sunFile
:str
- path for file containing times and sun positions for ephemeris generation
actualEph
:str
- path for file containing real ephemeris results from JPL Horizons
Returns
None
Expand source code
def GenerateEphemeris(inputFile:str, ODdates:list, sunFile:str, actualEph:str): """ Outputs ephemeris to file. Generates one file: GeneratedEphemeris.txt (the results of ephemeris generation) Args: inputFile (str): path for file containing observations to determine orbital elements ODdates (list): list of dates for orbital elements determination sunFile (str): path for file containing times and sun positions for ephemeris generation actualEph (str): path for file containing real ephemeris results from JPL Horizons Returns: None """ # calculating orbital elements data=Data() od=OD(inputFile) od.MoG(selDate=ODdates) # generate ephemeris od.genEphemeris(sunFile, "GeneratedEphemeris.txt", actualEph) print("Finished generating ephemeris")
def HMStoDeg(h: float, m: float, s: float, convert=False) ‑> float
-
Given HMS, return in degrees or radians
Args
h
:float
- hours
m
:float
- minutes
s
:float
- seconds
convert
:bool
- True to convert to radians
Returns
float
- value in degrees or radians
Expand source code
def HMStoDeg(h:float,m:float,s:float,convert=False)->float: """ Given HMS, return in degrees or radians Args: h (float): hours m (float): minutes s (float): seconds convert (bool): True to convert to radians Returns: float: value in degrees or radians """ try: h,m,s=float(h),float(m),float(s) except: raise Exception("Values must be floats") res = h*15 + m/60*15 + s/3600*15 res%=360 return np.deg2rad(res) if convert else res
def RAdecimalToHMS(dec: float) ‑> tuple
-
Converts from decimal to HMS (right ascension)
Args
dec
:float
- decimal value
Returns
tuple
- (hours, minutes, seconds)
Expand source code
def RAdecimalToHMS(dec:float)->tuple: """ Converts from decimal to HMS (right ascension) Args: dec (float): decimal value Returns: tuple: (hours, minutes, seconds) """ dec%=360 h=dec/15 m=(dec%15)/15*60 s=m%1*60 h//=1 m//=1 return (h,m,s)
def RunCompleteOD(iterations: int, inputFile: str, fitsFiles: list, sunFile: str, dates: list, results: list, actualEph: str)
-
Runs complete orbit determination code. Generates three files: SoongODResults.txt (the results of orbital determination), SoongMonteCarloOutput.txt (the results from the Monte Carlo simulation), SoongGeneratedEphemeris.txt (the results from the ephemeris generation)
Args
iterations
:int
- the number of iteration for which to run the Monte Carlo simulation
inputFile
:str
- path for file containing observations to determine orbital elements
fitsFiles
:list
- list of strings describing paths for the three fits files for Monte Carlo sim
sunFile
:str
- path for file containing times and sun positions for ephemeris generation
dates
:list
- list of dates for orbital elements determination
results
:list
- the actual values for orbital elements in the format [a,e,i,o,w,m,T]
actualEph
:str
- the file path for the actual ephemeris from JPL Horizons
Returns
None
Expand source code
def RunCompleteOD(iterations:int, inputFile:str, fitsFiles:list, sunFile:str, dates:list, results:list, actualEph:str): """ Runs complete orbit determination code. Generates three files: SoongODResults.txt (the results of orbital determination), SoongMonteCarloOutput.txt (the results from the Monte Carlo simulation), SoongGeneratedEphemeris.txt (the results from the ephemeris generation) Args: iterations (int): the number of iteration for which to run the Monte Carlo simulation inputFile (str): path for file containing observations to determine orbital elements fitsFiles (list): list of strings describing paths for the three fits files for Monte Carlo sim sunFile (str): path for file containing times and sun positions for ephemeris generation dates (list): list of dates for orbital elements determination results (list): the actual values for orbital elements in the format [a,e,i,o,w,m,T] actualEph (str): the file path for the actual ephemeris from JPL Horizons Returns: None """ # generate orbital elements data=Data() od=OD(inputFile) od.MoG(selDate=dates) # determine error a,e,i,o,w,m,T = results print("--- Results from orbital elements determination ---") od.getError([e,0,i,o,w,T,0,0,0,a]) od.exportResults(2459784.7916667, "July 24, 2022 7:00 UT", "SoongODResults.txt") # Monte Carlo results = [a,e,i,o,w,T] print("\n--- Monte Carlo Simulation ---") od.monteCarlo(iterations, fitsFiles, "SoongMonteCarloOutput.txt", results, selDate=dates) # generate the ephemeris od.genEphemeris(sunFile, "SoongGeneratedEphemeris.txt", actualEph) print("--- Ephemeris generation completed ---")
def cross(v1: list, v2: list) ‑> list
-
Returns the cross product of two 3D vectors
Args
v1
:list
- vector1
v2
:list
- vector2
Returns
list
- resulting cross product
Expand source code
def cross(v1:list, v2:list)->list: """ Returns the cross product of two 3D vectors Args: v1 (list): vector1 v2 (list): vector2 Returns: list: resulting cross product """ return [(v1[1]*v2[2] - v1[2]*v2[1]),-(v1[0]*v2[2] - v1[2]*v2[0]),(v1[0]*v2[1] - v1[1]*v2[0])]
def decToRad(dec: float) ‑> float
-
Given decimal value return in radians
Args
dec
:float
- decimal value
Returns
float
- value in radians
Expand source code
def decToRad(dec:float)->float: """ Given decimal value return in radians Args: dec (float): decimal value Returns: float: value in radians """ return float(dec)/180*math.pi
def det2x2(m) ‑> float
-
Returns the determinant of a 2x2 matrix
Args
m
:list
- matrix
Returns
float
- resulting determinant
Expand source code
def det2x2(m)->float: """ Returns the determinant of a 2x2 matrix Args: m (list): matrix Returns: float: resulting determinant """ return m[0][0]*m[1][1]-m[0][1]*m[1][0]
def dot(v1: list, v2: list) ‑> float
-
Returns the dot product of two vectors
Args
v1
:list
- vector1
v2
:list
- vector2
Returns
float
- resulting dot product
Expand source code
def dot(v1:list,v2:list)->float: """ Returns the dot product of two vectors Args: v1 (list): vector1 v2 (list): vector2 Returns: float: resulting dot product """ return sum([v1[i]*v2[i] for i in range(len(v1))])
def error(acc: float, exp: float) ‑> float
-
Returns the error given accepted and experimental values
Args
acc
:float
- accepted value
exp
:float
- experimental value
Returns
float
- the error in percent
Expand source code
def error(acc:float,exp:float)->float: """ Returns the error given accepted and experimental values Args: acc (float): accepted value exp (float): experimental value Returns: float: the error in percent """ return math.copysign(abs(acc-exp)/acc*100,1)
def getAngle(sin: float, cos: float) ‑> float
-
Returns the angle (in radians) in the correct quadrant given sin and cos
Args
sin
:float
- sin value
cos
:float
- cos value
Returns
float
- resulting angle in radians
Expand source code
def getAngle(sin:float,cos:float)->float: """ Returns the angle (in radians) in the correct quadrant given sin and cos Args: sin (float): sin value cos (float): cos value Returns: float: resulting angle in radians """ return math.atan2(sin,cos) % (math.pi*2)
def getMag(vec: list) ‑> float
-
Returns the magnitude given a vector
Args
vec
:list
- vector
Returns
float
- the magnitude of the vector
Expand source code
def getMag(vec:list)->float: """ Returns the magnitude given a vector Args: vec (list): vector Returns: float: the magnitude of the vector """ return math.sqrt(sum([vec[i]**2 for i in range(len(vec))]))
def newton(func, der, init: float, err: float) ‑> float
-
Performs the Newton-Rhapson method
Args
func
:function
- function
der
:function
- derivative of function
init
:float
- initial guess
err
:float
- tolerance
Returns
float
- the answer
Expand source code
def newton(func,der,init:float,err:float)->float: #function, derivative, inital_guess, error_tolerance """ Performs the Newton-Rhapson method Args: func (function): function der (function): derivative of function init (float): initial guess err (float): tolerance Returns: float: the answer """ prev=-1e10 while abs(init-prev)>=err: prev,init=init,init-func(init)/der(init) return init
def rotX(v: list, omega: float) ‑> list
-
Rotates a vector around x axis with omega (in radians)'''
Args
v
:list
- original vector
omega
:float
- angle to rotate around x axis (in radians)
Returns
list
- rotated vector
Expand source code
def rotX(v:list,omega:float)->list: """ Rotates a vector around x axis with omega (in radians)''' Args: v (list): original vector omega (float): angle to rotate around x axis (in radians) Returns: list: rotated vector """ x=np.array([[1,0,0], [0,np.cos(omega),-np.sin(omega)], [0,np.sin(omega),np.cos(omega)]]) return np.matmul(x,v)
def rotZX(v: list, alpha: float, beta: float) ‑> list
-
Rotates a vector around z axis with alpha, then rotates around x axis with beta'''
Args
v
:list
- original vector
alpha
:float
- angle to rotate around z axis (in radians)
beta
:float
- angle to rotate around x axis (in radians)
Returns
list
- rotated vector
Expand source code
def rotZX(v:list,alpha:float,beta:float)->list: """ Rotates a vector around z axis with alpha, then rotates around x axis with beta''' Args: v (list): original vector alpha (float): angle to rotate around z axis (in radians) beta (float): angle to rotate around x axis (in radians) Returns: list: rotated vector """ z=np.array([[np.cos(alpha),-np.sin(alpha),0], [np.sin(alpha),np.cos(alpha),0], [0,0,1]]) x=np.array([[1,0,0], [0,np.cos(beta),-np.sin(beta)], [0,np.sin(beta),np.cos(beta)]]) return np.dot(np.matmul(x,z),v)
Classes
class Data
-
Class that reads and interprets data from input file
Initializes the class
Args
None
Returns
None
Expand source code
class Data: '''Class that reads and interprets data from input file''' def __init__(self): """ Initializes the class Args: None Returns: None """ self.info=np.array([]) # non formatted data from input file self.inputData=None # formatted data from input file (list)] self.infoByTime={} self.infoByDate={} # dictionary of values. date is key. format for key: 2018-Jul-14 00:00:00.0000 self.sunFileName="" self.inputFileName="" # constants self.JDTIME=0 self.DATE=1 self.RA=2 self.DEC=3 self.R=4 def getInput(self, file:str)->list: """ Formats and returns formatted input. Stores into dictionaries, and converts all RA and Dec into decimals. Args: file (str): the input file name Returns: list: the formatted input [jdtime (float), date (str), ra (float), dec (float), [RX,RY,RZ] (np.array)] """ self.inputFileName=file self.info=np.loadtxt(file,dtype=str,delimiter=",") # store in dictionary for fast retrieval; also, formats all the dec and ra, converting to decimals # [jdtime, date(str), ra, dec, [RX,RY,RZ] (np.array)] self.inputData=[] for line in range(1,len(self.info)): data=self.info[line] jdtime = float(data[0]) date = data[1].strip() strRA,strDec = data[2].split(':'), data[3].split(':') h,m,s=float(strRA[0]),float(strRA[1]),float(strRA[2]) ra=HMStoDeg(h,m,s) d,m,s=float(strDec[0]),float(strDec[1]),float(strDec[2]) dec=DMStoDeg(d,m,s) R=[float(data[4]),float(data[5]),float(data[6])] self.inputData.append([jdtime, date, ra, dec, R]) self.infoByDate[date] = [jdtime, date, ra, dec, R] self.infoByTime[jdtime] = [jdtime, date, ra, dec, R] return self.inputData # nothing is formatted, just all information def getSunInput(self,date:str=None,time:float=None)->list: """ Returns the sun input for a given time Args: date (str): optional; the date for sun vector time (float): optional; the time in Julian days for sun vector Returns: list: the sun vector """ if np.shape(self.info)==0: raise Exception("No input has been loaded up") if not(date==None): return self.infoByDate[date][self.R] elif not(time==None): return self.infoByTime[time][self.R] else: # nothing has been inputted, throw an exception raise Exception("No time has been given to find sun input") def getRADECInput(self,date:str=None,time:float=None): """ Returns the right ascension and declination for a given time Args: date (str): optional; the date for right ascension and declination time (float): optional; the time in Julian days for right ascension and declination Returns: floats: ra, dec """ if np.shape(self.info)==0: raise Exception("No input has been loaded up") if not(date==None): d=self.infoByDate[date] return d[self.RA], d[self.DEC] elif not(time==None): d=self.infoByTime[time] return d[self.RA], d[self.DEC] else: # nothing has been inputted, throw an exception raise Exception("No time has been given to find ra") def getJDTime(self,date:str)->float: """ Returns Julian Days time given date Args: date (str): optional; the date for right ascension and declination time (float): optional; the time in Julian days for right ascension and declination Returns: floats: ra, dec """ if np.shape(self.info)==0: raise Exception("No input has been loaded up") d=self.infoByDate[date] return d[self.JDTIME] def formatTestInputInfo(self, file:str): """ Returns re-formatted data from test input file (for testing purposes, specifically OD elements generation) Args: file (str): file name Returns: lists: time (in julian days), data [[x,y,z],[dx,dy,dz]], timestamps (strings) """ info=np.loadtxt(file,dtype=str,delimiter=",") time=np.array([float(info[i,0]) for i in range(1,len(info))]) timestamps=np.copy(info[1:,1]) return time,np.array([([float(info[i][2]),float(info[i][3]),float(info[i][4])], [float(info[i][5]),float(info[i][6]),float(info[i][7])]) for i in range(1,len(info))]), timestamps def getTestInput(self, file:str, date:str): """ Returns pos, vel, and times for testing asteroid (for testing purposes, specifically OD elements generation) Args: file (str): file name date (str): date for testing Returns: lists: pos, vel, time """ times,data,timestamps=self.formatTestInputInfo(file) line = 0 for i in range(len(timestamps)): if date in timestamps[i]: line = i break time,info=times[line],data[line] pos,vel=info[0],info[1] return pos, vel, time def getSunPos(self, date:str, file:str)->list: """ Gets the vector from the Earth to the Sun given the date Args: date (str): the date to use file (str): file name Returns: list: sun vector R """ info=np.loadtxt(file,dtype=str,delimiter=",") timestamps=info[:,1] flag=False for i in range(len(timestamps)): if date in timestamps[i]: line = i flag=True break if not flag: return np.array([0,0,0]) # not found stuff=info[line,:] x,y,z=float(stuff[2]),float(stuff[3]),float(stuff[4]) return np.array([x,y,z]) def getAllSunPos(self, file:str)->list: """ Gets all sun positions and returns as a list Args: file (str): file name Returns: list: list of lists [time, date, sunPos (np.array)] """ info=np.loadtxt(file,dtype=str,delimiter=",") results=[] for line in info[1:]: time,date=float(line[0]),line[1].strip() R=np.array([float(line[2]),float(line[3]),float(line[4])]) results.append([time,date,R]) return results def exportEphemeris(self, fileName:str, results:list, actualEph:str): """ Exports ephemeris to a file Args: fileName (str): file name for exported ephemeris results (list): all results actualEph (str): file path for actual ephemeris from JPL horizons Returns: None """ actualRA,actualDec=[],[] if not(actualEph==""): data=np.loadtxt(actualEph,dtype=str,delimiter=",")[1:,3:] for i in range(len(data)): ra=data[i][0].split() dec=data[i][1].split() actualRA.append(HMStoDeg(float(ra[0]),float(ra[1]),float(ra[2]))) actualDec.append(DMStoDeg(float(dec[0]),float(dec[1]),float(dec[2]))) with open(fileName, 'w') as file: file.write(("Date\tTime\tRA\tDec\tRA error\tDec error\n").expandtabs(35)) counter=0 for date, time, ra, dec in results: err="" if not (actualEph==""): cra=ra.split() cdec=dec.split() err="\t" err+=str(error(actualRA[counter],HMStoDeg(float(cra[0]),float(cra[1]),float(cra[2])))) err+="\t" err+=str(error(actualDec[counter],DMStoDeg(float(cdec[0]),float(cdec[1]),float(cdec[2])))) file.write((date+"\t"+time+"\t"+ra+"\t"+dec+err+"\n").expandtabs(35)) counter+=1 def exportMonteCarlo(self, fileName:str, vals:list, results): """ Exports Monte Carlo to a file Args: fileName (str): file name for exported ephemeris vals (list): all vals [num,a,e,i,o,w,T] results (list): real values to compare vals [a,e,i,o,w,t] Returns: None """ labels=["a","e","i","o","w","T"] with open(fileName, 'w') as file: for j in range(len(vals)): file.write("----"+str(vals[j][0])+"----\n") for i in range(6): file.write(labels[i]+": "+str(vals[j][i+1])+" \t error: " + str(error(results[i],vals[j][i+1])) + "\n") def printResults(self, fileName:str, pos:list, vel:list, rho:list, a:float, e:float, i:float, o:float, T:float, w:float, date:str, M:float): """ Gets the vector from the Earth to the Sun given the date Args: fileName (str): exported file name pos (list): position from sun vel (list): the velocity of the asteroid rho (list): the position from Earth a (float): semi major axis e (float): eccentricity i (float): inclination o (float): longitude of ascending node (Degrees) T (float): time of perihelion passage w (float): argument of perihelion (Degrees) date (str): date for calculated mean anomaly M (float): mean anomaly Returns: None """ with open(fileName, 'w') as file: file.write("1999 GJ2 Orbit Determination") file.write("\n----------------------------------------------\n") file.write("Position From Sun (r, AU):\n\t" + str(pos)) file.write("\nVelocity (AU/day):\n\t"+str(vel)) file.write("\nPosition From Earth (rho, AU):\n\t"+str(rho)) file.write("\n----------------------------------------------\n") file.write("Orbital Elements:") file.write("\n\tSemi-Major Axis (AU): " + str(a)) file.write("\n\tEccentricity: " + str(e)) file.write("\n\tInclination (deg): " + str(i)) file.write("\n\tLongitude of Ascending Node (deg): " + str(o)) file.write("\n\tTime of Perihelion Passage (Julian days): " + str(T)) file.write("\n\tArgument of Perihelion (deg): " + str(w)) file.write("\n----------------------------------------------\n") file.write("Mean Anomaly (deg) for "+date+":") file.write("\n\t"+str(M))
Methods
def exportEphemeris(self, fileName: str, results: list, actualEph: str)
-
Exports ephemeris to a file
Args
fileName
:str
- file name for exported ephemeris
results
:list
- all results
actualEph
:str
- file path for actual ephemeris from JPL horizons
Returns
None
Expand source code
def exportEphemeris(self, fileName:str, results:list, actualEph:str): """ Exports ephemeris to a file Args: fileName (str): file name for exported ephemeris results (list): all results actualEph (str): file path for actual ephemeris from JPL horizons Returns: None """ actualRA,actualDec=[],[] if not(actualEph==""): data=np.loadtxt(actualEph,dtype=str,delimiter=",")[1:,3:] for i in range(len(data)): ra=data[i][0].split() dec=data[i][1].split() actualRA.append(HMStoDeg(float(ra[0]),float(ra[1]),float(ra[2]))) actualDec.append(DMStoDeg(float(dec[0]),float(dec[1]),float(dec[2]))) with open(fileName, 'w') as file: file.write(("Date\tTime\tRA\tDec\tRA error\tDec error\n").expandtabs(35)) counter=0 for date, time, ra, dec in results: err="" if not (actualEph==""): cra=ra.split() cdec=dec.split() err="\t" err+=str(error(actualRA[counter],HMStoDeg(float(cra[0]),float(cra[1]),float(cra[2])))) err+="\t" err+=str(error(actualDec[counter],DMStoDeg(float(cdec[0]),float(cdec[1]),float(cdec[2])))) file.write((date+"\t"+time+"\t"+ra+"\t"+dec+err+"\n").expandtabs(35)) counter+=1
def exportMonteCarlo(self, fileName: str, vals: list, results)
-
Exports Monte Carlo to a file
Args
fileName
:str
- file name for exported ephemeris
vals
:list
- all vals [num,a,e,i,o,w,T]
results
:list
- real values to compare vals [a,e,i,o,w,t]
Returns
None
Expand source code
def exportMonteCarlo(self, fileName:str, vals:list, results): """ Exports Monte Carlo to a file Args: fileName (str): file name for exported ephemeris vals (list): all vals [num,a,e,i,o,w,T] results (list): real values to compare vals [a,e,i,o,w,t] Returns: None """ labels=["a","e","i","o","w","T"] with open(fileName, 'w') as file: for j in range(len(vals)): file.write("----"+str(vals[j][0])+"----\n") for i in range(6): file.write(labels[i]+": "+str(vals[j][i+1])+" \t error: " + str(error(results[i],vals[j][i+1])) + "\n")
def formatTestInputInfo(self, file: str)
-
Returns re-formatted data from test input file (for testing purposes, specifically OD elements generation)
Args
file
:str
- file name
Returns
lists
- time (in julian days), data [[x,y,z],[dx,dy,dz]], timestamps (strings)
Expand source code
def formatTestInputInfo(self, file:str): """ Returns re-formatted data from test input file (for testing purposes, specifically OD elements generation) Args: file (str): file name Returns: lists: time (in julian days), data [[x,y,z],[dx,dy,dz]], timestamps (strings) """ info=np.loadtxt(file,dtype=str,delimiter=",") time=np.array([float(info[i,0]) for i in range(1,len(info))]) timestamps=np.copy(info[1:,1]) return time,np.array([([float(info[i][2]),float(info[i][3]),float(info[i][4])], [float(info[i][5]),float(info[i][6]),float(info[i][7])]) for i in range(1,len(info))]), timestamps
def getAllSunPos(self, file: str) ‑> list
-
Gets all sun positions and returns as a list
Args
file
:str
- file name
Returns
list
- list of lists [time, date, sunPos (np.array)]
Expand source code
def getAllSunPos(self, file:str)->list: """ Gets all sun positions and returns as a list Args: file (str): file name Returns: list: list of lists [time, date, sunPos (np.array)] """ info=np.loadtxt(file,dtype=str,delimiter=",") results=[] for line in info[1:]: time,date=float(line[0]),line[1].strip() R=np.array([float(line[2]),float(line[3]),float(line[4])]) results.append([time,date,R]) return results
def getInput(self, file: str) ‑> list
-
Formats and returns formatted input. Stores into dictionaries, and converts all RA and Dec into decimals.
Args
file
:str
- the input file name
Returns
list
- the formatted input [jdtime (float), date (str), ra (float), dec (float), [RX,RY,RZ] (np.array)]
Expand source code
def getInput(self, file:str)->list: """ Formats and returns formatted input. Stores into dictionaries, and converts all RA and Dec into decimals. Args: file (str): the input file name Returns: list: the formatted input [jdtime (float), date (str), ra (float), dec (float), [RX,RY,RZ] (np.array)] """ self.inputFileName=file self.info=np.loadtxt(file,dtype=str,delimiter=",") # store in dictionary for fast retrieval; also, formats all the dec and ra, converting to decimals # [jdtime, date(str), ra, dec, [RX,RY,RZ] (np.array)] self.inputData=[] for line in range(1,len(self.info)): data=self.info[line] jdtime = float(data[0]) date = data[1].strip() strRA,strDec = data[2].split(':'), data[3].split(':') h,m,s=float(strRA[0]),float(strRA[1]),float(strRA[2]) ra=HMStoDeg(h,m,s) d,m,s=float(strDec[0]),float(strDec[1]),float(strDec[2]) dec=DMStoDeg(d,m,s) R=[float(data[4]),float(data[5]),float(data[6])] self.inputData.append([jdtime, date, ra, dec, R]) self.infoByDate[date] = [jdtime, date, ra, dec, R] self.infoByTime[jdtime] = [jdtime, date, ra, dec, R] return self.inputData # nothing is formatted, just all information
def getJDTime(self, date: str) ‑> float
-
Returns Julian Days time given date
Args
date
:str
- optional; the date for right ascension and declination
time
:float
- optional; the time in Julian days for right ascension and declination
Returns
floats
- ra, dec
Expand source code
def getJDTime(self,date:str)->float: """ Returns Julian Days time given date Args: date (str): optional; the date for right ascension and declination time (float): optional; the time in Julian days for right ascension and declination Returns: floats: ra, dec """ if np.shape(self.info)==0: raise Exception("No input has been loaded up") d=self.infoByDate[date] return d[self.JDTIME]
def getRADECInput(self, date: str = None, time: float = None)
-
Returns the right ascension and declination for a given time
Args
date
:str
- optional; the date for right ascension and declination
time
:float
- optional; the time in Julian days for right ascension and declination
Returns
floats
- ra, dec
Expand source code
def getRADECInput(self,date:str=None,time:float=None): """ Returns the right ascension and declination for a given time Args: date (str): optional; the date for right ascension and declination time (float): optional; the time in Julian days for right ascension and declination Returns: floats: ra, dec """ if np.shape(self.info)==0: raise Exception("No input has been loaded up") if not(date==None): d=self.infoByDate[date] return d[self.RA], d[self.DEC] elif not(time==None): d=self.infoByTime[time] return d[self.RA], d[self.DEC] else: # nothing has been inputted, throw an exception raise Exception("No time has been given to find ra")
def getSunInput(self, date: str = None, time: float = None) ‑> list
-
Returns the sun input for a given time
Args
date
:str
- optional; the date for sun vector
time
:float
- optional; the time in Julian days for sun vector
Returns
list
- the sun vector
Expand source code
def getSunInput(self,date:str=None,time:float=None)->list: """ Returns the sun input for a given time Args: date (str): optional; the date for sun vector time (float): optional; the time in Julian days for sun vector Returns: list: the sun vector """ if np.shape(self.info)==0: raise Exception("No input has been loaded up") if not(date==None): return self.infoByDate[date][self.R] elif not(time==None): return self.infoByTime[time][self.R] else: # nothing has been inputted, throw an exception raise Exception("No time has been given to find sun input")
def getSunPos(self, date: str, file: str) ‑> list
-
Gets the vector from the Earth to the Sun given the date
Args
date
:str
- the date to use
file
:str
- file name
Returns
list
- sun vector R
Expand source code
def getSunPos(self, date:str, file:str)->list: """ Gets the vector from the Earth to the Sun given the date Args: date (str): the date to use file (str): file name Returns: list: sun vector R """ info=np.loadtxt(file,dtype=str,delimiter=",") timestamps=info[:,1] flag=False for i in range(len(timestamps)): if date in timestamps[i]: line = i flag=True break if not flag: return np.array([0,0,0]) # not found stuff=info[line,:] x,y,z=float(stuff[2]),float(stuff[3]),float(stuff[4]) return np.array([x,y,z])
def getTestInput(self, file: str, date: str)
-
Returns pos, vel, and times for testing asteroid (for testing purposes, specifically OD elements generation)
Args
file
:str
- file name
date
:str
- date for testing
Returns
lists
- pos, vel, time
Expand source code
def getTestInput(self, file:str, date:str): """ Returns pos, vel, and times for testing asteroid (for testing purposes, specifically OD elements generation) Args: file (str): file name date (str): date for testing Returns: lists: pos, vel, time """ times,data,timestamps=self.formatTestInputInfo(file) line = 0 for i in range(len(timestamps)): if date in timestamps[i]: line = i break time,info=times[line],data[line] pos,vel=info[0],info[1] return pos, vel, time
def printResults(self, fileName: str, pos: list, vel: list, rho: list, a: float, e: float, i: float, o: float, T: float, w: float, date: str, M: float)
-
Gets the vector from the Earth to the Sun given the date
Args
fileName
:str
- exported file name
pos
:list
- position from sun
vel
:list
- the velocity of the asteroid
rho
:list
- the position from Earth
a
:float
- semi major axis
e
:float
- eccentricity
i
:float
- inclination
o
:float
- longitude of ascending node (Degrees)
T
:float
- time of perihelion passage
w
:float
- argument of perihelion (Degrees)
date
:str
- date for calculated mean anomaly
M
:float
- mean anomaly
Returns
None
Expand source code
def printResults(self, fileName:str, pos:list, vel:list, rho:list, a:float, e:float, i:float, o:float, T:float, w:float, date:str, M:float): """ Gets the vector from the Earth to the Sun given the date Args: fileName (str): exported file name pos (list): position from sun vel (list): the velocity of the asteroid rho (list): the position from Earth a (float): semi major axis e (float): eccentricity i (float): inclination o (float): longitude of ascending node (Degrees) T (float): time of perihelion passage w (float): argument of perihelion (Degrees) date (str): date for calculated mean anomaly M (float): mean anomaly Returns: None """ with open(fileName, 'w') as file: file.write("1999 GJ2 Orbit Determination") file.write("\n----------------------------------------------\n") file.write("Position From Sun (r, AU):\n\t" + str(pos)) file.write("\nVelocity (AU/day):\n\t"+str(vel)) file.write("\nPosition From Earth (rho, AU):\n\t"+str(rho)) file.write("\n----------------------------------------------\n") file.write("Orbital Elements:") file.write("\n\tSemi-Major Axis (AU): " + str(a)) file.write("\n\tEccentricity: " + str(e)) file.write("\n\tInclination (deg): " + str(i)) file.write("\n\tLongitude of Ascending Node (deg): " + str(o)) file.write("\n\tTime of Perihelion Passage (Julian days): " + str(T)) file.write("\n\tArgument of Perihelion (deg): " + str(w)) file.write("\n----------------------------------------------\n") file.write("Mean Anomaly (deg) for "+date+":") file.write("\n\t"+str(M))
class OD (inputFile: str)
-
Class that performs all orbital determination calculations
Initializes OD class
Args
inputFile
:str
- input file name
Returns
None
Expand source code
class OD: '''Class that performs all orbital determination calculations''' def __init__(self, inputFile:str): """ Initializes OD class Args: inputFile (str): input file name Returns: None """ # constants self.k = 0.0172020989484 #Gaussian gravitational constant self.cAU = 173.144643267 #speed of light in au/(mean solar)day self.mu = 1 self.eps = np.radians(23.4374) #Earth's obliquity self.toGaussian=365.2568983 self.mu = 1 self.data=Data() # Data object self.inputFile=inputFile def genElements(self, pos:list, vel:list, time:float, update:bool=True): """ Calculates and returns the orbital elements given position, velocity, time Args: pos (list): the position vector vel (list): the velocity vector time (float): the time in Julian days update (bool): if True keeps the newly calculated orbital elements Returns: floats: the orbital elements; a,e,i,o,v,w,T,M """ if update: self.pos,self.vel,self.time=pos,vel,time self.od=ODElements(self.pos,self.vel,self.time) self.a,self.e,self.i,self.o,self.v,self.w,self.T,self.M = self.od.getElements() return self.getElements() else: od=ODElements(pos,vel,time) return od.getElements() def getElements(self): """ Returns the orbital elements (already calculated) Args: rad (bool): True if return in radians Returns: floats: a,e,i,o,v,w,T,M """ return self.a, self.e, self.i, self.o, self.v, self.w, self.T, self.M def SEL(self, taus:list, sunR2:list, rhohat2:list, coefD:list): """ Scalar Equation of Lagrange to calculate the roots (r) and rhos corresponding to each r Args: taus (list): a list of floats of taus [T1,T3,T] sunR2 (list): a list representing the sun vector R2 rhohat2 (list): a list representing the rhohat2 vector coefD (list): a list of D coefficients [D0,D21,D22,D23] Returns: lists: roots (r's), rhos """ T1,T3,T=taus[0],taus[1],taus[2] D0,D21,D22,D23=coefD[0],coefD[1],coefD[2],coefD[3] A1=T3/T B1=A1/6*(T**2-T3**2) A3=-T1/T B3=A3/6*(T**2-T1**2) A=(A1*D21-D22+A3*D23)/(-D0) B=(B1*D21+B3*D23)/(-D0) E=-2*(dot(rhohat2, sunR2)) F=dot(sunR2,sunR2) a=-(A**2+A*E+F) b=-self.mu*(2*A*B+B*E) c=-self.mu**2*B**2 coef=[c,0,0,b,0,0,a,0,1] res=poly.polyroots(coef) temproots=[] for val in res: if np.isreal(val) and np.real(val)>0: temproots.append(np.real(val)) temprhos=[A+B/temproots[i]**3 for i in range(len(temproots))] # ignore pairs where rho magnitude is negative roots=[] rhos=[] #print(temproots, temprhos) for i in range(len(temproots)): if temprhos[i]>0.0: roots.append(temproots[i]) rhos.append(temprhos[i]) return roots,rhos def ephemeris(self, time:float, date:str, file:str="", sunPos:list=np.array([])): """ Calculates RA and Dec given time and date, using previously calculated orbital elements Args: time (float): time to determine ephemeris for in Julian Days date (str): date for which to determine ephemeris file (str): file name for sun positions; optional Returns: floats: ra, dec """ n=self.k*math.sqrt(self.mu/(self.a**3)) M=n*(time-self.T) E=newton(lambda E:M - E + self.e*np.sin(E), lambda E: -1+self.e*np.cos(E), M, 1e-17) pos=np.array([self.a*math.cos(E)-self.a*self.e, self.a*math.sqrt(1-self.e**2)*math.sin(E), 0]) # the four rotations pos=rotZX(pos,np.deg2rad(self.w),np.deg2rad(self.i)) pos=rotZX(pos,np.deg2rad(self.o),self.eps) if file=="": # no file given, use sunPos R=sunPos else: R=self.data.getSunPos(date, file) if np.array_equal(R, np.array([0,0,0])): raise Exception("Sun Position Not Found in SunPos.txt") rho=pos+R rhohat=rho/getMag(rho) dec=math.asin(rhohat[2]) cra=rhohat[0]/math.cos(dec) sra=rhohat[1]/math.cos(dec) ra=getAngle(sra,cra) dec=np.rad2deg(dec) ra=np.rad2deg(ra) dec=DECdecimalToDMS(dec) ra=RAdecimalToHMS(ra) return ra,dec def fg(self, tau:float, r2mag:float, r2dot:list, order:int, r2:list=[]): """ Gets the f and g values given one time Args: tau (float): the time in Julian Days r2mag (float): the magnitude of r2 r2dot (list): the velocity vector 2 order (int): order of f and g taylor series approximations r2 (list): optional parameter, the position vector 2 Returns: floats: f, g """ if len(r2)==0: u=self.mu/r2mag**3 else: u=self.mu/getMag(r2)**3 f=1-1/2*u*tau**2 g=tau if order>=3: z=dot(r2,r2dot)/(dot(r2,r2)) q=dot(r2dot,r2dot)/(dot(r2,r2))-u f+=1/2*u*z*tau**3 g+=-1/6*u*tau**3 if order>=4: f+=1/24*(3*u*q-15*u*z**2+u**2)*tau**4 g+=1/4*u*z*tau**4 return f, g def getFGVals(self, tau1:float, tau3:float, r2mag:float, r2dot:list, order1:int, order2:int, r2:list=[]): """ Gets all f and g values Args: tau1 (float): the time in Julian Days for observation 1 from obs 2(T1-T2) tau3 (float): the time in Julian days for observation 3 from obs 2(T3-T2) r2mag (float): the magnitude of r2 r2dot (list): the velocity vector 2 order1 (int): Order of Taylor Series expansion for f and g values for observation 1 order2 (int): Order of Taylor Series expansion for f and g values for observation 3 r2 (list): optional parameter, the position vector 2 Returns: lists: [f1,f3], [g1,g3] """ f1,g1=self.fg(tau1,r2mag,r2dot,order1,r2) f3,g3=self.fg(tau3,r2mag,r2dot,order2,r2) return [f1,f3], [g1,g3] def getDCoef(self, ra:list, dec:list, R1:list, R2:list, R3:list): """ Gets the D coefficients given the ra and dec for three observations (in radians) Args: ra (list): the right ascensions for three observations (radians) dec (list): the declinations for three observations (radians) R1 (list): the sun vector for observation 1 R2 (list): the sun vector for observation 2 R3 (list): the sun vector for observation 3 Returns: list: [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33], [rhohat1, rhohat2, rhohat3] """ ra1,ra2,ra3=ra[0],ra[1],ra[2] dec1,dec2,dec3=dec[0],dec[1],dec[2] rhohat1=np.array([np.cos(ra1)*np.cos(dec1), np.sin(ra1)*np.cos(dec1), np.sin(dec1)]) rhohat2=np.array([np.cos(ra2)*np.cos(dec2), np.sin(ra2)*np.cos(dec2), np.sin(dec2)]) rhohat3=np.array([np.cos(ra3)*np.cos(dec3), np.sin(ra3)*np.cos(dec3), np.sin(dec3)]) D0=dot(rhohat1, cross(rhohat2,rhohat3)) D11=dot(cross(R1, rhohat2),rhohat3) D12=dot(cross(R2, rhohat2),rhohat3) D13=dot(cross(R3, rhohat2),rhohat3) D21=dot(cross(rhohat1,R1), rhohat3) D22=dot(cross(rhohat1,R2), rhohat3) D23=dot(cross(rhohat1,R3), rhohat3) D31=dot(rhohat1, cross(rhohat2,R1)) D32=dot(rhohat1, cross(rhohat2,R2)) D33=dot(rhohat1, cross(rhohat2,R3)) return [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33], np.array([rhohat1, rhohat2, rhohat3]) def MoGGenData(self,loaded:bool,selTime:list=[],selDate:list=[],ra:list=[],dec:list=[]): """ Generates the data for Method of Gauss calculations Args: loaded (bool): True if data is already loaded selTime (list): list of chosen times (in Julian days) for observations 1,2,3 selDate (list): list of chosen datesfor observations 1,2,3 ra (list): list of right ascensions dec (list): list of declinations Returns: lists: ra, dec, R1, R2, R3, taus, ts (original times in Julian days) """ if not loaded: self.data.getInput(self.inputFile) # formats and generates info # generate data if not(selTime==[]): # using Julian day times if ra==[] and dec==[]: ra,dec=[],[] for time in selTime: r,d=self.data.getRADECInput(time=time) ra.append(np.deg2rad(r)) dec.append(np.deg2rad(d)) R1=self.data.getSunInput(time=selTime[0]) R2=self.data.getSunInput(time=selTime[1]) R3=self.data.getSunInput(time=selTime[2]) # calculate the taus t1,t2,t3=selTime[0],selTime[1],selTime[2] ts=[t1,t2,t3] T1=t1-t2 T3=t3-t2 T=t3-t1 taus = [T1,T3,T] # in Gaussian days elif not(selDate==[]): if ra==[] and dec==[]: ra,dec=[],[] for date in selDate: r,d=self.data.getRADECInput(date=date) ra.append(np.deg2rad(r)) dec.append(np.deg2rad(d)) R1=self.data.getSunInput(date=selDate[0]) R2=self.data.getSunInput(date=selDate[1]) R3=self.data.getSunInput(date=selDate[2]) # calculate the taus given the dates t1,t2,t3=self.data.getJDTime(selDate[0]),self.data.getJDTime(selDate[1]),self.data.getJDTime(selDate[2]) ts=[t1,t2,t3] T1=t1-t2 T3=t3-t2 T=t3-t1 taus = [T1*self.k,T3*self.k,T*self.k] else: raise Exception("No data given") self.loadedValues=[ra,dec,np.array(R1),np.array(R2),np.array(R3),taus,np.array(ts)] else: if not(ra==[] and dec==[]): self.loadedValues[0] = ra self.loadedValues[1] = dec return self.loadedValues def MoGCalcRhos(self, rhohats:list, coefD:list, fvals:list, gvals:list): """ Generates the rhos (and their magnitudes) for Method of Gauss calculations Args: rhohats (list): the direction vectors for rhos coefD (list): the D coefficients fvals (list): the f values gvals (list): the g values Returns: lists: rhos, rhomags """ f1,f3=fvals g1,g3=gvals D0,D11,D12,D13,D21,D22,D23,D31,D32,D33=coefD C1=g3/(f1*g3-g1*f3) C2=-1 C3=-g1/(f1*g3-g1*f3) rho1=(C1*D11+C2*D12+C3*D13)/(C1*D0) rho2=(C1*D21+C2*D22+C3*D23)/(C2*D0) rho3=(C1*D31+C2*D32+C3*D33)/(C3*D0) rhomags=np.array([rho1,rho2,rho3]) rhos=np.array([rhohats[0]*rho1, rhohats[1]*rho2, rhohats[2]*rho3]) return rhos, rhomags def MoGCalcPos(self, rhos:list, Rs:list)->list: """ Calculates the r vectors (position of asteroid from sun) for Method of Gauss calculations Args: rhos (list): the position from Earth vectors Rs (list): the position from Sun vectors Returns: lists: rs """ return rhos-Rs def MoGGetErr(self, prev:list, cur:list, tolerance:float)->bool: """ Calculates the r vectors (position of asteroid from sun) for Method of Gauss calculations Args: prev (list): previous r vector cur (list): the newly calculated r vector tolerance (float): the tolerance Returns: bool: True if error is good, False if no within tolerance """ for i in range(len(prev)): if abs(prev[i]-cur[i])>tolerance: return False return True def MoGGetAdjustedTaus(self, origt:list, rhomags:float)->list: """ Returns adjusted taus for Method of Gauss calculations Args: origt (list): original observation times in Julian days rhomags (list): the magnitude of the rho vectors Returns: list: the adjusted taus """ ts=np.copy(origt)-rhomags/self.cAU t1,t2,t3=ts return [(t1-t2)*self.k,(t3-t2)*self.k,(t3-t1)*self.k] def MoG(self,selTime:list=[],selDate:list=[],override:bool=True,ra:list=[],dec:list=[],loaded:bool=False): """ Performs Method of Gauss calculations to determine orbital elements Args: selTime (list): list of chosen times (in Julian days) for observations 1,2,3 selDate (list): list of chosen datesfor observations 1,2,3 override (bool): boolean, True if override info ra (list): optional list of right ascensions dec (list): optional list of declinations loaded (bool): optional, True if all data is already loaded (speeds up run time) Returns: None """ # generate data ra,dec,R1,R2,R3,taus,ts=self.MoGGenData(False,selTime,selDate,ra,dec) # calculate the initial estimate for r2 DONE coefD,rhohats=self.getDCoef(ra, dec, R1, R2, R3) SELcoefD=[coefD[0],coefD[4],coefD[5],coefD[6]] # [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33] -> [D0,D21,D22,D23] r2MagGuesses,rhoMagGuesses=self.SEL(taus, R2, rhohats[1], SELcoefD) results=[] # calculate the f and g values to second order for r2mag in r2MagGuesses: # initial guesses fvals,gvals=self.getFGVals(taus[0], taus[1], r2mag, [], 2, 2) # using r2mag rhos,rhomags=self.MoGCalcRhos(rhohats, coefD, fvals, gvals) rs=self.MoGCalcPos(rhos, np.array([R1,R2,R3])) # calculate the central velocity vector f1,f3=fvals g1,g3=gvals r2dot = (-f3/(-f3*g1+g3*f1))*rs[0] + (f1/(f1*g3-f3*g1))*rs[2] counter=0 timedOut=True timeout=time.time() + 60*3 # timeout after 3 minutes while counter<5000 and time.time()<timeout: # timeout prev=rs[1] # time adjustment newtaus=self.MoGGetAdjustedTaus(ts, rhomags) fvals,gvals=self.getFGVals(newtaus[0], newtaus[1], r2mag, r2dot, 4, 4, rs[1]) # using r2mag rhos,rhomags=self.MoGCalcRhos(rhohats, coefD, fvals, gvals) rs=self.MoGCalcPos(rhos, np.array([R1,R2,R3])) f1,f3=fvals g1,g3=gvals r2dot = (-f3/(-f3*g1+g3*f1))*rs[0] + (f1/(f1*g3-f3*g1))*rs[2] counter+=1 if (self.MoGGetErr(prev, rs[1], 1e-23)): timedOut=False break if not timedOut: # rotate around x axis to get to ecliptic r2=rotX(np.array(rs[1]),-self.eps) r2dot=rotX(r2dot,-self.eps) rho2=rotX(rhos[1],-self.eps) results.append([r2, r2dot, rho2]) # position and velocity resIndex=0 if len(results)>1: # get user input to choose which result to use for i in range(len(results)): print("Result",str(i)+":") print("\tPos from Sun:",r2,"\n\tVel:",r2dot,"\n\tPos from Earth:",rho2,"\n") while True: try: resIndex=int(input("Choose which result to use (int): ")) if not(0<=resIndex<len(results)): resIndex/=0 # throw an error break except: print("Enter a valid integer") if len(results)==0: return False, 0, 0, 0, 0, 0, 0 # get final answers pos,vel,rho=results[resIndex] if override: self.genElements(pos,np.array(vel)*(2*math.pi)/365.2568983,ts[1]) self.vel = vel self.pos = pos self.rho = rho else: a,e,i,o,v,w,T,M=self.genElements(pos,np.array(vel)*(2*math.pi)/365.2568983,ts[1], update=False) return True, a,e,i,o,w,T def getError(self, results:list): """ Prints error and results from orbital elements determination after Method of Gauss calculations Args: results (list): [e,0,i,o,w,T,0,0,0,a] Returns: None """ # adjust time of perihelion from results to match period of calculated time of perihelion period=results[9]**(3/2)*self.toGaussian results[5]=self.time-((self.time-results[5])%period) # subtract the change in time since the last perihelion from the obs 2 time self.od.printError(results) def exportResults(self, time:float, date:str, fileName:str): """ Exports results to a file Args: time (float): time in Julian days for the Mean Anomaly date (str): date for Mean Anomaly fileName (str): the exported file name Returns: None """ M=self.od.getTimeMeanAnomaly(time, date) self.data.printResults(fileName, self.pos, self.vel/self.toGaussian*(2*math.pi), self.rho, self.a, self.e, self.i, self.o, self.T, self.w, date, M) def genEphemeris(self, inputFile:str, outputFile:str, actualEph:str): """ Generates ephemeris for all times in a given sun position file. Exports to an output file Args: inputFile (str): input file name outputFile (str): output file name actualEph (str): file with real values from JPL horizons Returns: None """ results=[] for time,date,R in self.data.getAllSunPos(inputFile): ra,dec=self.ephemeris(time, date, sunPos=R) ra=str(ra[0]) + " " + str(ra[1]) + " " + str(ra[2]) if dec[0]<0: sign="-" else: sign="+" dec=sign+str(dec[0]) + " " + str(dec[1]) + " " + str(dec[2]) results.append([date,str(time),ra,dec]) self.data.exportEphemeris(outputFile, results, actualEph) def monteCarlo(self, n:int, files:str, outputFile:str, results:list, selTime:list=[], selDate:list=[]): """ Using uncertainty, runs through Method of Gauss n-times to generate orbital elements. Outputs to file. Args: n (int): number of times to run monteCarlo files (list): list of fits file names outputFile (str): the output file name results (list): the actual values for error analysis [a, e, i, o, w, T] selTime (list): optional; the selected times for which to run Method of Gauss selDate (list): optional; the selected dates for which to run Method of Gauss Returns: None """ rmsDec,rmsRA=[],[] for file in files: data = fits.open(file)[1].data fieldDec, indexDec = data.field_dec[:], data.index_dec[:] fieldRA, indexRA = data.field_ra[:], data.index_ra[:] dec = np.sqrt(1/int(fieldDec.shape[0]) * sum([(indexDec[i]-fieldDec[i])**2 for i in range(int(fieldDec.shape[0]))])) ra = np.sqrt(1/int(fieldRA.shape[0]) * sum([(indexRA[i]-fieldRA[i])**2 for i in range(int(fieldDec.shape[0]))])) rmsDec.append(dec) rmsRA.append(ra) ora, odec, R1, R2, R3, taus, ts = self.MoGGenData(False,selTime=selTime,selDate=selDate) vals=[] num=0 for trial in range(n): newdec = [np.deg2rad(np.random.normal()*rmsDec[i] + np.rad2deg(odec[i])) for i in range(3)] newra = [np.deg2rad(np.random.normal()*rmsRA[i] + np.rad2deg(ora[i])) for i in range(3)] works, a, e, i, o, w, T=self.MoG(selTime=selTime,selDate=selDate,override=False,ra=newra,dec=newdec,loaded=True) if works: num+=1 calcVals=[num,a,e,i,o,w,T] # sus vals.append(calcVals) vals.append(calcVals) # adjust time of perihelion for results # for time of perihelion, need to adjust to be close to the given observation time # because our calculated time of perihelion is closest to the given observation time period=results[0]**(3/2)*self.toGaussian results[5]=ts[1]-((ts[1]-results[5])%period) # subtract the change in time since the last perihelion from the obs 2 time # write to output file self.data.exportMonteCarlo(outputFile, vals.copy(), results) # histograms labels=["","Semi-Major Axis","Eccentricity", "Inclination", "Longitude of Ascending Node", "Argument of Perihelion", "Time of Perihelion Passage"] xlabels=["AU","Value","Degrees","Degrees","Degrees","Julian Day Number"] custom_lines = [Line2D([0], [0], color='black', linestyle='dashed',lw=1), Line2D([0], [0], color='blue', lw=1)] for j in range(5): mean=sum([vals[i][1+j] for i in range(len(vals))])/len(vals) sdom=np.sqrt(sum([(vals[i][j+1]-mean)**2 for i in range(len(vals))])/len(vals)) plt.hist([vals[i][1+j] for i in range(len(vals))],bins=20) plt.axvline(results[j], color='k', linestyle='dashed', linewidth=1) plt.axvline(mean, color='b', linewidth=1) plt.title(labels[1+j]) plt.legend(custom_lines, ['True Value', 'Mean']) plt.xlabel(xlabels[j]) plt.ylabel("Frequency") plt.show() print(labels[j+1]+": \n\tmean: "+str(mean)+"\n\terror: " + str(error(results[j],mean)) + "\n\tstandard deviation of mean: " + str(sdom) + "\n") # special case for time of perihelion mean=sum([vals[i][6] for i in range(len(vals))])/len(vals) sdom=np.sqrt(sum([(vals[i][6]-mean)**2 for i in range(len(vals))])/len(vals)) plt.hist([vals[i][6] for i in range(len(vals))],bins=20) plt.axvline(results[5], color='k', linestyle='dashed', linewidth=1) plt.axvline(mean, color='b', linewidth=1) plt.title(labels[6]) plt.legend(custom_lines, ['True Value', 'Mean']) plt.xlabel(xlabels[5]) plt.ylabel("Frequency") plt.show() print(labels[6]+": \n\tmean: "+str(mean)+ "\n\terror: " + str(error(results[5],mean)) + "\n\tstandard deviation of mean: " + str(sdom) + "\n")
Methods
def MoG(self, selTime: list = [], selDate: list = [], override: bool = True, ra: list = [], dec: list = [], loaded: bool = False)
-
Performs Method of Gauss calculations to determine orbital elements
Args
selTime
:list
- list of chosen times (in Julian days) for observations 1,2,3
selDate
:list
- list of chosen datesfor observations 1,2,3
override
:bool
- boolean, True if override info
ra
:list
- optional list of right ascensions
dec
:list
- optional list of declinations
loaded
:bool
- optional, True if all data is already loaded (speeds up run time)
Returns
None
Expand source code
def MoG(self,selTime:list=[],selDate:list=[],override:bool=True,ra:list=[],dec:list=[],loaded:bool=False): """ Performs Method of Gauss calculations to determine orbital elements Args: selTime (list): list of chosen times (in Julian days) for observations 1,2,3 selDate (list): list of chosen datesfor observations 1,2,3 override (bool): boolean, True if override info ra (list): optional list of right ascensions dec (list): optional list of declinations loaded (bool): optional, True if all data is already loaded (speeds up run time) Returns: None """ # generate data ra,dec,R1,R2,R3,taus,ts=self.MoGGenData(False,selTime,selDate,ra,dec) # calculate the initial estimate for r2 DONE coefD,rhohats=self.getDCoef(ra, dec, R1, R2, R3) SELcoefD=[coefD[0],coefD[4],coefD[5],coefD[6]] # [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33] -> [D0,D21,D22,D23] r2MagGuesses,rhoMagGuesses=self.SEL(taus, R2, rhohats[1], SELcoefD) results=[] # calculate the f and g values to second order for r2mag in r2MagGuesses: # initial guesses fvals,gvals=self.getFGVals(taus[0], taus[1], r2mag, [], 2, 2) # using r2mag rhos,rhomags=self.MoGCalcRhos(rhohats, coefD, fvals, gvals) rs=self.MoGCalcPos(rhos, np.array([R1,R2,R3])) # calculate the central velocity vector f1,f3=fvals g1,g3=gvals r2dot = (-f3/(-f3*g1+g3*f1))*rs[0] + (f1/(f1*g3-f3*g1))*rs[2] counter=0 timedOut=True timeout=time.time() + 60*3 # timeout after 3 minutes while counter<5000 and time.time()<timeout: # timeout prev=rs[1] # time adjustment newtaus=self.MoGGetAdjustedTaus(ts, rhomags) fvals,gvals=self.getFGVals(newtaus[0], newtaus[1], r2mag, r2dot, 4, 4, rs[1]) # using r2mag rhos,rhomags=self.MoGCalcRhos(rhohats, coefD, fvals, gvals) rs=self.MoGCalcPos(rhos, np.array([R1,R2,R3])) f1,f3=fvals g1,g3=gvals r2dot = (-f3/(-f3*g1+g3*f1))*rs[0] + (f1/(f1*g3-f3*g1))*rs[2] counter+=1 if (self.MoGGetErr(prev, rs[1], 1e-23)): timedOut=False break if not timedOut: # rotate around x axis to get to ecliptic r2=rotX(np.array(rs[1]),-self.eps) r2dot=rotX(r2dot,-self.eps) rho2=rotX(rhos[1],-self.eps) results.append([r2, r2dot, rho2]) # position and velocity resIndex=0 if len(results)>1: # get user input to choose which result to use for i in range(len(results)): print("Result",str(i)+":") print("\tPos from Sun:",r2,"\n\tVel:",r2dot,"\n\tPos from Earth:",rho2,"\n") while True: try: resIndex=int(input("Choose which result to use (int): ")) if not(0<=resIndex<len(results)): resIndex/=0 # throw an error break except: print("Enter a valid integer") if len(results)==0: return False, 0, 0, 0, 0, 0, 0 # get final answers pos,vel,rho=results[resIndex] if override: self.genElements(pos,np.array(vel)*(2*math.pi)/365.2568983,ts[1]) self.vel = vel self.pos = pos self.rho = rho else: a,e,i,o,v,w,T,M=self.genElements(pos,np.array(vel)*(2*math.pi)/365.2568983,ts[1], update=False) return True, a,e,i,o,w,T
def MoGCalcPos(self, rhos: list, Rs: list) ‑> list
-
Calculates the r vectors (position of asteroid from sun) for Method of Gauss calculations
Args
rhos
:list
- the position from Earth vectors
Rs
:list
- the position from Sun vectors
Returns
lists
- rs
Expand source code
def MoGCalcPos(self, rhos:list, Rs:list)->list: """ Calculates the r vectors (position of asteroid from sun) for Method of Gauss calculations Args: rhos (list): the position from Earth vectors Rs (list): the position from Sun vectors Returns: lists: rs """ return rhos-Rs
def MoGCalcRhos(self, rhohats: list, coefD: list, fvals: list, gvals: list)
-
Generates the rhos (and their magnitudes) for Method of Gauss calculations
Args
rhohats
:list
- the direction vectors for rhos
coefD
:list
- the D coefficients
fvals
:list
- the f values
gvals
:list
- the g values
Returns
lists
- rhos, rhomags
Expand source code
def MoGCalcRhos(self, rhohats:list, coefD:list, fvals:list, gvals:list): """ Generates the rhos (and their magnitudes) for Method of Gauss calculations Args: rhohats (list): the direction vectors for rhos coefD (list): the D coefficients fvals (list): the f values gvals (list): the g values Returns: lists: rhos, rhomags """ f1,f3=fvals g1,g3=gvals D0,D11,D12,D13,D21,D22,D23,D31,D32,D33=coefD C1=g3/(f1*g3-g1*f3) C2=-1 C3=-g1/(f1*g3-g1*f3) rho1=(C1*D11+C2*D12+C3*D13)/(C1*D0) rho2=(C1*D21+C2*D22+C3*D23)/(C2*D0) rho3=(C1*D31+C2*D32+C3*D33)/(C3*D0) rhomags=np.array([rho1,rho2,rho3]) rhos=np.array([rhohats[0]*rho1, rhohats[1]*rho2, rhohats[2]*rho3]) return rhos, rhomags
def MoGGenData(self, loaded: bool, selTime: list = [], selDate: list = [], ra: list = [], dec: list = [])
-
Generates the data for Method of Gauss calculations
Args
loaded
:bool
- True if data is already loaded
selTime
:list
- list of chosen times (in Julian days) for observations 1,2,3
selDate
:list
- list of chosen datesfor observations 1,2,3
ra
:list
- list of right ascensions
dec
:list
- list of declinations
Returns
lists
- ra, dec, R1, R2, R3, taus, ts (original times in Julian days)
Expand source code
def MoGGenData(self,loaded:bool,selTime:list=[],selDate:list=[],ra:list=[],dec:list=[]): """ Generates the data for Method of Gauss calculations Args: loaded (bool): True if data is already loaded selTime (list): list of chosen times (in Julian days) for observations 1,2,3 selDate (list): list of chosen datesfor observations 1,2,3 ra (list): list of right ascensions dec (list): list of declinations Returns: lists: ra, dec, R1, R2, R3, taus, ts (original times in Julian days) """ if not loaded: self.data.getInput(self.inputFile) # formats and generates info # generate data if not(selTime==[]): # using Julian day times if ra==[] and dec==[]: ra,dec=[],[] for time in selTime: r,d=self.data.getRADECInput(time=time) ra.append(np.deg2rad(r)) dec.append(np.deg2rad(d)) R1=self.data.getSunInput(time=selTime[0]) R2=self.data.getSunInput(time=selTime[1]) R3=self.data.getSunInput(time=selTime[2]) # calculate the taus t1,t2,t3=selTime[0],selTime[1],selTime[2] ts=[t1,t2,t3] T1=t1-t2 T3=t3-t2 T=t3-t1 taus = [T1,T3,T] # in Gaussian days elif not(selDate==[]): if ra==[] and dec==[]: ra,dec=[],[] for date in selDate: r,d=self.data.getRADECInput(date=date) ra.append(np.deg2rad(r)) dec.append(np.deg2rad(d)) R1=self.data.getSunInput(date=selDate[0]) R2=self.data.getSunInput(date=selDate[1]) R3=self.data.getSunInput(date=selDate[2]) # calculate the taus given the dates t1,t2,t3=self.data.getJDTime(selDate[0]),self.data.getJDTime(selDate[1]),self.data.getJDTime(selDate[2]) ts=[t1,t2,t3] T1=t1-t2 T3=t3-t2 T=t3-t1 taus = [T1*self.k,T3*self.k,T*self.k] else: raise Exception("No data given") self.loadedValues=[ra,dec,np.array(R1),np.array(R2),np.array(R3),taus,np.array(ts)] else: if not(ra==[] and dec==[]): self.loadedValues[0] = ra self.loadedValues[1] = dec return self.loadedValues
def MoGGetAdjustedTaus(self, origt: list, rhomags: float) ‑> list
-
Returns adjusted taus for Method of Gauss calculations
Args
origt
:list
- original observation times in Julian days
rhomags
:list
- the magnitude of the rho vectors
Returns
list
- the adjusted taus
Expand source code
def MoGGetAdjustedTaus(self, origt:list, rhomags:float)->list: """ Returns adjusted taus for Method of Gauss calculations Args: origt (list): original observation times in Julian days rhomags (list): the magnitude of the rho vectors Returns: list: the adjusted taus """ ts=np.copy(origt)-rhomags/self.cAU t1,t2,t3=ts return [(t1-t2)*self.k,(t3-t2)*self.k,(t3-t1)*self.k]
def MoGGetErr(self, prev: list, cur: list, tolerance: float) ‑> bool
-
Calculates the r vectors (position of asteroid from sun) for Method of Gauss calculations
Args
prev
:list
- previous r vector
cur
:list
- the newly calculated r vector
tolerance
:float
- the tolerance
Returns
bool
- True if error is good, False if no within tolerance
Expand source code
def MoGGetErr(self, prev:list, cur:list, tolerance:float)->bool: """ Calculates the r vectors (position of asteroid from sun) for Method of Gauss calculations Args: prev (list): previous r vector cur (list): the newly calculated r vector tolerance (float): the tolerance Returns: bool: True if error is good, False if no within tolerance """ for i in range(len(prev)): if abs(prev[i]-cur[i])>tolerance: return False return True
def SEL(self, taus: list, sunR2: list, rhohat2: list, coefD: list)
-
Scalar Equation of Lagrange to calculate the roots (r) and rhos corresponding to each r
Args
taus
:list
- a list of floats of taus [T1,T3,T]
sunR2
:list
- a list representing the sun vector R2
rhohat2
:list
- a list representing the rhohat2 vector
coefD
:list
- a list of D coefficients [D0,D21,D22,D23]
Returns
lists
- roots (r's), rhos
Expand source code
def SEL(self, taus:list, sunR2:list, rhohat2:list, coefD:list): """ Scalar Equation of Lagrange to calculate the roots (r) and rhos corresponding to each r Args: taus (list): a list of floats of taus [T1,T3,T] sunR2 (list): a list representing the sun vector R2 rhohat2 (list): a list representing the rhohat2 vector coefD (list): a list of D coefficients [D0,D21,D22,D23] Returns: lists: roots (r's), rhos """ T1,T3,T=taus[0],taus[1],taus[2] D0,D21,D22,D23=coefD[0],coefD[1],coefD[2],coefD[3] A1=T3/T B1=A1/6*(T**2-T3**2) A3=-T1/T B3=A3/6*(T**2-T1**2) A=(A1*D21-D22+A3*D23)/(-D0) B=(B1*D21+B3*D23)/(-D0) E=-2*(dot(rhohat2, sunR2)) F=dot(sunR2,sunR2) a=-(A**2+A*E+F) b=-self.mu*(2*A*B+B*E) c=-self.mu**2*B**2 coef=[c,0,0,b,0,0,a,0,1] res=poly.polyroots(coef) temproots=[] for val in res: if np.isreal(val) and np.real(val)>0: temproots.append(np.real(val)) temprhos=[A+B/temproots[i]**3 for i in range(len(temproots))] # ignore pairs where rho magnitude is negative roots=[] rhos=[] #print(temproots, temprhos) for i in range(len(temproots)): if temprhos[i]>0.0: roots.append(temproots[i]) rhos.append(temprhos[i]) return roots,rhos
def ephemeris(self, time: float, date: str, file: str = '', sunPos: list = array([], dtype=float64))
-
Calculates RA and Dec given time and date, using previously calculated orbital elements
Args
time
:float
- time to determine ephemeris for in Julian Days
date
:str
- date for which to determine ephemeris
file
:str
- file name for sun positions; optional
Returns
floats
- ra, dec
Expand source code
def ephemeris(self, time:float, date:str, file:str="", sunPos:list=np.array([])): """ Calculates RA and Dec given time and date, using previously calculated orbital elements Args: time (float): time to determine ephemeris for in Julian Days date (str): date for which to determine ephemeris file (str): file name for sun positions; optional Returns: floats: ra, dec """ n=self.k*math.sqrt(self.mu/(self.a**3)) M=n*(time-self.T) E=newton(lambda E:M - E + self.e*np.sin(E), lambda E: -1+self.e*np.cos(E), M, 1e-17) pos=np.array([self.a*math.cos(E)-self.a*self.e, self.a*math.sqrt(1-self.e**2)*math.sin(E), 0]) # the four rotations pos=rotZX(pos,np.deg2rad(self.w),np.deg2rad(self.i)) pos=rotZX(pos,np.deg2rad(self.o),self.eps) if file=="": # no file given, use sunPos R=sunPos else: R=self.data.getSunPos(date, file) if np.array_equal(R, np.array([0,0,0])): raise Exception("Sun Position Not Found in SunPos.txt") rho=pos+R rhohat=rho/getMag(rho) dec=math.asin(rhohat[2]) cra=rhohat[0]/math.cos(dec) sra=rhohat[1]/math.cos(dec) ra=getAngle(sra,cra) dec=np.rad2deg(dec) ra=np.rad2deg(ra) dec=DECdecimalToDMS(dec) ra=RAdecimalToHMS(ra) return ra,dec
def exportResults(self, time: float, date: str, fileName: str)
-
Exports results to a file
Args
time
:float
- time in Julian days for the Mean Anomaly
date
:str
- date for Mean Anomaly
fileName
:str
- the exported file name
Returns
None
Expand source code
def exportResults(self, time:float, date:str, fileName:str): """ Exports results to a file Args: time (float): time in Julian days for the Mean Anomaly date (str): date for Mean Anomaly fileName (str): the exported file name Returns: None """ M=self.od.getTimeMeanAnomaly(time, date) self.data.printResults(fileName, self.pos, self.vel/self.toGaussian*(2*math.pi), self.rho, self.a, self.e, self.i, self.o, self.T, self.w, date, M)
def fg(self, tau: float, r2mag: float, r2dot: list, order: int, r2: list = [])
-
Gets the f and g values given one time
Args
tau
:float
- the time in Julian Days
r2mag
:float
- the magnitude of r2
r2dot
:list
- the velocity vector 2
order
:int
- order of f and g taylor series approximations
r2
:list
- optional parameter, the position vector 2
Returns
floats
- f, g
Expand source code
def fg(self, tau:float, r2mag:float, r2dot:list, order:int, r2:list=[]): """ Gets the f and g values given one time Args: tau (float): the time in Julian Days r2mag (float): the magnitude of r2 r2dot (list): the velocity vector 2 order (int): order of f and g taylor series approximations r2 (list): optional parameter, the position vector 2 Returns: floats: f, g """ if len(r2)==0: u=self.mu/r2mag**3 else: u=self.mu/getMag(r2)**3 f=1-1/2*u*tau**2 g=tau if order>=3: z=dot(r2,r2dot)/(dot(r2,r2)) q=dot(r2dot,r2dot)/(dot(r2,r2))-u f+=1/2*u*z*tau**3 g+=-1/6*u*tau**3 if order>=4: f+=1/24*(3*u*q-15*u*z**2+u**2)*tau**4 g+=1/4*u*z*tau**4 return f, g
def genElements(self, pos: list, vel: list, time: float, update: bool = True)
-
Calculates and returns the orbital elements given position, velocity, time
Args
pos
:list
- the position vector
vel
:list
- the velocity vector
time
:float
- the time in Julian days
update
:bool
- if True keeps the newly calculated orbital elements
Returns
floats
- the orbital elements; a,e,i,o,v,w,T,M
Expand source code
def genElements(self, pos:list, vel:list, time:float, update:bool=True): """ Calculates and returns the orbital elements given position, velocity, time Args: pos (list): the position vector vel (list): the velocity vector time (float): the time in Julian days update (bool): if True keeps the newly calculated orbital elements Returns: floats: the orbital elements; a,e,i,o,v,w,T,M """ if update: self.pos,self.vel,self.time=pos,vel,time self.od=ODElements(self.pos,self.vel,self.time) self.a,self.e,self.i,self.o,self.v,self.w,self.T,self.M = self.od.getElements() return self.getElements() else: od=ODElements(pos,vel,time) return od.getElements()
def genEphemeris(self, inputFile: str, outputFile: str, actualEph: str)
-
Generates ephemeris for all times in a given sun position file. Exports to an output file
Args
inputFile
:str
- input file name
outputFile
:str
- output file name
actualEph
:str
- file with real values from JPL horizons
Returns
None
Expand source code
def genEphemeris(self, inputFile:str, outputFile:str, actualEph:str): """ Generates ephemeris for all times in a given sun position file. Exports to an output file Args: inputFile (str): input file name outputFile (str): output file name actualEph (str): file with real values from JPL horizons Returns: None """ results=[] for time,date,R in self.data.getAllSunPos(inputFile): ra,dec=self.ephemeris(time, date, sunPos=R) ra=str(ra[0]) + " " + str(ra[1]) + " " + str(ra[2]) if dec[0]<0: sign="-" else: sign="+" dec=sign+str(dec[0]) + " " + str(dec[1]) + " " + str(dec[2]) results.append([date,str(time),ra,dec]) self.data.exportEphemeris(outputFile, results, actualEph)
def getDCoef(self, ra: list, dec: list, R1: list, R2: list, R3: list)
-
Gets the D coefficients given the ra and dec for three observations (in radians)
Args
ra
:list
- the right ascensions for three observations (radians)
dec
:list
- the declinations for three observations (radians)
R1
:list
- the sun vector for observation 1
R2
:list
- the sun vector for observation 2
R3
:list
- the sun vector for observation 3
Returns
list
- [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33], [rhohat1, rhohat2, rhohat3]
Expand source code
def getDCoef(self, ra:list, dec:list, R1:list, R2:list, R3:list): """ Gets the D coefficients given the ra and dec for three observations (in radians) Args: ra (list): the right ascensions for three observations (radians) dec (list): the declinations for three observations (radians) R1 (list): the sun vector for observation 1 R2 (list): the sun vector for observation 2 R3 (list): the sun vector for observation 3 Returns: list: [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33], [rhohat1, rhohat2, rhohat3] """ ra1,ra2,ra3=ra[0],ra[1],ra[2] dec1,dec2,dec3=dec[0],dec[1],dec[2] rhohat1=np.array([np.cos(ra1)*np.cos(dec1), np.sin(ra1)*np.cos(dec1), np.sin(dec1)]) rhohat2=np.array([np.cos(ra2)*np.cos(dec2), np.sin(ra2)*np.cos(dec2), np.sin(dec2)]) rhohat3=np.array([np.cos(ra3)*np.cos(dec3), np.sin(ra3)*np.cos(dec3), np.sin(dec3)]) D0=dot(rhohat1, cross(rhohat2,rhohat3)) D11=dot(cross(R1, rhohat2),rhohat3) D12=dot(cross(R2, rhohat2),rhohat3) D13=dot(cross(R3, rhohat2),rhohat3) D21=dot(cross(rhohat1,R1), rhohat3) D22=dot(cross(rhohat1,R2), rhohat3) D23=dot(cross(rhohat1,R3), rhohat3) D31=dot(rhohat1, cross(rhohat2,R1)) D32=dot(rhohat1, cross(rhohat2,R2)) D33=dot(rhohat1, cross(rhohat2,R3)) return [D0,D11,D12,D13,D21,D22,D23,D31,D32,D33], np.array([rhohat1, rhohat2, rhohat3])
def getElements(self)
-
Returns the orbital elements (already calculated)
Args
rad
:bool
- True if return in radians
Returns
floats
- a,e,i,o,v,w,T,M
Expand source code
def getElements(self): """ Returns the orbital elements (already calculated) Args: rad (bool): True if return in radians Returns: floats: a,e,i,o,v,w,T,M """ return self.a, self.e, self.i, self.o, self.v, self.w, self.T, self.M
def getError(self, results: list)
-
Prints error and results from orbital elements determination after Method of Gauss calculations
Args
results
:list
- [e,0,i,o,w,T,0,0,0,a]
Returns
None
Expand source code
def getError(self, results:list): """ Prints error and results from orbital elements determination after Method of Gauss calculations Args: results (list): [e,0,i,o,w,T,0,0,0,a] Returns: None """ # adjust time of perihelion from results to match period of calculated time of perihelion period=results[9]**(3/2)*self.toGaussian results[5]=self.time-((self.time-results[5])%period) # subtract the change in time since the last perihelion from the obs 2 time self.od.printError(results)
def getFGVals(self, tau1: float, tau3: float, r2mag: float, r2dot: list, order1: int, order2: int, r2: list = [])
-
Gets all f and g values
Args
tau1
:float
- the time in Julian Days for observation 1 from obs 2(T1-T2)
tau3
:float
- the time in Julian days for observation 3 from obs 2(T3-T2)
r2mag
:float
- the magnitude of r2
r2dot
:list
- the velocity vector 2
order1
:int
- Order of Taylor Series expansion for f and g values for observation 1
order2
:int
- Order of Taylor Series expansion for f and g values for observation 3
r2
:list
- optional parameter, the position vector 2
Returns
lists
- [f1,f3], [g1,g3]
Expand source code
def getFGVals(self, tau1:float, tau3:float, r2mag:float, r2dot:list, order1:int, order2:int, r2:list=[]): """ Gets all f and g values Args: tau1 (float): the time in Julian Days for observation 1 from obs 2(T1-T2) tau3 (float): the time in Julian days for observation 3 from obs 2(T3-T2) r2mag (float): the magnitude of r2 r2dot (list): the velocity vector 2 order1 (int): Order of Taylor Series expansion for f and g values for observation 1 order2 (int): Order of Taylor Series expansion for f and g values for observation 3 r2 (list): optional parameter, the position vector 2 Returns: lists: [f1,f3], [g1,g3] """ f1,g1=self.fg(tau1,r2mag,r2dot,order1,r2) f3,g3=self.fg(tau3,r2mag,r2dot,order2,r2) return [f1,f3], [g1,g3]
def monteCarlo(self, n: int, files: str, outputFile: str, results: list, selTime: list = [], selDate: list = [])
-
Using uncertainty, runs through Method of Gauss n-times to generate orbital elements. Outputs to file.
Args
n
:int
- number of times to run monteCarlo
files
:list
- list of fits file names
outputFile
:str
- the output file name
results
:list
- the actual values for error analysis [a, e, i, o, w, T]
selTime
:list
- optional; the selected times for which to run Method of Gauss
selDate
:list
- optional; the selected dates for which to run Method of Gauss
Returns
None
Expand source code
def monteCarlo(self, n:int, files:str, outputFile:str, results:list, selTime:list=[], selDate:list=[]): """ Using uncertainty, runs through Method of Gauss n-times to generate orbital elements. Outputs to file. Args: n (int): number of times to run monteCarlo files (list): list of fits file names outputFile (str): the output file name results (list): the actual values for error analysis [a, e, i, o, w, T] selTime (list): optional; the selected times for which to run Method of Gauss selDate (list): optional; the selected dates for which to run Method of Gauss Returns: None """ rmsDec,rmsRA=[],[] for file in files: data = fits.open(file)[1].data fieldDec, indexDec = data.field_dec[:], data.index_dec[:] fieldRA, indexRA = data.field_ra[:], data.index_ra[:] dec = np.sqrt(1/int(fieldDec.shape[0]) * sum([(indexDec[i]-fieldDec[i])**2 for i in range(int(fieldDec.shape[0]))])) ra = np.sqrt(1/int(fieldRA.shape[0]) * sum([(indexRA[i]-fieldRA[i])**2 for i in range(int(fieldDec.shape[0]))])) rmsDec.append(dec) rmsRA.append(ra) ora, odec, R1, R2, R3, taus, ts = self.MoGGenData(False,selTime=selTime,selDate=selDate) vals=[] num=0 for trial in range(n): newdec = [np.deg2rad(np.random.normal()*rmsDec[i] + np.rad2deg(odec[i])) for i in range(3)] newra = [np.deg2rad(np.random.normal()*rmsRA[i] + np.rad2deg(ora[i])) for i in range(3)] works, a, e, i, o, w, T=self.MoG(selTime=selTime,selDate=selDate,override=False,ra=newra,dec=newdec,loaded=True) if works: num+=1 calcVals=[num,a,e,i,o,w,T] # sus vals.append(calcVals) vals.append(calcVals) # adjust time of perihelion for results # for time of perihelion, need to adjust to be close to the given observation time # because our calculated time of perihelion is closest to the given observation time period=results[0]**(3/2)*self.toGaussian results[5]=ts[1]-((ts[1]-results[5])%period) # subtract the change in time since the last perihelion from the obs 2 time # write to output file self.data.exportMonteCarlo(outputFile, vals.copy(), results) # histograms labels=["","Semi-Major Axis","Eccentricity", "Inclination", "Longitude of Ascending Node", "Argument of Perihelion", "Time of Perihelion Passage"] xlabels=["AU","Value","Degrees","Degrees","Degrees","Julian Day Number"] custom_lines = [Line2D([0], [0], color='black', linestyle='dashed',lw=1), Line2D([0], [0], color='blue', lw=1)] for j in range(5): mean=sum([vals[i][1+j] for i in range(len(vals))])/len(vals) sdom=np.sqrt(sum([(vals[i][j+1]-mean)**2 for i in range(len(vals))])/len(vals)) plt.hist([vals[i][1+j] for i in range(len(vals))],bins=20) plt.axvline(results[j], color='k', linestyle='dashed', linewidth=1) plt.axvline(mean, color='b', linewidth=1) plt.title(labels[1+j]) plt.legend(custom_lines, ['True Value', 'Mean']) plt.xlabel(xlabels[j]) plt.ylabel("Frequency") plt.show() print(labels[j+1]+": \n\tmean: "+str(mean)+"\n\terror: " + str(error(results[j],mean)) + "\n\tstandard deviation of mean: " + str(sdom) + "\n") # special case for time of perihelion mean=sum([vals[i][6] for i in range(len(vals))])/len(vals) sdom=np.sqrt(sum([(vals[i][6]-mean)**2 for i in range(len(vals))])/len(vals)) plt.hist([vals[i][6] for i in range(len(vals))],bins=20) plt.axvline(results[5], color='k', linestyle='dashed', linewidth=1) plt.axvline(mean, color='b', linewidth=1) plt.title(labels[6]) plt.legend(custom_lines, ['True Value', 'Mean']) plt.xlabel(xlabels[5]) plt.ylabel("Frequency") plt.show() print(labels[6]+": \n\tmean: "+str(mean)+ "\n\terror: " + str(error(results[5],mean)) + "\n\tstandard deviation of mean: " + str(sdom) + "\n")
class ODElements (pos: list, vel: list, time: float)
-
Class that represents and calculates orbital elements
Initializes ODElements class
Args
pos
:list
- position of asteroid
vel
:list
- velocity of asteroid (non Gaussian)
time
:float
- given time in julian days
Returns
None
Expand source code
class ODElements: '''Class that represents and calculates orbital elements''' def __init__(self, pos:list, vel:list, time:float): """ Initializes ODElements class Args: pos (list): position of asteroid vel (list): velocity of asteroid (non Gaussian) time (float): given time in julian days Returns: None """ # constants self.k = 0.0172020989484 # au^(3/2)/day self.time=time self.vel=vel self.vel=np.array([self.vel[i]/(2*math.pi)*365.2568983 for i in range(3)]) # convert to Gaussian self.pos=pos self.angMoment=self.getAngMoment() self.mu=1 self.a=self.getSemiMajor() # semi-major axis self.e= self.getEcc() # eccentricity self.i=self.getInc() # inclination self.o=self.getLongAsc() # longitude of ascending node self.v=self.getTrueAnom() # true anomaly self.w=self.getArgPer() # argument of perihelion self.M=self.getMeanAnomaly() # mean anomaly self.T=self.getPeriT() # time of perihelion passage T def getInfo(self)->list: """ Returns info from given day from initialization Args: None Returns: list: all info """ return self.info def getPos(self)->list: """ Returns the position of the asteroid Args: None Returns: list: the position """ return self.pos def getVel(self)->list: """ Returns the velocity of the asteroid Args: None Returns: list: the velocity """ return self.vel def getPosMag(self)->float: """ Returns the magnitude of the position vector Args: None Returns: float: magnitude of position """ return math.sqrt(sum([self.pos[i]**2 for i in range(3)])) def getVelMag(self)->float: """ Returns the magnitude of the velocity vector Args: None Returns: float: magnitude of velocity """ return math.sqrt(sum([self.vel[i]**2 for i in range(3)])) def getAngMoment(self)->list: """ Calculates and returns the specific angular momentum Args: None Returns: list: specific angular momentum components """ pos,vel=self.getPos(),self.getVel() res=cross(pos,vel) return np.array([res[0], res[1], res[2]]) def getAngMomentMag(self)->float: """ Returns the magnitude of the specific angular momentum Args: None Returns: float: the magnitude of specific angular momentum """ return math.sqrt(sum([self.angMoment[i]**2 for i in range(3)])) def getSemiMajor(self)->float: """ Calculates and returns the semi major axis using vis-viva Args: None Returns: float: the semi major axis """ return 1/(2/self.getPosMag() - dot(self.vel,self.vel)/self.mu) def getEcc(self)->float: """ Calculates and returns the eccentricity Args: None Returns: float: eccentricity """ return math.sqrt(1-dot(self.angMoment, self.angMoment)/(self.mu*self.a)) def getInc(self, rad:bool=False)->float: """ Calculates and returns the inclination in degrees Args: rad (bool): True if return in radians Returns: float: the inclination in degrees or radians """ return math.acos(self.angMoment[2]/self.getAngMomentMag()) if rad else np.rad2deg(math.acos(self.angMoment[2]/self.getAngMomentMag())) def getLongAsc(self, rad:bool=False): """ Calculates and returns the longitude of ascending node in degrees Args: rad (bool): True if return in radians Returns: float: the longitude of ascending node in degrees or radians """ s=self.angMoment[0]/(self.getAngMomentMag()*math.sin(np.deg2rad(self.i))) c=-self.angMoment[1]/(self.getAngMomentMag()*math.sin(np.deg2rad(self.i))) return getAngle(s,c) if rad else np.rad2deg(getAngle(s,c)) def getArgPer(self, rad:bool=False)->float: """ Calculates and returns the argument of perihelion in degrees Args: rad (bool): True if return in radians Returns: float: the longitude of ascending node in degrees or radians """ x,y,z=self.pos[0],self.pos[1],self.pos[2] s=z/(self.getPosMag()*math.sin(np.deg2rad(self.i))) c=(x*math.cos(np.deg2rad(self.o)) + y*math.sin(np.deg2rad(self.o)))/self.getPosMag() U=np.rad2deg(getAngle(s,c)) return np.deg2rad((U-self.v)%360) if rad else (U-self.v)%360 def getTrueAnom(self, rad:bool=False)->float: """ Calculates and returns the true anomaly Args: rad (bool): True if return in radians Returns: float: the true anomaly in degrees or radians """ c=1/self.e*(self.a*(1-self.e**2)/self.getPosMag() - 1) s=self.a*(1-self.e**2)/(self.getAngMomentMag() * self.e)*dot(self.pos,self.vel)/self.getPosMag() return getAngle(s,c) if rad else np.rad2deg(getAngle(s,c)) def getPeriT(self)->float: """ Calculates and returns the time of perihelion Args: None Returns: float: the time of perihelion """ n=self.k/(self.a**(3/2)) return self.time-np.deg2rad(self.M)/n def getMeanAnomaly(self,rad:bool=False)->float: """ Calculates and returns the mean anomaly Args: rad (bool): True if return in radians Returns: float: the mean anomaly in degrees or radians """ s=(self.getPosMag()*math.sin(np.deg2rad(self.v)))/(self.a*np.sqrt(1-self.e**2)) c=(self.e+np.cos(np.deg2rad(self.v)))/(1+self.e*np.cos(np.deg2rad(self.v))) E=getAngle(s,c) return E-self.e*np.sin(E) if rad else np.rad2deg(E-self.e*np.sin(E)) def getTimeMeanAnomaly(self, time:float, date:str)->float: """ Calculates mean anomaly for given date Args: time (float): time in Julian days for the Mean Anomaly date (str): date for Mean Anomaly Returns: float: mean anomaly in degrees """ n=self.k*np.sqrt(self.mu/(self.a**3)) M=np.rad2deg(n*(time-self.T)) return M def printError(self, results:list): """ Prints everything Args: None Returns: None """ # EC, QR, IN, OM, W, Tp, N, MA, TA, A, AD, PR, print("Semi-major axis:", "\n\tactual:",results[9], "\n\tcalculated:",self.a, "\n\terror:",error(results[9],self.a)) print("Eccentricity:", "\n\tactual:",results[0], "\n\tcalculated:",self.e, "\n\terror:",error(results[0],self.e)) print("Inclination:","\n\tactual:",results[2],"\n\tcalculated:",self.i, "\n\terror:",error(results[2],self.i)) print("Longitude of Ascending Node:","\n\tactual:",results[3],"\n\tcalculated:",self.o, "\n\terror:",error(results[3],self.o)) #print("True anomaly:",results[8],self.v,error(results[8],self.v)) print("Argument of perihelion:","\n\tactual:",results[4],"\n\tcalculated:",self.w, "\n\terror:",error(results[4],self.w)) print("Time of Perihelion Passage T:","\n\tactual:",results[5],"\n\tcalculated:", self.T,"\n\terror:",error(results[5],self.T)) #print("Mean Anomaly:",results[7],self.M,error(results[7],self.M)) #print(od.a, od.e, od.i, od.o, od.v, od.w) def getElements(self): """ Returns all orbital elements Args: rad (bool): True if return in radians Returns: floats: a,e,i,o,v,w,T,M """ return self.a, self.e, self.i, self.o, self.v, self.w, self.T, self.M
Methods
def getAngMoment(self) ‑> list
-
Calculates and returns the specific angular momentum
Args
None
Returns
list
- specific angular momentum components
Expand source code
def getAngMoment(self)->list: """ Calculates and returns the specific angular momentum Args: None Returns: list: specific angular momentum components """ pos,vel=self.getPos(),self.getVel() res=cross(pos,vel) return np.array([res[0], res[1], res[2]])
def getAngMomentMag(self) ‑> float
-
Returns the magnitude of the specific angular momentum
Args
None
Returns
float
- the magnitude of specific angular momentum
Expand source code
def getAngMomentMag(self)->float: """ Returns the magnitude of the specific angular momentum Args: None Returns: float: the magnitude of specific angular momentum """ return math.sqrt(sum([self.angMoment[i]**2 for i in range(3)]))
def getArgPer(self, rad: bool = False) ‑> float
-
Calculates and returns the argument of perihelion in degrees
Args
rad
:bool
- True if return in radians
Returns
float
- the longitude of ascending node in degrees or radians
Expand source code
def getArgPer(self, rad:bool=False)->float: """ Calculates and returns the argument of perihelion in degrees Args: rad (bool): True if return in radians Returns: float: the longitude of ascending node in degrees or radians """ x,y,z=self.pos[0],self.pos[1],self.pos[2] s=z/(self.getPosMag()*math.sin(np.deg2rad(self.i))) c=(x*math.cos(np.deg2rad(self.o)) + y*math.sin(np.deg2rad(self.o)))/self.getPosMag() U=np.rad2deg(getAngle(s,c)) return np.deg2rad((U-self.v)%360) if rad else (U-self.v)%360
def getEcc(self) ‑> float
-
Calculates and returns the eccentricity
Args
None
Returns
float
- eccentricity
Expand source code
def getEcc(self)->float: """ Calculates and returns the eccentricity Args: None Returns: float: eccentricity """ return math.sqrt(1-dot(self.angMoment, self.angMoment)/(self.mu*self.a))
def getElements(self)
-
Returns all orbital elements
Args
rad
:bool
- True if return in radians
Returns
floats
- a,e,i,o,v,w,T,M
Expand source code
def getElements(self): """ Returns all orbital elements Args: rad (bool): True if return in radians Returns: floats: a,e,i,o,v,w,T,M """ return self.a, self.e, self.i, self.o, self.v, self.w, self.T, self.M
def getInc(self, rad: bool = False) ‑> float
-
Calculates and returns the inclination in degrees
Args
rad
:bool
- True if return in radians
Returns
float
- the inclination in degrees or radians
Expand source code
def getInc(self, rad:bool=False)->float: """ Calculates and returns the inclination in degrees Args: rad (bool): True if return in radians Returns: float: the inclination in degrees or radians """ return math.acos(self.angMoment[2]/self.getAngMomentMag()) if rad else np.rad2deg(math.acos(self.angMoment[2]/self.getAngMomentMag()))
def getInfo(self) ‑> list
-
Returns info from given day from initialization
Args
None
Returns
list
- all info
Expand source code
def getInfo(self)->list: """ Returns info from given day from initialization Args: None Returns: list: all info """ return self.info
def getLongAsc(self, rad: bool = False)
-
Calculates and returns the longitude of ascending node in degrees
Args
rad
:bool
- True if return in radians
Returns
float
- the longitude of ascending node in degrees or radians
Expand source code
def getLongAsc(self, rad:bool=False): """ Calculates and returns the longitude of ascending node in degrees Args: rad (bool): True if return in radians Returns: float: the longitude of ascending node in degrees or radians """ s=self.angMoment[0]/(self.getAngMomentMag()*math.sin(np.deg2rad(self.i))) c=-self.angMoment[1]/(self.getAngMomentMag()*math.sin(np.deg2rad(self.i))) return getAngle(s,c) if rad else np.rad2deg(getAngle(s,c))
def getMeanAnomaly(self, rad: bool = False) ‑> float
-
Calculates and returns the mean anomaly
Args
rad
:bool
- True if return in radians
Returns
float
- the mean anomaly in degrees or radians
Expand source code
def getMeanAnomaly(self,rad:bool=False)->float: """ Calculates and returns the mean anomaly Args: rad (bool): True if return in radians Returns: float: the mean anomaly in degrees or radians """ s=(self.getPosMag()*math.sin(np.deg2rad(self.v)))/(self.a*np.sqrt(1-self.e**2)) c=(self.e+np.cos(np.deg2rad(self.v)))/(1+self.e*np.cos(np.deg2rad(self.v))) E=getAngle(s,c) return E-self.e*np.sin(E) if rad else np.rad2deg(E-self.e*np.sin(E))
def getPeriT(self) ‑> float
-
Calculates and returns the time of perihelion
Args
None
Returns
float
- the time of perihelion
Expand source code
def getPeriT(self)->float: """ Calculates and returns the time of perihelion Args: None Returns: float: the time of perihelion """ n=self.k/(self.a**(3/2)) return self.time-np.deg2rad(self.M)/n
def getPos(self) ‑> list
-
Returns the position of the asteroid
Args
None
Returns
list
- the position
Expand source code
def getPos(self)->list: """ Returns the position of the asteroid Args: None Returns: list: the position """ return self.pos
def getPosMag(self) ‑> float
-
Returns the magnitude of the position vector
Args
None
Returns
float
- magnitude of position
Expand source code
def getPosMag(self)->float: """ Returns the magnitude of the position vector Args: None Returns: float: magnitude of position """ return math.sqrt(sum([self.pos[i]**2 for i in range(3)]))
def getSemiMajor(self) ‑> float
-
Calculates and returns the semi major axis using vis-viva
Args
None
Returns
float
- the semi major axis
Expand source code
def getSemiMajor(self)->float: """ Calculates and returns the semi major axis using vis-viva Args: None Returns: float: the semi major axis """ return 1/(2/self.getPosMag() - dot(self.vel,self.vel)/self.mu)
def getTimeMeanAnomaly(self, time: float, date: str) ‑> float
-
Calculates mean anomaly for given date
Args
time
:float
- time in Julian days for the Mean Anomaly
date
:str
- date for Mean Anomaly
Returns
float
- mean anomaly in degrees
Expand source code
def getTimeMeanAnomaly(self, time:float, date:str)->float: """ Calculates mean anomaly for given date Args: time (float): time in Julian days for the Mean Anomaly date (str): date for Mean Anomaly Returns: float: mean anomaly in degrees """ n=self.k*np.sqrt(self.mu/(self.a**3)) M=np.rad2deg(n*(time-self.T)) return M
def getTrueAnom(self, rad: bool = False) ‑> float
-
Calculates and returns the true anomaly
Args
rad
:bool
- True if return in radians
Returns
float
- the true anomaly in degrees or radians
Expand source code
def getTrueAnom(self, rad:bool=False)->float: """ Calculates and returns the true anomaly Args: rad (bool): True if return in radians Returns: float: the true anomaly in degrees or radians """ c=1/self.e*(self.a*(1-self.e**2)/self.getPosMag() - 1) s=self.a*(1-self.e**2)/(self.getAngMomentMag() * self.e)*dot(self.pos,self.vel)/self.getPosMag() return getAngle(s,c) if rad else np.rad2deg(getAngle(s,c))
def getVel(self) ‑> list
-
Returns the velocity of the asteroid
Args
None
Returns
list
- the velocity
Expand source code
def getVel(self)->list: """ Returns the velocity of the asteroid Args: None Returns: list: the velocity """ return self.vel
def getVelMag(self) ‑> float
-
Returns the magnitude of the velocity vector
Args
None
Returns
float
- magnitude of velocity
Expand source code
def getVelMag(self)->float: """ Returns the magnitude of the velocity vector Args: None Returns: float: magnitude of velocity """ return math.sqrt(sum([self.vel[i]**2 for i in range(3)]))
def printError(self, results: list)
-
Prints everything
Args
None
Returns
None
Expand source code
def printError(self, results:list): """ Prints everything Args: None Returns: None """ # EC, QR, IN, OM, W, Tp, N, MA, TA, A, AD, PR, print("Semi-major axis:", "\n\tactual:",results[9], "\n\tcalculated:",self.a, "\n\terror:",error(results[9],self.a)) print("Eccentricity:", "\n\tactual:",results[0], "\n\tcalculated:",self.e, "\n\terror:",error(results[0],self.e)) print("Inclination:","\n\tactual:",results[2],"\n\tcalculated:",self.i, "\n\terror:",error(results[2],self.i)) print("Longitude of Ascending Node:","\n\tactual:",results[3],"\n\tcalculated:",self.o, "\n\terror:",error(results[3],self.o)) #print("True anomaly:",results[8],self.v,error(results[8],self.v)) print("Argument of perihelion:","\n\tactual:",results[4],"\n\tcalculated:",self.w, "\n\terror:",error(results[4],self.w)) print("Time of Perihelion Passage T:","\n\tactual:",results[5],"\n\tcalculated:", self.T,"\n\terror:",error(results[5],self.T)) #print("Mean Anomaly:",results[7],self.M,error(results[7],self.M)) #print(od.a, od.e, od.i, od.o, od.v, od.w)