Interpolation complexity with Ephemeris or SpacecraftStateInterpolator

Hi Orekit Forum,

I tried to perform an interpolation with a SpacecraftStateInterpolator:

# Get blending propagator
blender_prop = KeplerianPropagator(initial_orbit)  # For example

# Get orbit blender
orbit_blender = OrbitBlender(blending_function, blender_prop, frame)

# Get state interpolator
state_interpolator = SpacecraftStateInterpolator(nb_interpolation, extrapolation_threshold, frame, orbit_blender, None, None, None, None)

# Interpolate state
interpolated_state = state_interpolator.interpolate(propagation_date, states_list)

And with an Ephemeris:

# Convert SpacecraftStateInterpolator to Ephemeris
ephemeris = Ephemeris(states_list, state_interpolator)

# Interpolate state
interpolated_state = ephemeris.propagate(propagation_date)

The returned interpolated_state is the same with the two methods, but the time complexity is much higher with the second one (takes twice as much time to compute).

I wonder why this is, and what I am not getting here.

Thanks for your help

Hello @Anosen and welcome to the Orekit forum !

What version of Orekit do you use ? I have run a benchmark using Orekit 12.2 and Ephemeris should be at least equivalent and can be even much faster when cache is relevant.

Solution

EDIT :
After checking several Orekit versions, i suppose that you are using Orekit 12.0 because using 12.0.1 fixes this performance issue (it was not normal indeed). I suggest you to update your Orekit dependency to at least 12.0.2.

Benchmark code

import org.hipparchus.analysis.polynomials.SmoothStepFactory;
import org.orekit.data.DataContext;
import org.orekit.data.DataProvider;
import org.orekit.data.DirectoryCrawler;
import org.orekit.frames.Frame;
import org.orekit.frames.FramesFactory;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.OrbitBlender;
import org.orekit.orbits.PositionAngleType;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.SpacecraftStateInterpolator;
import org.orekit.propagation.analytical.Ephemeris;
import org.orekit.propagation.analytical.KeplerianPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.Constants;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ForumInterpolatorVsEphemeris {
   public static void main(String[] args) {

      // Load Orekit data
      loadOrekitData();

      // Create some orbit
      final AbsoluteDate date  = new AbsoluteDate();
      final double       mu    = Constants.EIGEN5C_EARTH_MU;
      final Frame        frame = FramesFactory.getEME2000();
      final Orbit orbit =
            new KeplerianOrbit((6378 + 500), 0.001, 0.001, 0, 0, 0, PositionAngleType.MEAN, frame, date, mu);

      // Create list of states
      final List<SpacecraftState> states     = new ArrayList<>();
      final KeplerianPropagator   propagator = new KeplerianPropagator(orbit);
      propagator.setStepHandler(60, (states::add));
      propagator.propagate(date.shiftedBy(3600));

      // Create orbit blender
      final OrbitBlender orbitBlender = new OrbitBlender(SmoothStepFactory.getQuintic(), propagator, frame);

      // Create spacecraft state interpolator
      final SpacecraftStateInterpolator interpolator =
            new SpacecraftStateInterpolator(frame, orbitBlender,
                                            null, null,
                                            null, null);

      // Create ephemeris
      final Ephemeris ephemeris = new Ephemeris(states, interpolator);

      // Interpolate with both methods
      final AbsoluteDate interpolationDate = date.shiftedBy(270);

      final long start = System.currentTimeMillis();
      final int nbIter = 1000;
      for (int i = 0; i < nbIter; i++) {
         interpolator.interpolate(interpolationDate, states);
      }
      final long middle = System.currentTimeMillis();
      for (int i = 0; i < nbIter; i++) {
         ephemeris.propagate(interpolationDate);
      }
      final long stop = System.currentTimeMillis();

      System.out.format("Interpolator interpolate method : %s\n", middle - start);
      System.out.format("Ephemeris propagate method : %s\n", stop - middle);
   }

   private static void loadOrekitData() {
      final File rootFile   = new File(System.getProperty("user.home"));
      final File orekitData = new File(rootFile, "orekit-data");
      if (!orekitData.exists()) {
         System.out.format("Le fichier %s n'a pas été trouvé.%n", orekitData.getAbsolutePath());
      }
      final DataProvider dirCrawler = new DirectoryCrawler(orekitData);
      DataContext.getDefault().getDataProvidersManager().addProvider(dirCrawler);
   }
}

Cheers,
Vincent

Hello Vincent,

I should have mentionned earlier that I am currently running Python Wrapper on the 12.1.2 version, sorry for that.

I adapted your benchmark to Python and found the Ephemeris to be indeed faster, with the Wrapper too.

However my use case is a bit different because I do not propagate to the same date multiple times, but instead at increments of time, and it seems to have an impact.

Here is a more detailled example, in which the Ephemeris is slower than the SpacecraftStateInterpolator (by a factor of 2 in average) :

from config import OREKIT_DATA

import datetime
import orekit
from orekit.pyhelpers import setup_orekit_curdir

from java.util import ArrayList
from org.hipparchus.analysis.polynomials import SmoothStepFactory
from org.orekit.frames import FramesFactory
from org.orekit.orbits import OrbitBlender, PositionAngleType, KeplerianOrbit
from org.orekit.propagation import SpacecraftState, SpacecraftStateInterpolator, Propagator
from org.orekit.propagation.analytical import Ephemeris, KeplerianPropagator
from org.orekit.time import AbsoluteDate, DateComponents, TimeComponents, TimeScalesFactory
from org.orekit.utils import Constants
from org.orekit.propagation.sampling import PythonOrekitFixedStepHandler

class StateCollectorHandler(PythonOrekitFixedStepHandler):
    """Custom step handler to collect states into an ArrayList."""

    def __init__(self, states_list):
        self.states_list = states_list
        super(StateCollectorHandler, self).__init__()

    def init(self, spacecraftState: SpacecraftState, absoluteDate: AbsoluteDate, double: float):
        """Required by OrekitFixedStepHandler."""
        pass

    def handleStep(self, spacecraftState):
        # Add current state to the list
        self.states_list.add(spacecraftState)

    def finish(self, finalState):
        """Required by OrekitFixedStepHandler."""
        pass

def get_states(orbit, date, step, nb_steps):
    # Create list of states
    states = ArrayList().of_(SpacecraftState)
    propagator = Propagator.cast_(KeplerianPropagator(orbit))

    # Set up custom step handler
    step_handler = StateCollectorHandler(states)
    propagator.setStepHandler(step, step_handler)  # Use custom handler to add states at one day intervals
    propagator.propagate(date.shiftedBy(step * nb_steps))

    return states

def launch_test(launch_type):
    # Create some orbit
    date = AbsoluteDate(DateComponents(2001, 1, 1), TimeComponents(12, 0, 0.0), TimeScalesFactory.getUTC())
    mu = Constants.EIGEN5C_EARTH_MU
    frame = FramesFactory.getEME2000()
    orbit = KeplerianOrbit((6378.0 + 500.0), 0.001, 0.001, 0.0, 0.0, 0.0, PositionAngleType.MEAN, frame, date, mu)

    # Get states list
    nb_steps = 10
    states = get_states(orbit, date, 86400.0, 10)

    # Create new propagator
    propagator = Propagator.cast_(KeplerianPropagator(orbit))

    # Create orbit blender
    orbitBlender = OrbitBlender(SmoothStepFactory.getQuintic(), propagator, frame)

    # Create spacecraft state interpolator
    interpolator = SpacecraftStateInterpolator(2, 1.0e-3, frame, orbitBlender, None, None, None, None)

    if launch_type == 'interpolator':
        start = datetime.datetime.now()
        for i in range(nb_steps):
            interpolator.interpolate(date, states)
            date = date.shiftedBy(86400.0)
        # print(f"Interpolator interpolate method : {datetime.datetime.now() - start}")
        return datetime.datetime.now() - start

    elif launch_type == 'ephemeris':
        # Create ephemeris
        ephemeris = Ephemeris(states, interpolator)
        start = datetime.datetime.now()
        for i in range(nb_steps):
            ephemeris.propagate(date)
            date = date.shiftedBy(86400.0)
        # print(f"Ephemeris propagate method : {datetime.datetime.now() - start}")
        return datetime.datetime.now() - start


if __name__ == '__main__':
    vm = orekit.initVM()
    setup_orekit_curdir(filename=OREKIT_DATA)

    interp_times = []
    ephem_times = []
    for i in range(100):
        interp_times.append(launch_test('interpolator'))
        ephem_times.append(launch_test('ephemeris'))

    interp_avg_time = sum(interp_times, datetime.timedelta(0))/len(interp_times)
    ephem_avg_time = sum(ephem_times, datetime.timedelta(0))/len(ephem_times)

    print(f'Interpolator interpolate method average time : {interp_avg_time}')
    print(f'Ephemeris propagate method average time : {ephem_avg_time}')
Interpolator interpolate method average time : 0:00:00.000304
Ephemeris propagate method average time : 0:00:00.000597

Regards,
Grégoire

Hello @Anosen,

No worries, i knew that we had some performance issue in 12.0 so i’ve bet on that. I’ll take a look at it when i can.

Thank you for the additional information & code sample, it makes investigation much easier !

Cheers,
Vincent

1 Like

Hello @Anosen, i did not forget you :slight_smile: !

I notice in your code sample that you are creating both the interpolator and ephemeris in each “launch_test” call. This bias the result in favor of the interpolator as the ephemeris performs a consistency check on given states list when it is instanciated.

I slightly modified my Java version to use changing interpolation dates (provided below). For 30 interpolation over 30 days (1 each day), i get the following results:

Total time with interpolator interpolate method : 389 ms
Total time with ephemeris propagate method : 11 ms

The reason being that an Ephemeris will only send a limited list of states to its internal pinterpolator using a cache while the interpolator will process the whole list of states every time.

Benchmark Java code

import org.hipparchus.analysis.polynomials.SmoothStepFactory;
import org.orekit.data.DataContext;
import org.orekit.data.DataProvider;
import org.orekit.data.DirectoryCrawler;
import org.orekit.frames.Frame;
import org.orekit.frames.FramesFactory;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.OrbitBlender;
import org.orekit.orbits.PositionAngleType;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.SpacecraftStateInterpolator;
import org.orekit.propagation.analytical.Ephemeris;
import org.orekit.propagation.analytical.KeplerianPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.Constants;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ForumInterpolatorVsEphemeris {
   public static void main(String[] args) {

      // Load Orekit data
      loadOrekitData();

      // Create some orbit
      final AbsoluteDate date  = new AbsoluteDate();
      final double       mu    = Constants.EIGEN5C_EARTH_MU;
      final Frame        frame = FramesFactory.getEME2000();
      final Orbit orbit =
            new KeplerianOrbit((6378 + 500), 0.001, 0.001, 0, 0, 0, PositionAngleType.MEAN, frame, date, mu);

      // Create list of states
      final int                   nbOfDay    = 30;
      final List<SpacecraftState> states     = new ArrayList<>();
      final KeplerianPropagator   propagator = new KeplerianPropagator(orbit);
      propagator.setStepHandler(60, (states::add));
      propagator.propagate(date.shiftedBy(nbOfDay * Constants.JULIAN_DAY));

      // Create orbit blender
      final OrbitBlender orbitBlender = new OrbitBlender(SmoothStepFactory.getQuintic(), propagator, frame);

      // Create spacecraft state interpolator
      final SpacecraftStateInterpolator interpolator =
            new SpacecraftStateInterpolator(frame, orbitBlender,
                                            null, null,
                                            null, null);

      // Create ephemeris
      final Ephemeris ephemeris = new Ephemeris(states, interpolator);

      // Interpolate with both methods
      final long start = System.currentTimeMillis();
      for (int i = 0; i < nbOfDay; i++) {
         interpolator.interpolate(date.shiftedBy(i * Constants.JULIAN_DAY), states);
      }
      final long middle = System.currentTimeMillis();
      for (int i = 0; i < nbOfDay; i++) {
         ephemeris.propagate(date.shiftedBy(i * Constants.JULIAN_DAY));
      }
      final long stop = System.currentTimeMillis();

      System.out.format("Total time with interpolator interpolate method : %s ms\n", middle - start);
      System.out.format("Total time with ephemeris propagate method : %s ms\n", stop - middle);
   }

   private static void loadOrekitData() {
      final File rootFile   = new File(System.getProperty("user.home"));
      final File orekitData = new File(rootFile, "orekit-data");
      if (!orekitData.exists()) {
         System.out.format("Le fichier %s n'a pas été trouvé.%n", orekitData.getAbsolutePath());
      }
      final DataProvider dirCrawler = new DirectoryCrawler(orekitData);
      DataContext.getDefault().getDataProvidersManager().addProvider(dirCrawler);
   }
}