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.MMethods
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)