I’ve long wondered about the loop in KeplerianPropagator::propagateOrbit
and, in particular, the comment -
// we use a loop here to compensate for very small date shifts error
// that occur with long propagation time
- that the loop is required due to loss of precision with long propagation durations, but I don’t think that this is accurate. (This is present in both v12 and v13, at least.)
I’m surprised this loop exists at all, as it should be the responsibility of the shiftedBy
impl.s to ensure that the shift
worked as intended. And I don’t think I’ve seen equivalent loops in any other propagation.
I digress: black-box testing and debugging (see below for black-box testing/debugging details) suggests that, in fact, this is/seems to be due to the AbsoluteDate(final AbsoluteDate since, final double elapsedDuration)
constructor (v12.2).
Moreover, inspection of the Orbit::shiftedBy
implementations - checked KeplerianOrbit
, CartesianOrbit
, CircularOrbit
, and EquinoctialOrbit
- reveals no inappropriate date manipulation, so I think it can only be due to the aforementioned constructor.
I see that in v13 AbsoluteDate
is now backed by TimeOffset
which strictly uses long
s to keep track of everything (seconds and attoseconds, IIRC) - which is great to see - so I wonder whether this issue is still present.
For v <= 12
, I’m wondering whether the AbsoluteDate
constructor should be updated with a loop like we see in propagateOrbit
.
Expand for full AbsoluteDate black-box testing procedure
Using
final TimeScale scale1 = ...;
final TimeScale scale2 = ...;
final int y = 2000;
final AbsoluteDate t1 = new AbsoluteDate(new DateComponents(y, 1, 1),
TimeComponents.H12,
scale1); // J2000 defn if scale1 == TT
System.out.println("start");
for (int dy = 0; dy < 100; dy++) {
for (int m = 1; m <= 12; m++) {
final AbsoluteDate t2 = new AbsoluteDate(new DateComponents(2000 + dy, m, 1),
TimeComponents.H12,
scale2);
final AbsoluteDate shifted = t1.shiftedBy(t2.durationFrom(t1));
final double error = t2.durationFrom(shifted);
if (error != 0.0) {
System.out.format("[%s, %s], [%3d, %3d]: % e%n", scale1, scale2, dy, m, error);
}
}
}
System.out.println("end");
with two TimeScale
s that have a constant offset - or using two TimeScales
that are equal - error == 0
for each iteration.
However, using a pair like [TT, UTC]
:
final TimeScale scale1 = TimeScalesFactory.getTT();
final TimeScale scale2 = TimeScalesFactory.getUTC();
We see the first non-zero error in month 2
, and it’s non-zero for each moth from then on:
start
[TT, UTC], [ 0, 2]: 1.080309e-10
[TT, UTC], [ 0, 3]: -3.576304e-10
...
[TT, UTC], [ 4, 5]: 1.454353e-08
...
[TT, UTC], [ 8, 8]: -1.525879e-08
...
end
Of course my first thought is that this is really due to something w.r.t. leap seconds, and testing with TT
and TAI
(constant offset and zero offset) more-or-less confirmed this, as there is never a non-zero difference.
Adding a few extra pieces to the if (error != 0.0)
block for easier debugging:
final var dt = t2.durationFrom(t1);
final var sd = t1.shiftedBy(dt);
t2.durationFrom(sd);
and stepping in, we arrive at the aforementioned AbsoluteDate
constructor, where the value of regularOffset
always matches - in magnitude but not necessarily sign - the error in the output.