AbsoluteDate issue

Hi all,

I have found what I believe to be a minor numerical parsing bug/issue in one of Orekit’s Absolute Date constructors. Specifically the one that takes in a Java.util.Date in it.

I have written a small reproducible test that demonstrates this issue:

public static boolean produceBug(){
      String testString = "2019-02-01T13:06:03.115";
      TimeScale timeScale=TimeScalesFactory.getUTC();
      
      DateTimeComponents expectedComponent = DateTimeComponents.parseDateTime(testString);
      AbsoluteDate expectedDate = new AbsoluteDate(expectedComponent, timeScale);

      ZonedDateTime actualComponent = LocalDateTime.from(DateTimeFormatter.ISO_DATE_TIME.parse(testString)).atZone(ZoneOffset.UTC);
      AbsoluteDate actualDate = new AbsoluteDate(java.sql.Timestamp.valueOf(actualComponent.toLocalDateTime()),timeScale);

      return expectedDate.equals(actualDate);
}
      

These dates should be identical, they represent the exact same date and time, however the comparison fails. This is because the absolute date’s “offset” is calculated to be subtly different. with the expected epoch being: 0.11500000000000021 (which is within expected double precision) and the actual epoch being: 0.11499999999796273. This precision loss was enough to cause two otherwise identical programs to produce different Chi squared values.

Tracking down the source of this issue lead to the TimeComponents constructor:

       public TimeComponents(final int secondInDayA, final double secondInDayB)
        throws OrekitIllegalArgumentException {

        // split the numbers as a whole number of seconds
        // and a fractional part between 0.0 (included) and 1.0 (excluded)
        final int carry         = (int) FastMath.floor(secondInDayB);
        int wholeSeconds        = secondInDayA + carry;
        final double fractional = secondInDayB - carry;

        // range check
        if (wholeSeconds < 0 || wholeSeconds > 86400) {
            // beware, 86400 must be allowed to cope with leap seconds introduction days
            throw new OrekitIllegalArgumentException(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER,
                                                     wholeSeconds + fractional);
        }

        // extract the time components
        hour           = wholeSeconds / 3600;
        wholeSeconds  -= 3600 * hour;
        minute         = wholeSeconds / 60;
        wholeSeconds  -= 60 * minute;
        second         = wholeSeconds + fractional;
        minutesFromUTC = 0;

    }

Specifically the line:

final double fractional = secondInDayB - carry;

introduces this minor error. I fixed in my personal situation by replacing this line with:

final double fractional = BigDecimal.valueOf(secondInDayB).subtract(BigDecimal.valueOf(carry)).doubleValue();

however I understand this line introduces additional dependencies and is a bit of a hotfix however this did now return the correct offset value for the constructed AbsoluteDate.

The issue isn’t consistent for every date, so was hard to spot, for example for one date I was parsing the two dates matched completely, but for others this same disparity was introduced.

I am not sure how severe this issue is however it seems like a bad idea to have one constructor return occasionally different results to the rest of them, and the fact this effects tangible results (even if to a very small degree) seems to be a problem.

Thanks for any input/corrections to this problem! It’s taken the better part of two weeks to get to this conclusion :slight_smile:

Hi Jimi,

Thanks for the test case and analysis. When I run it on my computer I see a time scale error: the Timestamp is created to be the time shown in testString in my local time zone instead of in UTC. Changing the timestamp creation to Timestamp.from(actualComponent.toInstant()) I was able to reproduce the behavior you see.

The difference in the created dates is ~ 2 femtoseconds (2e-12 seconds). Considering that all the Java date types you use (LocalDateTime, ZonedDateTime, Timestamp) only have nanosecond precision the observed femtosecond accuracy seems to be better than what you could expect. At this point it seems like it’s not a bug to me. If you need accurate time my recommendation is to not use any of the date/time classes in the standard Java library as they are not designed to be accurate.

What are you doing that needs sub-femtosecond accuracy?

Regards,
Evan

Hi Evan,

Sorry I didn’t think to check that before posting it!

The difference is indeed tiny, and in 99.99999% of circumstances it would never be an issue, the problem only arose as two identical programs (one parsing in .itc files with a custom timestamp that was being parsed using a custom Java SimpleDateFormat to get a Date and then put into an Orekit AbsoluteDate. The other program parsed in a Json created by this first program and ran a fit on the data in the file) were giving different Chi Squared values (of relative order 1E-6).

While I do agree it is a minute difference and far smaller than the precision of Java.util.Date, it was still Orekit causing the numerical difference, and personally I believe the fact what is being parsed in is lacks precision isn’t reason to let smaller precision losses slide. However I also appreciate there isn’t an easy fix that aligns with Orekit’s library methodology. Maybe just adding a JavaDoc comment to the constructor noting this for the 0.000001%?

Regards,
Jimi

Hi,

I think I have found a way to fix this. In fact, there TimeComponents can be built from either the number of seconds in day as a double, or using an int for the whole seconds and a double for the rest. The fis constructor calls the second one by setting the integer number to zero and everything in the second number. However, in your case as we start from a long we can already extract the integer number without loss of precision and directly call the second constructor.

I will try it in the upcoming minutes.

I have fixed this directly in the orekit-10.0 release branch.

Note however that the AbsoluteDate constructor used here see the input as a java.util.Date only, not as a java.sql.Timestamp. This implies that as long as the fractional part is a whole number of milliseconds, it will be OK, but the AbsoluteDate constructor does not call the getNanos method, so it will drop the part between milliseconds and nanoseconds.

2 Likes

Thanks Luc and Evan!
I wasn’t too fussed about sub millisecond precision as all timestamps given only go to milliseconds anyway, I just wanted whole number of milliseconds to be parsed identically which you have now done!

Thank you again,
Jimi.