Regression with Field propagation + detection

Hello everyone,

I’ve started using Field propagation coupled with event detection with Orekit 12.0 and I’ve stumbled upon a couple of regressions. Basically, I’ve got a FieldNumericalPropagator with a custom ForceModel, whose intrinsic event detectors are instances of FieldDateDetector, let’s call the corresponding dates d_i.
I then propagate to several FieldAbsoludateDate equal to: d_i plus a Gradient variable with 0 constant term and non-zero derivatives. The original problem I came across was that with Orekit 12.0, the propagation would get stuck in a seemingly infinite loop at some point (not necessarily on the first date). This seems to happen in FieldDetectorBasedEventState from Hipparchus. I suspect it is due to one or several calls to isZero from CalculusFieldElement. However I couldn’t directly compare to version 2.3 as the class is new.

When I tried to extract a minimum reproduction example, I think I encountered another regression, but they might be linked.
The code is below, first for Orekit 12.0 and then for 11.3. The former leads to an MathRuntimeException while the latter runs fine.


@Test
    void regressionDetection() {
        final GradientField field = GradientField.getField(1);
        final FieldAbsoluteDate<Gradient> fieldEpoch = FieldAbsoluteDate.getArbitraryEpoch(field);
        final FieldPVCoordinates<Gradient> fieldPVCoordinates = new FieldPVCoordinates<>(
                new FieldVector3D<>(field, new Vector3D(10000.e3, 0, 0)),
                new FieldVector3D<>(field, new Vector3D(0, 7.5e3, 0)));
        final FieldCartesianOrbit<Gradient> fieldOrbit = new FieldCartesianOrbit<>(fieldPVCoordinates,
                FramesFactory.getGCRF(), fieldEpoch, field.getZero().add(Constants.EGM96_EARTH_MU));
        final ClassicalRungeKuttaFieldIntegrator<Gradient> fieldIntegrator = new ClassicalRungeKuttaFieldIntegrator<>(field,
                field.getZero().add(5.));
        final FieldNumericalPropagator<Gradient> fieldNumericalPropagator = new FieldNumericalPropagator<>(
                field, fieldIntegrator);
        fieldNumericalPropagator.resetInitialState(new FieldSpacecraftState<>(fieldOrbit));
        fieldNumericalPropagator.setResetAtEnd(true);

        final AbsoluteDate epoch = fieldEpoch.toAbsoluteDate();
        final TestForce testForce = new TestForce(epoch.shiftedBy(10.),
                epoch.shiftedBy(100.), epoch.shiftedBy(1000.));
        fieldNumericalPropagator.addForceModel(testForce);

        for (AbsoluteDate date: testForce.dates) {
            final FieldAbsoluteDate<Gradient> fieldDate = new FieldAbsoluteDate<>(field, date)
                    .shiftedBy(Gradient.variable(1, 0, 0.));
            fieldNumericalPropagator.propagate(fieldDate);
        }

    }

    private static class TestForce implements ForceModel {

        private final AbsoluteDate[] dates;

        TestForce (AbsoluteDate ...dates) {
            this.dates = dates;
        }

        @Override
        public boolean dependsOnPositionOnly() {
            return true;
        }

        @Override
        public Stream<EventDetector> getEventDetectors() {
            return Arrays.stream(dates).map(DateDetector::new)
                    .map(d -> d.withHandler((a, b, c) -> Action.RESET_DERIVATIVES));
        }

        @Override
        public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(Field<T> field) {
            return Arrays.stream(dates).map(date -> new FieldDateDetector<>(field, new FieldAbsoluteDate<>(field, date)))
                    .map(d -> d.withHandler((a, b, c) -> Action.RESET_DERIVATIVES));
        }

        @Override
        public Vector3D acceleration(SpacecraftState s, double[] parameters) {
            return Vector3D.ZERO;
        }

        @Override
        public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(FieldSpacecraftState<T> s, T[] parameters) {
            return FieldVector3D.getZero(s.getDate().getField());
        }

        @Override
        public List<ParameterDriver> getParametersDrivers() {
            return new ArrayList<>();
        }
    }

and


    @Test
    void regressionDetection() {
        final GradientField field = GradientField.getField(1);
        final FieldAbsoluteDate<Gradient> fieldEpoch = FieldAbsoluteDate.getArbitraryEpoch(field);
        final FieldPVCoordinates<Gradient> fieldPVCoordinates = new FieldPVCoordinates<>(
                new FieldVector3D<>(field, new Vector3D(10000.e3, 0, 0)),
                new FieldVector3D<>(field, new Vector3D(0, 7.5e3, 0)));
        final FieldCartesianOrbit<Gradient> fieldOrbit = new FieldCartesianOrbit<>(fieldPVCoordinates,
                FramesFactory.getGCRF(), fieldEpoch, field.getZero().add(Constants.EGM96_EARTH_MU));
        final ClassicalRungeKuttaFieldIntegrator<Gradient> fieldIntegrator = new ClassicalRungeKuttaFieldIntegrator<>(field,
                field.getZero().add(5.));
        final FieldNumericalPropagator<Gradient> fieldNumericalPropagator = new FieldNumericalPropagator<>(
                field, fieldIntegrator);
        fieldNumericalPropagator.resetInitialState(new FieldSpacecraftState<>(fieldOrbit));
        fieldNumericalPropagator.setResetAtEnd(true);

        final AbsoluteDate epoch = fieldEpoch.toAbsoluteDate();
        final TestForce testForce = new TestForce(epoch.shiftedBy(10.),
                epoch.shiftedBy(100.), epoch.shiftedBy(1000.));
        fieldNumericalPropagator.addForceModel(testForce);

        for (AbsoluteDate date: testForce.dates) {
            final FieldAbsoluteDate<Gradient> fieldDate = new FieldAbsoluteDate<>(field, date)
                    .shiftedBy(Gradient.variable(1, 0, 0.));
            fieldNumericalPropagator.propagate(fieldDate);
        }

    }

    private static class TestForce extends AbstractForceModel {

        private final AbsoluteDate[] dates;

        TestForce (AbsoluteDate ...dates) {
            this.dates = dates;
        }

        @Override
        public boolean dependsOnPositionOnly() {
            return true;
        }

        @Override
        public Stream<EventDetector> getEventsDetectors() {
            return Arrays.stream(dates).map(DateDetector::new)
                    .map(d -> d.withHandler((a, b, c) -> Action.RESET_DERIVATIVES));
        }

        @Override
        public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventsDetectors(Field<T> field) {
            return Arrays.stream(dates).map(date ->
                            new FieldDateDetector<>(field.getZero().add(AbstractDetector.DEFAULT_MAXCHECK),
                                    field.getZero().add(AbstractDetector.DEFAULT_THRESHOLD), new FieldAbsoluteDate<>(field, date)))
                    .map(d -> d.withHandler((a, b, c) -> Action.RESET_DERIVATIVES));
        }

        @Override
        public Vector3D acceleration(SpacecraftState s, double[] parameters) {
            return Vector3D.ZERO;
        }

        @Override
        public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(FieldSpacecraftState<T> s, T[] parameters) {
            return FieldVector3D.getZero(s.getDate().getField());
        }

        @Override
        public List<ParameterDriver> getParametersDrivers() {
            return new ArrayList<>();
        }
    }

Cheers,
Romain.

Regressions are always annoying. They shall be fixed as soon as possible. Could you open an issue with the attached example?

Thank you!
Bryan

I’ve opened issue 1281. Note that this is the second regression I mentioned.
So far I’ve not been able to make a small piece of code reproducing the first one.

Cheers,
Romain.

Update: if in FieldDetectorBasedEventState, one replaces isZero() with getReal() == 0. (and so on for negations), both regressions are gone.

Should I create a PR on Hipparchus with the modification? I don’t think that library gets patches, does it?

Cheers,
Romain.