Code Monkey home page Code Monkey logo

lambertfit's Introduction

LambertFit

An experimental Python-based angles-only initial orbit determination tool for solar system objects by way of a Lambert solver.

About

LambertFit takes equatorial angular observations of solar system objects in right ascension and declination and finds an orbit that minimizes the RMS residuals. As the name implies, LambertFit leverages the solution to Lambert's problem to fit the orbit. The conceptual outline of the orbit determination process is as follows:

  1. Lambert solve an initial orbit between two observation endpoints (e.g. the first and the last observation) by guessing a number of constant, scalar heliocentric ranges at those two endpoints.
  2. Take the Lambert solution for the singular heliocentric range guess that yields the best (i.e. smallest) residuals as your starting orbit.
  3. Update each endpoint range estimate, one at a time, by stepping in the direction that reduces the residuals (as measured by the RMS error) until the residuals stop getting smaller.

There's a little more LambertFit detail on my website including plots for solves for the first 99 numbered objects for various observation durations and counts.

Installation

pip install git+https://github.com/bengebre/lambertfit

Requirements

I've tested the pip install method above with recent Anaconda installs, but if you're having issues, this is the environment LambertFit was developed and tested in.

  • python=3.8.8
  • numpy=1.20.1
  • astroquery=0.4.3
  • astropy=4.2.1
  • poliastro=[0.16.0,0.17.0]

Example usage

Get simulated RA/DEC observations from JPL Horizons

import numpy as np
import lambertfit as lf
from astroquery.jplhorizons import Horizons
from astropy.time import Time

#define observer location and body to observe
loc = 'G96' #MPC observatory location code (eg. 'G96' or '500@0')
body_id = '2000002' #small body designation (e.g. '2000002' == Pallas)

#how many (simulated) observations do we want and at what time
tstart = 2459000.5 #julian start date
dt = 14 #number of days of observations
nobs = 30 #number of observations

#observation times
times = np.linspace(tstart,tstart+dt,nobs) #julian date observation times
tdbs = Time(times,format='jd').tdb.value #barycenter dynamical times for the observations

#get simulated observation data from Horizons
ephs = Horizons(id=body_id,location=loc,epochs=times,id_type='designation').ephemerides()
radecs = np.array([[eph['RA'],eph['DEC']] for eph in ephs]) #RA and DEC in degrees

Orbit Determination via LambertFit

#sun->observer position vector at observation times
r_so = lf.sun_observer(loc,tdbs)

#LambertFit OD solution for RA/DEC observations
rvm_sb, rvn_sb, fit_rms = lf.fit(radecs,times,r_so)

Note: rvm_sb and rvn_sb are heliocentric position and velocity vectors of the LambertFit solution with units of AU and km/s at the endpoints (the first and last observation by default). fit_rms is the final RMS error of the orbit fit in arc seconds.

Useful optional arguments for fit()

LambertFit can be quite slow to converge. By default it stops solving when the step size reaches 100km. To prevent LamberFit from running for a half an hour while it chips away at that 5th decimal place of RMS, you can use the following optional arguments on the fit() function:

  • tol: The minimum improvement (in arc seconds) at which the solver stops solving. Default: 1e-5
  • max_iter: The maximum number of iterations the solver will attempt. Default: 100,000
  • min_rms: When the solver achieves this RMS threshold it will stop. Default: 0.001

To specify different endpoints to fit around than the first and last observations:

  • pts: The indicies of the observation endpoints that will be fit around. Default: [0,-1]

Results

The blue orbit is the earth. The true orbit of the body we're trying to fit an orbit to is in green. The left orange orbit is the initial guess orbit that LambertFit starts with (generated internally by LambertFit) and then refines. The LambertFit solution orbit is in orange on the right. The observations from JPL are equally spaced in time between the green diamond (the first) and the green circle (the last). The diamond and circle on the blue orbit represent the earth's position at the starting and ending observation times. The diamond and circle on the orange orbit represent LambertFit's estimate of the position of the solar system object at the starting and ending observation times. The reported RMS errors are in arc seconds.

g2000002-14

Caveats, Limitations and Todos

  • LambertFit uses two body propagation around the solar system barycenter. In my testing, solving for observation durations greater than 90 days or so starts to be limited by the fidelity of the orbit propagator.
  • LambertFit requires that you choose two observations (which I call endpoints) to solve around. By default these are the first and last observations. More than any other observations, the quality of the orbit LambertFit finds is dependent on how good these two endpoint observations are. The orbit is in a sense 'pinned' at these two endpoint observations and fit to the remaining observations.
  • LambertFit is SLOW. I'm essentially using the dumbest implementation of gradient descent possible right now. I'm optimistic that there is room for improvement here.

Acknowledgements

lambertfit's People

Contributors

bengebre avatar

Stargazers

 avatar

Watchers

 avatar

lambertfit's Issues

Lambertfit example raises OSError: Could not find a suitable TLS CA certificate bundle, invalid path: <null>

In running the Lambertfit example

#!/Users/user/opt/anaconda3/bin/python3

import numpy as np
import lambertfit as lf
from astroquery.jplhorizons import Horizons
from astropy.time import Time

#define observer location and body to observe
loc = 'G96' #MPC observatory location code (eg. 'G96' or '500@0')
body_id = '2000002' #small body designation (e.g. '2000002' == Pallas)

#how many (simulated) observations do we want and at what time
tstart = 2459000.5 #julian start date
dt = 14 #number of days of observations
nobs = 30 #number of observations

#observation times
times = np.linspace(tstart,tstart+dt,nobs) #julian date observation times
tdbs = Time(times,format='jd').tdb.value #barycenter dynamical times for the observations

#get simulated observation data from Horizons
ephs = Horizons(id=body_id,location=loc,epochs=times,id_type='designation').ephemerides()
radecs = np.array([[eph['RA'],eph['DEC']] for eph in ephs]) #RA and DEC in degrees

#sun->observer position vector at observation times
r_so = lf.sun_observer(loc,tdbs)

#LambertFit OD solution for RA/DEC observations
rvm_sb, rvn_sb, fit_rms = lf.fit(radecs,times,r_so)

The following traceback and error message occurs

Traceback (most recent call last):
  File "/Users/user/Various_Python_Examples/LambertFit_Ex-1.py", line 22, in <module>
    ephs = Horizons(id=body_id,location=loc,epochs=times,id_type='designation').ephemerides()
  File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/astroquery/utils/class_or_instance.py", line 25, in f
    return self.fn(obj, *args, **kwds)
  File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/astroquery/utils/process_asyncs.py", line 26, in newmethod
    response = getattr(self, async_method_name)(*args, **kwargs)
  File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/astroquery/jplhorizons/core.py", line 595, in ephemerides_async
    response = self._request('GET', URL, params=request_payload,
  File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/astroquery/query.py", line 317, in _request
    response = query.request(self._session,
  File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/astroquery/query.py", line 71, in request
    return session.request(self.method, self.url, params=self.params,
  File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/requests/sessions.py", line 589, in request
    resp = self.send(prep, **send_kwargs)
  File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/requests/sessions.py", line 703, in send
    r = adapter.send(request, **kwargs)
  File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/requests/adapters.py", line 458, in send
    self.cert_verify(conn, request.url, verify, cert)
  File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/requests/adapters.py", line 261, in cert_verify
    raise OSError(
OSError: Could not find a suitable TLS CA certificate bundle, invalid path: <null>

I'm running the Anaconda Distribution of Python version 3.9.18 on a Mac Pro(2019) running Mac OSX Sonoma version 14.2.1.

Please advise.

Sam Dupree.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.