question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

State vector differences between poliastro.twobody.Orbit and spg4

See original GitHub issue

I am trying to compare various poliastro orbit propagation methods vs the spg4 propagator. However, the resulting state vectors are significantly different between both.

🐞 Problem

Essentially, we generate a poliastro.twobody.Orbit and use it to initialise the spg4 orbit. However, when converting back from spg4 to Orbit (for the same epoch, without actual propagation), the state vector is off by several kilometres.

Sample code here:

#!/usr/local/bin/python3

from astropy import coordinates as coord, units as u
from astropy.time import Time
from astropy.coordinates import TEME

from poliastro.twobody import Orbit
from poliastro.bodies import Earth
from poliastro.twobody import angles
from poliastro.examples import iss

from sgp4.api import Satrec, SGP4_ERRORS, WGS84


if __name__ == "__main__":

    # Display some initial data
    print(f" Orbit: {iss}")
    print(" State vector [poliastro]")
    print(f"     r = {iss.r}")
    print(f"     v = {iss.v}")
    print()

    # Generate the equivalent using spg4
    epochTime = Time(iss.epoch, format='datetime', scale='utc')
    eccAnomaly = angles.nu_to_E(iss.nu, iss.ecc)
    meanAnomaly = angles.E_to_M(eccAnomaly, iss.ecc)
    sat = Satrec()
    sat.sgp4init(WGS84, 'i', 0, epochTime.jd - 2433281.5, 0.0, 0.0, 0.0,
                 iss.ecc, iss.argp.value, iss.inc.value, meanAnomaly.value,
                 iss.n.to(u.rad/u.minute).value, iss.raan.value,
                 )

    # Compute state vector from sgp4
    errorCode, rTEME, vTEME = sat.sgp4(epochTime.jd1, epochTime.jd2)
    if errorCode != 0:
        raise RuntimeError(SGP4_ERRORS[errorCode])

    pTEME = coord.CartesianRepresentation(rTEME*u.km)
    vTEME = coord.CartesianDifferential(vTEME*u.km/u.s)
    svTEME = TEME(pTEME.with_differentials(vTEME), obstime=iss.epoch)

    svITRS = svTEME.transform_to(coord.ITRS(obstime=iss.epoch))
    sv = Orbit.from_coords(Earth, svITRS)
    print("State vector [spg4]")
    print(f"     r = {sv.r}")
    print(f"     v = {sv.v}")
    print()

    print("State vector differences [poliastro - spg4]")
    print(f"     r = {iss.r - sv.r}")
    print(f"     v = {iss.v - sv.v}")
    print()

🖥 Results:

 State vector [poliastro]
     r = [  859.07256 -4137.20368  5295.56871] km
     v = [7.37289205 2.08223573 0.43999979] km / s

State vector [spg4]
     r = [  852.72472797 -4137.51683084  5287.30166418] km
     v = [7.38429906 2.06186541 0.430843  ] km / s

State vector differences [poliastro - spg4]
     r = [6.34783203 0.31315084 8.26704582] km
     v = [-0.01140701  0.02037032  0.0091568 ] km / s

💡 Possible solutions

This involves a combination of poliastro, astropy and spg4 and transformation between different frames, so it is not clear where the problem is.

Any advise is much appreciated.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:2
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

5reactions
astrojuanlucommented, May 2, 2021

After giving this a closer inspection, I can conclude that this is “not a bug”, but a very common confusion arising with TLEs. Let me explain without getting into much detail.

This line:

    sat.sgp4init(WGS84, 'i', 0, epochTime.jd - 2433281.5, 0.0, 0.0, 0.0,
                 iss.ecc, iss.argp.value, iss.inc.value, meanAnomaly.value,
                 iss.n.to(u.rad/u.minute).value, iss.raan.value,
                 )

creates a TLE using osculating orbital elements, which are the ones that Orbit objects in poliastro handle. As poliastro is mostly concerned with the purely Keplerian two body problem, this is everything we need.

However, as you know better than me1, the real world is way more complex, and orbits deviate from its purely Keplerian motion all the time. As a result, osculating elements only have value on a given instant in time, but they slowly evolve with time (with the exception, of course, of anomalies, that change rapidly anyway).

And here comes the common mistake: TLEs do not use osculating elements, but Brouwer mean elements. They are mean elements because they use some averaging technique over a period of time, and this technique is due to Dirk Brouwer (Brouwer, Dirk “Solution of the Problem of Artificial Satellite Theory without Drag”, 1959) as opposed to, say, Kozai mean elements (Kozai, Yoshihide “The Motion of a Close Earth Satellite”, 1959).

In summary, when creating a TLE directly from osculating elements, this leads to a systematic error, and therefore one cannot expect the propagation to be accurate (they will be precise, but not accurate, i.e. close to the true value).

This is a surprisingly common misconception that is not clearly addressed in the famous TLE FAQ by Dr. T. S. Kelso. I have pointed out this mistake in other places, for example in https://github.com/aerospaceresearch/orbitdeterminator/issues/195.

There are several ways to do this properly:

And there are several actions we could take on the poliastro side:

  • Add the wall-of-text above to the “Background” section of our documentation, with more examples and mathematical formulas, so it’s more easily discoverable by our users.
  • Work on better poliastro.twobody.Orbit to/from TLE (or any other GP data format, like OMM) so there is a clear API that does _the right thing_™ and reduces confusion.

Please @jtegedor let me know if all of this makes sense.

Please forgive this “mansplaining” line - it’s not for you @jtegedor, but for future readers!

0reactions
astrojuanlucommented, May 9, 2021

Merged! 🚀 I’m closing this support issue in favor of https://github.com/poliastro/poliastro/issues/1215, which I think it’s the first step towards providing a proper API.

Read more comments on GitHub >

github_iconTop Results From Across the Web

poliastro.twobody.orbit.scalar
Position and velocity of a body with respect to an attractor at a given time (epoch). Regardless of how the Orbit is created,...
Read more >
poliastro.twobody.states
State defined by its position and velocity vectors. ModifiedEquinoctialState. State defined by modified equinoctial elements representation. class ...
Read more >
Quickstart — poliastro 0.17.0 documentation
The core of poliastro are the Orbit objects inside the poliastro.twobody module. ... The position and velocity vectors or the orbital elements.
Read more >
poliastro.twobody.orbit
Orbit. Position and velocity of a body with respect to an attractor. Previous Next. © Copyright 2013, Juan Luis Cano Rodríguez and the...
Read more >
API reference — poliastro 0.17.0 documentation
class poliastro.twobody.Orbit(state, epoch). Position and velocity of a body with respect to an attractor at a given time (epoch). Regardless of how the ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found