Additionnal states in ephemerides not reinitilialized and/or updated

Hello,
I use additonal states to store data during the simulation. The state is updated during the simulation thanks to event handlers. The propagation works well but when I try to build the ephemerides the additional state is not reinitialized… so the state is not the one during the simulation at this time.
I may use badly ephemerides, below is the code to reproduce the problem. Any idea of what I am doing wrong ?

package space.exotrail.exoops.numerical.orekit;

import java.io.IOException;

import org.hipparchus.ode.AbstractIntegrator;
import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
import org.hipparchus.util.FastMath;
import org.junit.Assert;
import org.junit.Test;
import org.orekit.errors.OrekitException;
import org.orekit.frames.Frame;
import org.orekit.frames.FramesFactory;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.PositionAngle;
import org.orekit.propagation.AdditionalStateProvider;
import org.orekit.propagation.BoundedPropagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.DateDetector;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.handlers.EventHandler;
import org.orekit.propagation.numerical.NumericalPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.time.TimeScalesFactory;

public class AdditionalStatesTest {

    public static final String STATE_NAME = "STATE";
    public static final int STATE_INDEX = 0;
    public static final int STATE_START = 10;
    public static final int STATE_END = -10;

    public class Handler implements EventHandler<EventDetector> {

        private StateProviderTest additionalStateProvider;

        public Handler (StateProviderTest additionalStateProvider) {
            this.additionalStateProvider = additionalStateProvider;
        }

        @Override
        public void init(final SpacecraftState s0, final AbsoluteDate t) {
            additionalStateProvider.setFirstPart(true);
        }

        @Override
        public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing)
                throws OrekitException {
            additionalStateProvider.setFirstPart(false);
            return Action.CONTINUE;
        }
    }

    public class StateProviderTest implements AdditionalStateProvider {

        public boolean firstPart = true;

        @Override
        public String getName() {
            return STATE_NAME;
        }

        @Override
        public double[] getAdditionalState(SpacecraftState state) throws OrekitException {
            if (isFirstPart()) {
                double[] ret = { STATE_START };
                return ret;
            } else {
                double[] ret = { STATE_END };
                return ret;
            }
        }

        public boolean isFirstPart() {
            return firstPart;
        }

        public void setFirstPart(boolean firstPart) {
            this.firstPart = firstPart;
        }

    }

    @Test
    public void TestAdditionalStates() throws OrekitException, IOException {

        // Initial orbit parameters
        double a = 24396159; // semi major axis in meters
        double e = 0.72831215; // eccentricity
        double i = FastMath.toRadians(7); // inclination
        double omega = FastMath.toRadians(180); // perigee argument
        double raan = FastMath.toRadians(261); // right ascension of ascending node
        double lM = 0; // mean anomaly

        // Inertial frame
        Frame inertialFrame = FramesFactory.getEME2000();

        // Initial date in UTC time scale
        TimeScale utc = TimeScalesFactory.getUTC();
        AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, utc);

        // gravitation coefficient
        double mu = 3.986004415e+14;

        // Orbit construction as Keplerian
        Orbit initialOrbit = new KeplerianOrbit(a, e, i, omega, raan, lM, PositionAngle.MEAN,
                inertialFrame, initialDate, mu);

        // Initialize state
        SpacecraftState initialState = new SpacecraftState(initialOrbit);

        // Numerical propagation with no perturbation (only Keplerian movement)
        // Using a very simple integrator with a fixed step: classical Runge-Kutta
        double stepSize = 10;  // the step is ten seconds
        AbstractIntegrator integrator = new ClassicalRungeKuttaIntegrator(stepSize);
        NumericalPropagator propagator = new NumericalPropagator(integrator);

        // Set the propagator to ephemeris mode
        propagator.setEphemerisMode();

        StateProviderTest stateProvider = new StateProviderTest();
        propagator.addEventDetector(new DateDetector(initialDate.shiftedBy(3000))
                .withHandler(new Handler(stateProvider)));
        propagator.addAdditionalStateProvider(stateProvider);

        propagator.setInitialState(initialState);

        // Propagation with storage of the results in an integrated ephemeris
        SpacecraftState firstPartState = propagator.propagate(initialDate.shiftedBy(2000));
        propagator.resetInitialState(initialState);
        SpacecraftState finalState = propagator.propagate(initialDate.shiftedBy(6000));
        BoundedPropagator ephemeris = propagator.getGeneratedEphemeris();

        double stateAfterPropagation = finalState.getAdditionalState(STATE_NAME)[STATE_INDEX];
        double stateInFirstPartOfPropagation = firstPartState
                .getAdditionalState(STATE_NAME)[STATE_INDEX];
        double stateInFirstPartOfEphemerides = ephemeris.propagate(initialDate.shiftedBy(1000))
                .getAdditionalState(STATE_NAME)[STATE_INDEX];
        double stateInSecondPartOfEphemerides = ephemeris.propagate(initialDate.shiftedBy(4000))
                .getAdditionalState(STATE_NAME)[STATE_INDEX];

        Assert.assertEquals(STATE_START, stateInFirstPartOfPropagation, 1e-12);
        Assert.assertEquals(STATE_END, stateAfterPropagation, 1e-12);
        Assert.assertEquals(STATE_START, stateInFirstPartOfEphemerides, 1e-12);
        Assert.assertEquals(STATE_END, stateInSecondPartOfEphemerides, 1e-12);

    }
}

Hi Mikael,

It seems your issue comes from the fact that the additional state’s value depends on an event detector.
When the bounded propagator is created it does not mirror the event detectors from the original propagator. I don’t know if it is a bug or an intended behaviour.
Since there is no more switching function for the additional state it is never changed in your bounded propagator.

A quick fix is to add your original detector into the bounded propagator.
After:
BoundedPropagator ephemeris = propagator.getGeneratedEphemeris();
Add your detector:
ephemeris.addEventDetector(new DateDetector(initialDate.shiftedBy(3000)) .withHandler(new Handler(stateProvider)));
With that your test passes.

If it is a bug in Orekit, a fix would be to add the original detectors after the bounded propagator is created.
After l.1073 in AbstractIntegratedPropagator.EphemerisModeHandler.handleStep, we would need to add:

//FIXME: Add event detectors
for (EventDetector detector : getEventsDetectors()) {
    ephemeris.addEventDetector(detector);
}

But I don’t know if this wouldn’t create unwanted side effects.
Maybe someone more experienced on the forum can tell us more about that.

Maxime

It is an intended behaviour. The ephemeris should be seen as something really static, like a dump of data taht could have been saved in a file and then reload on a different computer days later. The original objects (like attitude provider, events detectors, events handlers) are not stored in the dump.

Hi, I agree with the static view of ephemerides and I wouldn’t want to add all the detecttors to the ephemeride propagator. But then why the additionnal states are not stored ?

They should be stored, so I think you found a bug here.

I looked at the internal class AbstractIntegratedPropagator.EphemerisModeHandler which handles ephemeris generation, at the end of the handleStep method, in the part performed only for the last step, just before the propagation stops. In this part, there are two separate handlings of additional states:

  • states managed by additional equations (i.e. differential equations), they will be retrieved from
    the underlying integrator additional states
  • states labelled as “unmanaged”, only their initial values as present in the initial state will be retrieved

It now seems to me that we missed a third category: states manged by an analytical AdditionalStateProvider, which is your case. I think we considered that as we can’t dump an unknown additional state provider, we cannot store it in an ephemeris. If ephemerides were used only on the exact sample points, this would not be a problem, we could simply dump the value. However, ephemerides can be interpolated, and how do we interpolate states for which we don’t have any equation?

There may be two different answers to this:

  • if the ephemeris is indeed used whay we are still in the same process, and the AdditionalStateProvider
    instances are still available: then we could store a reference to them in the ephemeris and call it so it
    computes the additional state just as it would do in the original propagation
  • if the ephemeris was dumped on file and reloaded, then we could use a n points interpolation (n to
    be defined at loading time) from the sampled states.

Could you open a bug report for this on https://gitlab.orekit.org/orekit/orekit/issues

Once again Luc, thanks a lot for your help and your reactivity. I will open the issue on GitLab. With our way of using the additional state provider (depending on previous events), we would use the interpolation in all cases. gitlab issue : https://gitlab.orekit.org/orekit/orekit/issues/563