API Refactor: State objects - poliastro/poliastro GitHub Wiki

The core of poliastro are State objects. They define the position of a body wrt another (e.g. a satellite wrt the Earth), assumed a particular reference frame.

Philosophy

I think a main goal could be: never ever have to remember the order of the returned elements. That means two things:

  • If there is a case where I have to do a, ecc, inc, raan, argp, nu = ss.coe there is something wrong.
  • I should always be able to retrieve individual elements: ss.a, ss.lonper.

Time info and Hohmann transfers

I can leave time information as optional, and set a default time in case it's missing (for example 2000-01-01 12:00:00).

Building States

New ideas

The current implementation (see https://github.com/Pybonacci/poliastro/blob/4718cb1ee1ad99284682ac922f775693fa23bf22/poliastro/twobody/core.py) dissatisfies me. I don't think rv2coe and coe2rv should output a State if the inputs are attractor and elements. And if those are not returning a State they make little sense being classmethods. Perhaps I should think about using a canonical representation and something like State.from_elements(Sun, a, ecc, inc, raan, argp, nu) (see http://programmers.stackexchange.com/q/235050/15297).

Old ideas

These combinations of orbital elements are accepted as input:

  • a, e, i, Ω, ω, ν
  • a, e, i, Ω, ω, M

In particular:

  • I'm not accepting eccentric (Æ), parabolic (B) or hyperbolic (H) anomaly as input, for one reason: the rest of the elements might not be consistent.
  • I'm not accepting longitude of periapsis, argument of latitude or mean longitude: for singular orbits, either I will provide helper functions (like State.circular, see API Refactor: Hohmann) or I will ask the user to convert on input.

I believe I should try to create an API that:

  1. is easy and direct for the regular cases, and
  2. is not too obstrusive for the non-obvious cases.

Current:

a = 1.523679 * u.AU
ecc = 0.093315
inc = 1.85 * u.deg
raan = 49.562 * u.deg
argp = 286.537 * u.deg
nu = 23.33 * u.deg
ss = twobody.State(Sun, a=a, ecc=ecc, inc=inc, raan=raan, argp=argp, nu=nu)

I need keyword arguments to detect the case. Verbose.

New:

a = 1.523679 * u.AU
ecc = 0.093315
inc = 1.85 * u.deg
raan = 49.562 * u.deg
argp = 286.537 * u.deg
nu = 23.33 * u.deg
ss = twobody.State(Sun, (a, ecc, inc, raan, argp, nu))
#ss = twobody.State(Sun, (r, v), time=Time.now())
#ss = twobody.State(Sun, (a, ecc, inc, raan, argp, nu), time=Time.now())
  1. If I receive a six-tuple assume it's (a, e, i, Ω, ω, ν). Also:
    1. a 2D array with last dimension = 6,
    2. a tuple with six consistent vectors.
  2. If I receive a tuple with two vectors assume they are (r, v). Also:
    1. a tuple with two three-tuples,
    2. a tuple with two consistent 2D arrays with last dimension = 3.
  3. For other cases, either provide some kind of input format parameter or ?.

In Python 3 I could get rid of the extra parentheses with this syntax:

def __init__(self, body, *values, time=DEFAULT_TIME)

In Python 2 I would have to use **kwargs.

Redundancy

Regarding which info will I store and return, ss.coe() stands for classical and ss.eqe() for equinoctial elements. If I want to forget about equinoctial, I can use ss.elements() for now. I have these options:

  1. Always return what was stored. Predictable, but beware of operations between states.
  2. Always store ν. Return ν, unless requested (for example, as a parameter of the function).
  3. Always store M. Return M, unless requested (for example, as a parameter of the function).

I would prefer not to store redundant information in the object, i.e. if COE are given, don't store also time of periapsis, mean longitude or r and v vectors. Inspired by Astropy Time objects, State can have a value (the one given by input) and then convert to other representations on the fly.

The implementation is quite smart:

https://github.com/astropy/astropy/blob/v0.3.x/astropy/time/core.py#L546

There are several subclasses of Time, each one holding a representation of the object, and overriding __getattr__ they manage to return the right one. It uses black magic though, iterating over the module locals():

https://github.com/astropy/astropy/blob/v0.3.x/astropy/time/core.py#L1652

Warning: Link above no longer valid.

Singular cases

While writing the implementation, accounting for all possible failures proved to be more difficult than expected (talking about singularities when using classical orbital elements).

Always asking for non-singular elements and handle only those is not acceptable, because they are far less intuitive and Keplerian elements work for most of the cases anyway.

Suppose r and v are introduced for a singular orbit: equatorial (no line of nodes) and/or circular (no periapsis). Then elements must be returned.

  1. Return Keplerian elements, with corresponding undefined ones (RAAN or argument of periapsis) set to zero and using the true anomaly to "fill" the angle. Always consistent.
  2. Return Keplerian elements, changing RAAN or argument of periapsis by longitude of periapsis, argument of latitude or mean longitude when needed.
  3. Return another set of elements, e.g. equinoctial elements.

I prefer option (1). This way something like ss.elements or ss.coe always means (a, ecc, inc, raan, argp, nu).

Other ideas

  • The idea of holding several states in a State object, similar to Time objects in Astropy, came up. This would be ideal for plotting for example, apart from the intermediate positions when performing maneuvers.