brandon-rhodes / python-sgp4

Python version of the SGP4 satellite position library
MIT License
377 stars 88 forks source link

added whichconst to twoline2rv #57

Closed zouhairm closed 4 years ago

zouhairm commented 4 years ago

Alright, this hopefully addresses #47 by making the new API expose the gravity constant while still providing a convenient way to call directly to a lower level API if speed is of the essence.

Based on your comments, here is what I have done:

Since performance is of importance, I used %timeit to get an idea of the impact, results of %timeit satellite = sgp4.api.Satrec.twoline2rv(s, t) are as follow:

So about 10% slower with the user friendly version which makes the API fully backward compatible, and for those who can't afford a much slower initialization calling _twoline2rv directly is only 3% slower. Hopefully this is acceptable :)

Let me know if you have comments or suggestions.

P.S. I tried also with using PyArg_ParseTupleAndKeywords directly in wrapper.cpp, but it's easier to deal with the different types on the python side, and I think PyArg_ParseTupleAndKeywords is likely slower than parsing tuples directly.

Also, here is some code for testing

import sgp4
import sgp4.api
import sgp4.io
from sgp4.earth_gravity import wgs72, wgs84

import datetime as dt
import numpy as np
import itertools

s = '1 25544U 98067A   19343.69339541  .00001764  00000-0  38792-4 0  9991'
t = '2 25544  51.6439 211.2001 0007417  17.6667  85.6398 15.50103472202482'
%timeit satellite = sgp4.api.Satrec.twoline2rv(s, t)

TLE_LINES = (
    "1 43204U 18015K   18339.11168986  .00000941  00000-0  42148-4 0  9999",
    "2 43204  97.3719 104.7825 0016180 271.1347 174.4597 15.23621941 46156",
)

REF_DATE = dt.datetime(2019, 1, 1)

date_args = (REF_DATE.year,
        REF_DATE.month,
        REF_DATE.day,
        REF_DATE.hour,
        REF_DATE.minute,
        REF_DATE.second + REF_DATE.microsecond * 1e-6)

date_jday = sgp4.api.jday(*date_args)

io_wgs72 = np.array(sgp4.io.twoline2rv(TLE_LINES[0], TLE_LINES[1], wgs72).propagate(*date_args)).flatten()
io_wgs84 = np.array(sgp4.io.twoline2rv(TLE_LINES[0], TLE_LINES[1], wgs84).propagate(*date_args)).flatten()

api_wgs72 = np.array(sgp4.api.Satrec.twoline2rv(TLE_LINES[0], TLE_LINES[1], wgs72).sgp4(*date_jday)[1:]).flatten()
api_wgs84 = np.array(sgp4.api.Satrec.twoline2rv(TLE_LINES[0], TLE_LINES[1], wgs84).sgp4(*date_jday)[1:]).flatten()

for (a, u), (b, v) in itertools.combinations([
        ('io_wgs72 ', io_wgs72), ('api_wgs72', api_wgs72), 
        ('io_wgs84 ', io_wgs84), ('api_wgs84', api_wgs84)], 2):

    err = u-v
    p_err = np.linalg.norm(err[0:3])
    v_err = np.linalg.norm(err[3: ])*1e3
    print(f'Diff between {a} & {b}: pos = {p_err:.2f} km , vel = {v_err:.2f} m/s')

Output:

Diff between io_wgs72  & api_wgs72: pos = 0.00 km , vel = 0.00 m/s
Diff between io_wgs72  & io_wgs84 : pos = 0.88 km , vel = 0.98 m/s
Diff between io_wgs72  & api_wgs84: pos = 0.88 km , vel = 0.98 m/s
Diff between api_wgs72 & io_wgs84 : pos = 0.88 km , vel = 0.98 m/s
Diff between api_wgs72 & api_wgs84: pos = 0.88 km , vel = 0.98 m/s
Diff between io_wgs84  & api_wgs84: pos = 0.00 km , vel = 0.00 m/s
zouhairm commented 4 years ago

I think having an integer as the argument to twoline2rv compromises ease of use and readibility of the API.

What I suggest is the following:

Let me know if that's a reasonable compromise. It makes the new api.twoline2rv user friendly while still avoiding dependency on earth_gravity.py

brandon-rhodes commented 4 years ago

To start with your third point:

… Although I'd argue this is not really needed since io.twoline2rv still exists.

Happily, I agree with you! Your inclusion of the old-style gravity constants in your first draft had made me wonder if you thought that the change from wgs84WGS84 was going to be too onerous for folks wanting to upgrade from the old to the new API. If instead you think that it’s fine for folks to stay with the earlier API io.twoline2rv(…, wgs84) until such time as they can switch to api.twoline2rv(…, WGS84), then I am very happy to avoid the additional complexity of a new module. Thanks for clarifying instead of just implementing it when I first mentioned it!

Your first two points, then, are motivated by:

I think having an integer as the argument to twoline2rv compromises ease of use and readability of the API.

Could you show an example of the change in use, and an example of the change in readability, and comment on the loss? I myself was comparing these in my imagination:

sat = twoline2rv(…, wgs84)    # old
sat = twoline2rv(…, WGS84)    # new

The only difference I can see is that the new convention actually looks like the real-world all-caps spelling of the abbreviation for "World Geodetic System", whereas the old way puts it in lowercase. Letting people switch to the string 'wgs84' would (a) seem to cost an extra function + hash table lookup + string comparison, (b) turns the signature of that argument from "integer" to "string OR integer" which my static-types friends would remind me creates something of a mess compared to having a single type expected, and (c) means the user gets no immediate error if they misspell. Any string will look good to pyflakes or their type checker; whereas misspelling WGS84 will be immediately caught as they type because it would be an undefined symbol.

Could you outline the example lines that you were thinking of, and how the string-based convention in that case helps the user with their code?

zouhairm commented 4 years ago

I was trying to avoid the case of having to call: twoline2rv(line1, line2, 1)

But I think if we expose the enums as named variables from the api module (currently they are inside of vallado_cpp ) then I agree with sticking with the keyword argument being an integer instead of a string given the other points.

I'll make the changes and update the PR.

brandon-rhodes commented 4 years ago

But I think if we expose the enums as named variables from the api module (currently they are inside of vallado_cpp ) then I agree with sticking with the keyword argument being an integer instead of a string given the other points.

Ah, excellent point! Yes, please import all three constants into api. I had entirely failed to notice that the PR didn't do that yet. Thanks!

zouhairm commented 4 years ago

working on adding tests now, but wanted to push to make sure this is in the right direction.

brandon-rhodes commented 4 years ago

Thanks! I'll plan to do a new release this month to more widely share the improvement you've made.

zouhairm commented 4 years ago

Thanks @brandon-rhodes - any chance we can do a minor release sooner as I'm relying on this in a different project I need to release (and referring to a branch/commit vs. a proper version number is not ideal)

brandon-rhodes commented 4 years ago

I'll see if I can have things in shape by the end of the weekend for a Monday release.