Reproduction case:
from orbital import Position, Velocity, KeplerianElements, earth
R = Position(0, 2500000, -0.01)
V = Velocity(-16703.901013, 0, 0)
print(KeplerianElements.from_state_vector(R, V, body=earth))
Expected output:
KeplerianElements:
Semimajor axis (a) = 10000.000 km
Eccentricity (e) = 0.750000
Inclination (i) = 0.0 deg
Right ascension of the ascending node (raan) = 0.0 deg
Argument of perigee (arg_pe) = 90.0 deg
Mean anomaly at reference epoch (M0) = 0.0 deg
Period (T) = 2:45:52.014055
Reference epoch (ref_epoch) = J2000.000
Mean anomaly (M) = 0.0 deg
Time (t) = 0:00:00
Epoch (epoch) = J2000.000
Actual output:
KeplerianElements:
Semimajor axis (a) = 10000.000 km
Eccentricity (e) = 0.750000
Inclination (i) = 0.0 deg
Right ascension of the ascending node (raan) = 0.0 deg
Argument of perigee (arg_pe) = 270.0 deg
Mean anomaly at reference epoch (M0) = 0.0 deg
Period (T) = 2:45:52.014055
Reference epoch (ref_epoch) = J2000.000
Mean anomaly (M) = 0.0 deg
Time (t) = 0:00:00
Epoch (epoch) = J2000.000
(Note: arg_pe is 270°, it should be 90°)
Analysis:
The incorrectness of the output can be seen by changing the R.z of -0.01 to 0.0, which then changes arg_pe (correctly) to 90°. Such a small change in the position should not rotate the body 180° around its orbit. Alternatively, changing R.z to 1 (correctly) causes raan to become 180° and arg_pe 270°. Either of these would be fine - it doesn't matter what raan and arg_pe are when the inclination is very small, but they have to be 90° apart.
The problem is this code in utilities.elements_from_state_vector:
if ev.z < 0:
arg_pe = 2 * pi - arg_pe
This is executed whenever e is not close to 0, inverting arg_pe to compensate for the fact that raan also flips around when the body goes below the horizontal. That is the correct behaviour when i is also not close to 0, but when i is close to 0, raan is hard-coded to 0, so it is not correct to flip arg_pe. So the above code should only run in the "e is not small and i is also not small" case.
Note that if i is exactly 0, this isn't an issue, since ev.z will also be 0. The bug is when the orbit is very very slightly inclined, but i counts as close enough to 0.
I am working on a fix for this alongside several other bugs in from_state_vector and will send a PR soon.
Reproduction case:
Expected output:
Actual output:
(Note:
arg_peis 270°, it should be 90°)Analysis:
The incorrectness of the output can be seen by changing the
R.zof -0.01 to 0.0, which then changesarg_pe(correctly) to 90°. Such a small change in the position should not rotate the body 180° around its orbit. Alternatively, changingR.zto 1 (correctly) causesraanto become 180° andarg_pe270°. Either of these would be fine - it doesn't matter whatraanandarg_peare when the inclination is very small, but they have to be 90° apart.The problem is this code in
utilities.elements_from_state_vector:This is executed whenever
eis not close to 0, invertingarg_peto compensate for the fact thatraanalso flips around when the body goes below the horizontal. That is the correct behaviour wheniis also not close to 0, but wheniis close to 0,raanis hard-coded to 0, so it is not correct to fliparg_pe. So the above code should only run in the "eis not small andiis also not small" case.Note that if
iis exactly 0, this isn't an issue, sinceev.zwill also be 0. The bug is when the orbit is very very slightly inclined, buticounts as close enough to 0.I am working on a fix for this alongside several other bugs in
from_state_vectorand will send a PR soon.