AbstractIntegratedPropagator.java

  1. /* Copyright 2002-2022 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.propagation.integration;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.LinkedList;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Queue;

  27. import org.hipparchus.exception.MathRuntimeException;
  28. import org.hipparchus.ode.DenseOutputModel;
  29. import org.hipparchus.ode.ExpandableODE;
  30. import org.hipparchus.ode.ODEIntegrator;
  31. import org.hipparchus.ode.ODEState;
  32. import org.hipparchus.ode.ODEStateAndDerivative;
  33. import org.hipparchus.ode.OrdinaryDifferentialEquation;
  34. import org.hipparchus.ode.SecondaryODE;
  35. import org.hipparchus.ode.events.Action;
  36. import org.hipparchus.ode.events.EventHandlerConfiguration;
  37. import org.hipparchus.ode.events.ODEEventHandler;
  38. import org.hipparchus.ode.sampling.AbstractODEStateInterpolator;
  39. import org.hipparchus.ode.sampling.ODEStateInterpolator;
  40. import org.hipparchus.ode.sampling.ODEStepHandler;
  41. import org.hipparchus.util.Precision;
  42. import org.orekit.attitudes.AttitudeProvider;
  43. import org.orekit.errors.OrekitException;
  44. import org.orekit.errors.OrekitInternalError;
  45. import org.orekit.errors.OrekitMessages;
  46. import org.orekit.frames.Frame;
  47. import org.orekit.orbits.OrbitType;
  48. import org.orekit.orbits.PositionAngle;
  49. import org.orekit.propagation.AbstractPropagator;
  50. import org.orekit.propagation.BoundedPropagator;
  51. import org.orekit.propagation.EphemerisGenerator;
  52. import org.orekit.propagation.PropagationType;
  53. import org.orekit.propagation.SpacecraftState;
  54. import org.orekit.propagation.events.EventDetector;
  55. import org.orekit.propagation.sampling.OrekitStepHandler;
  56. import org.orekit.propagation.sampling.OrekitStepInterpolator;
  57. import org.orekit.time.AbsoluteDate;
  58. import org.orekit.utils.DoubleArrayDictionary;


  59. /** Common handling of {@link org.orekit.propagation.Propagator Propagator}
  60.  *  methods for both numerical and semi-analytical propagators.
  61.  *  @author Luc Maisonobe
  62.  */
  63. public abstract class AbstractIntegratedPropagator extends AbstractPropagator {

  64.     /** Internal name used for complete secondary state dimension.
  65.      * @since 11.1
  66.      */
  67.     private static final String SECONDARY_DIMENSION = "Orekit-secondary-dimension";

  68.     /** Event detectors not related to force models. */
  69.     private final List<EventDetector> detectors;

  70.     /** Step handlers dedicated to ephemeris generation. */
  71.     private final List<StoringStepHandler> ephemerisGenerators;

  72.     /** Integrator selected by the user for the orbital extrapolation process. */
  73.     private final ODEIntegrator integrator;

  74.     /** Offsets of secondary states managed by {@link AdditionalEquations}.
  75.      * @since 11.1
  76.      */
  77.     private final Map<String, Integer> secondaryOffsets;

  78.     /** Additional derivatives providers.
  79.      * @since 11.1
  80.      */
  81.     private List<AdditionalDerivativesProvider> additionalDerivativesProviders;

  82.     /** Map of secondary equation offset in main
  83.     /** Counter for differential equations calls. */
  84.     private int calls;

  85.     /** Mapper between raw double components and space flight dynamics objects. */
  86.     private StateMapper stateMapper;

  87.     /** Flag for resetting the state at end of propagation. */
  88.     private boolean resetAtEnd;

  89.     /** Type of orbit to output (mean or osculating) <br/>
  90.      * <p>
  91.      * This is used only in the case of semianalitical propagators where there is a clear separation between
  92.      * mean and short periodic elements. It is ignored by the Numerical propagator.
  93.      * </p>
  94.      */
  95.     private PropagationType propagationType;

  96.     /** Build a new instance.
  97.      * @param integrator numerical integrator to use for propagation.
  98.      * @param propagationType type of orbit to output (mean or osculating).
  99.      */
  100.     protected AbstractIntegratedPropagator(final ODEIntegrator integrator, final PropagationType propagationType) {
  101.         detectors                      = new ArrayList<>();
  102.         ephemerisGenerators            = new ArrayList<>();
  103.         additionalDerivativesProviders = new ArrayList<>();
  104.         this.secondaryOffsets          = new HashMap<>();
  105.         this.integrator                = integrator;
  106.         this.propagationType           = propagationType;
  107.         this.resetAtEnd                = true;
  108.     }

  109.     /** Allow/disallow resetting the initial state at end of propagation.
  110.      * <p>
  111.      * By default, at the end of the propagation, the propagator resets the initial state
  112.      * to the final state, thus allowing a new propagation to be started from there without
  113.      * recomputing the part already performed. Calling this method with {@code resetAtEnd} set
  114.      * to false changes prevents such reset.
  115.      * </p>
  116.      * @param resetAtEnd if true, at end of each propagation, the {@link
  117.      * #getInitialState() initial state} will be reset to the final state of
  118.      * the propagation, otherwise the initial state will be preserved
  119.      * @since 9.0
  120.      */
  121.     public void setResetAtEnd(final boolean resetAtEnd) {
  122.         this.resetAtEnd = resetAtEnd;
  123.     }

  124.     /** Initialize the mapper. */
  125.     protected void initMapper() {
  126.         stateMapper = createMapper(null, Double.NaN, null, null, null, null);
  127.     }

  128.     /**  {@inheritDoc} */
  129.     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
  130.         super.setAttitudeProvider(attitudeProvider);
  131.         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
  132.                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
  133.                                    attitudeProvider, stateMapper.getFrame());
  134.     }

  135.     /** Set propagation orbit type.
  136.      * @param orbitType orbit type to use for propagation, null for
  137.      * propagating using {@link org.orekit.utils.AbsolutePVCoordinates AbsolutePVCoordinates}
  138.      * rather than {@link org.orekit.orbits Orbit}
  139.      */
  140.     protected void setOrbitType(final OrbitType orbitType) {
  141.         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
  142.                                    orbitType, stateMapper.getPositionAngleType(),
  143.                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
  144.     }

  145.     /** Get propagation parameter type.
  146.      * @return orbit type used for propagation, null for
  147.      * propagating using {@link org.orekit.utils.AbsolutePVCoordinates AbsolutePVCoordinates}
  148.      * rather than {@link org.orekit.orbits Orbit}
  149.      */
  150.     protected OrbitType getOrbitType() {
  151.         return stateMapper.getOrbitType();
  152.     }

  153.     /** Check if only the mean elements should be used in a semianalitical propagation.
  154.      * @return {@link PropagationType MEAN} if only mean elements have to be used or
  155.      *         {@link PropagationType OSCULATING} if osculating elements have to be also used.
  156.      * @deprecated as of 11.1, replaced by {@link #getPropagationType()}
  157.      */
  158.     @Deprecated
  159.     protected PropagationType isMeanOrbit() {
  160.         return getPropagationType();
  161.     }

  162.     /** Get the propagation type.
  163.      * @return propagation type.
  164.      * @since 11.1
  165.      */
  166.     public PropagationType getPropagationType() {
  167.         return propagationType;
  168.     }

  169.     /** Set position angle type.
  170.      * <p>
  171.      * The position parameter type is meaningful only if {@link
  172.      * #getOrbitType() propagation orbit type}
  173.      * support it. As an example, it is not meaningful for propagation
  174.      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
  175.      * </p>
  176.      * @param positionAngleType angle type to use for propagation
  177.      */
  178.     protected void setPositionAngleType(final PositionAngle positionAngleType) {
  179.         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
  180.                                    stateMapper.getOrbitType(), positionAngleType,
  181.                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
  182.     }

  183.     /** Get propagation parameter type.
  184.      * @return angle type to use for propagation
  185.      */
  186.     protected PositionAngle getPositionAngleType() {
  187.         return stateMapper.getPositionAngleType();
  188.     }

  189.     /** Set the central attraction coefficient μ.
  190.      * @param mu central attraction coefficient (m³/s²)
  191.      */
  192.     public void setMu(final double mu) {
  193.         stateMapper = createMapper(stateMapper.getReferenceDate(), mu,
  194.                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
  195.                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
  196.     }

  197.     /** Get the central attraction coefficient μ.
  198.      * @return mu central attraction coefficient (m³/s²)
  199.      * @see #setMu(double)
  200.      */
  201.     public double getMu() {
  202.         return stateMapper.getMu();
  203.     }

  204.     /** Get the number of calls to the differential equations computation method.
  205.      * <p>The number of calls is reset each time the {@link #propagate(AbsoluteDate)}
  206.      * method is called.</p>
  207.      * @return number of calls to the differential equations computation method
  208.      */
  209.     public int getCalls() {
  210.         return calls;
  211.     }

  212.     /** {@inheritDoc} */
  213.     @Override
  214.     public boolean isAdditionalStateManaged(final String name) {

  215.         // first look at already integrated states
  216.         if (super.isAdditionalStateManaged(name)) {
  217.             return true;
  218.         }

  219.         // then look at states we integrate ourselves
  220.         for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
  221.             if (provider.getName().equals(name)) {
  222.                 return true;
  223.             }
  224.         }

  225.         return false;
  226.     }

  227.     /** {@inheritDoc} */
  228.     @Override
  229.     public String[] getManagedAdditionalStates() {
  230.         final String[] alreadyIntegrated = super.getManagedAdditionalStates();
  231.         final String[] managed = new String[alreadyIntegrated.length + additionalDerivativesProviders.size()];
  232.         System.arraycopy(alreadyIntegrated, 0, managed, 0, alreadyIntegrated.length);
  233.         for (int i = 0; i < additionalDerivativesProviders.size(); ++i) {
  234.             managed[i + alreadyIntegrated.length] = additionalDerivativesProviders.get(i).getName();
  235.         }
  236.         return managed;
  237.     }

  238.     /** Add a set of user-specified equations to be integrated along with the orbit propagation.
  239.      * @param additional additional equations
  240.      * @deprecated as of 11.1, replaced by {@link #addAdditionalDerivativesProvider(AdditionalDerivativesProvider)}
  241.      */
  242.     @Deprecated
  243.     public void addAdditionalEquations(final AdditionalEquations additional) {
  244.         addAdditionalDerivativesProvider(new AdditionalEquationsAdapter(additional, this::getInitialState));
  245.     }

  246.     /** Add a provider for user-specified state derivatives to be integrated along with the orbit propagation.
  247.      * @param provider provider for additional derivatives
  248.      * @see #addAdditionalStateProvider(org.orekit.propagation.AdditionalStateProvider)
  249.      * @since 11.1
  250.      */
  251.     public void addAdditionalDerivativesProvider(final AdditionalDerivativesProvider provider) {

  252.         // check if the name is already used
  253.         if (isAdditionalStateManaged(provider.getName())) {
  254.             // these derivatives are already registered, complain
  255.             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_NAME_ALREADY_IN_USE,
  256.                                       provider.getName());
  257.         }

  258.         // this is really a new set of derivatives, add it
  259.         additionalDerivativesProviders.add(provider);

  260.         secondaryOffsets.clear();

  261.     }

  262.     /** Get an unmodifiable list of providers for additional derivatives.
  263.      * @return providers for the additional derivatives
  264.      * @since 11.1
  265.      */
  266.     public List<AdditionalDerivativesProvider> getAdditionalDerivativesProviders() {
  267.         return Collections.unmodifiableList(additionalDerivativesProviders);
  268.     }

  269.     /** {@inheritDoc} */
  270.     public void addEventDetector(final EventDetector detector) {
  271.         detectors.add(detector);
  272.     }

  273.     /** {@inheritDoc} */
  274.     public Collection<EventDetector> getEventsDetectors() {
  275.         return Collections.unmodifiableCollection(detectors);
  276.     }

  277.     /** {@inheritDoc} */
  278.     public void clearEventsDetectors() {
  279.         detectors.clear();
  280.     }

  281.     /** Set up all user defined event detectors.
  282.      */
  283.     protected void setUpUserEventDetectors() {
  284.         for (final EventDetector detector : detectors) {
  285.             setUpEventDetector(integrator, detector);
  286.         }
  287.     }

  288.     /** Wrap an Orekit event detector and register it to the integrator.
  289.      * @param integ integrator into which event detector should be registered
  290.      * @param detector event detector to wrap
  291.      */
  292.     protected void setUpEventDetector(final ODEIntegrator integ, final EventDetector detector) {
  293.         integ.addEventHandler(new AdaptedEventDetector(detector),
  294.                               detector.getMaxCheckInterval(),
  295.                               detector.getThreshold(),
  296.                               detector.getMaxIterationCount());
  297.     }

  298.     /** {@inheritDoc} */
  299.     @Override
  300.     public EphemerisGenerator getEphemerisGenerator() {
  301.         final StoringStepHandler storingHandler = new StoringStepHandler();
  302.         ephemerisGenerators.add(storingHandler);
  303.         return storingHandler;
  304.     }

  305.     /** Create a mapper between raw double components and spacecraft state.
  306.     /** Simple constructor.
  307.      * <p>
  308.      * The position parameter type is meaningful only if {@link
  309.      * #getOrbitType() propagation orbit type}
  310.      * support it. As an example, it is not meaningful for propagation
  311.      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
  312.      * </p>
  313.      * @param referenceDate reference date
  314.      * @param mu central attraction coefficient (m³/s²)
  315.      * @param orbitType orbit type to use for mapping
  316.      * @param positionAngleType angle type to use for propagation
  317.      * @param attitudeProvider attitude provider
  318.      * @param frame inertial frame
  319.      * @return new mapper
  320.      */
  321.     protected abstract StateMapper createMapper(AbsoluteDate referenceDate, double mu,
  322.                                                 OrbitType orbitType, PositionAngle positionAngleType,
  323.                                                 AttitudeProvider attitudeProvider, Frame frame);

  324.     /** Get the differential equations to integrate (for main state only).
  325.      * @param integ numerical integrator to use for propagation.
  326.      * @return differential equations for main state
  327.      */
  328.     protected abstract MainStateEquations getMainStateEquations(ODEIntegrator integ);

  329.     /** {@inheritDoc} */
  330.     public SpacecraftState propagate(final AbsoluteDate target) {
  331.         if (getStartDate() == null) {
  332.             if (getInitialState() == null) {
  333.                 throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
  334.             }
  335.             setStartDate(getInitialState().getDate());
  336.         }
  337.         return propagate(getStartDate(), target);
  338.     }

  339.     /** {@inheritDoc} */
  340.     public SpacecraftState propagate(final AbsoluteDate tStart, final AbsoluteDate tEnd) {

  341.         if (getInitialState() == null) {
  342.             throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
  343.         }

  344.         // make sure the integrator will be reset properly even if we change its events handlers and step handlers
  345.         try (IntegratorResetter resetter = new IntegratorResetter(integrator)) {

  346.             // prepare handling of STM and Jacobian matrices
  347.             setUpStmAndJacobianGenerators();

  348.             // Initialize additional states
  349.             initializeAdditionalStates(tEnd);

  350.             if (!tStart.equals(getInitialState().getDate())) {
  351.                 // if propagation start date is not initial date,
  352.                 // propagate from initial to start date without event detection
  353.                 try (IntegratorResetter startResetter = new IntegratorResetter(integrator)) {
  354.                     integrateDynamics(tStart);
  355.                 }
  356.             }

  357.             // set up events added by user
  358.             setUpUserEventDetectors();

  359.             // set up step handlers
  360.             for (final OrekitStepHandler handler : getMultiplexer().getHandlers()) {
  361.                 integrator.addStepHandler(new AdaptedStepHandler(handler));
  362.             }
  363.             for (final StoringStepHandler generator : ephemerisGenerators) {
  364.                 generator.setEndDate(tEnd);
  365.                 integrator.addStepHandler(generator);
  366.             }

  367.             // propagate from start date to end date with event detection
  368.             final SpacecraftState finalState = integrateDynamics(tEnd);

  369.             return finalState;

  370.         }

  371.     }

  372.     /** Set up State Transition Matrix and Jacobian matrix handling.
  373.      * @since 11.1
  374.      */
  375.     protected void setUpStmAndJacobianGenerators() {
  376.         // nothing to do by default
  377.     }

  378.     /** Propagation with or without event detection.
  379.      * @param tEnd target date to which orbit should be propagated
  380.      * @return state at end of propagation
  381.      */
  382.     private SpacecraftState integrateDynamics(final AbsoluteDate tEnd) {
  383.         try {

  384.             initializePropagation();

  385.             if (getInitialState().getDate().equals(tEnd)) {
  386.                 // don't extrapolate
  387.                 return getInitialState();
  388.             }

  389.             // space dynamics view
  390.             stateMapper = createMapper(getInitialState().getDate(), stateMapper.getMu(),
  391.                                        stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
  392.                                        stateMapper.getAttitudeProvider(), getInitialState().getFrame());


  393.             if (Double.isNaN(getMu())) {
  394.                 setMu(getInitialState().getMu());
  395.             }

  396.             if (getInitialState().getMass() <= 0.0) {
  397.                 throw new OrekitException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE,
  398.                                           getInitialState().getMass());
  399.             }

  400.             // convert space flight dynamics API to math API
  401.             final SpacecraftState initialIntegrationState = getInitialIntegrationState();
  402.             final ODEState mathInitialState = createInitialState(initialIntegrationState);
  403.             final ExpandableODE mathODE = createODE(integrator, mathInitialState);

  404.             // mathematical integration
  405.             final ODEStateAndDerivative mathFinalState;
  406.             beforeIntegration(initialIntegrationState, tEnd);
  407.             mathFinalState = integrator.integrate(mathODE, mathInitialState,
  408.                                                   tEnd.durationFrom(getInitialState().getDate()));
  409.             afterIntegration();

  410.             // get final state
  411.             SpacecraftState finalState =
  412.                             stateMapper.mapArrayToState(stateMapper.mapDoubleToDate(mathFinalState.getTime(),
  413.                                                                                     tEnd),
  414.                                                         mathFinalState.getPrimaryState(),
  415.                                                         mathFinalState.getPrimaryDerivative(),
  416.                                                         propagationType);

  417.             if (!additionalDerivativesProviders.isEmpty()) {
  418.                 final double[] secondary            = mathFinalState.getSecondaryState(1);
  419.                 final double[] secondaryDerivatives = mathFinalState.getSecondaryDerivative(1);
  420.                 for (AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
  421.                     final String   name        = provider.getName();
  422.                     final int      offset      = secondaryOffsets.get(name);
  423.                     final int      dimension   = provider.getDimension();
  424.                     finalState = finalState.
  425.                                  addAdditionalState(name, Arrays.copyOfRange(secondary, offset, offset + dimension)).
  426.                                  addAdditionalStateDerivative(name, Arrays.copyOfRange(secondaryDerivatives, offset, offset + dimension));
  427.                 }
  428.             }
  429.             finalState = updateAdditionalStates(finalState);

  430.             if (resetAtEnd) {
  431.                 resetInitialState(finalState);
  432.                 setStartDate(finalState.getDate());
  433.             }

  434.             return finalState;

  435.         } catch (MathRuntimeException mre) {
  436.             throw OrekitException.unwrap(mre);
  437.         }
  438.     }

  439.     /** Get the initial state for integration.
  440.      * @return initial state for integration
  441.      */
  442.     protected SpacecraftState getInitialIntegrationState() {
  443.         return getInitialState();
  444.     }

  445.     /** Create an initial state.
  446.      * @param initialState initial state in flight dynamics world
  447.      * @return initial state in mathematics world
  448.      */
  449.     private ODEState createInitialState(final SpacecraftState initialState) {

  450.         // retrieve initial state
  451.         final double[] primary = new double[getBasicDimension()];
  452.         stateMapper.mapStateToArray(initialState, primary, null);

  453.         if (secondaryOffsets.isEmpty()) {
  454.             // compute dimension of the secondary state
  455.             int offset = 0;
  456.             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
  457.                 secondaryOffsets.put(provider.getName(), offset);
  458.                 offset += provider.getDimension();
  459.             }
  460.             secondaryOffsets.put(SECONDARY_DIMENSION, offset);
  461.         }

  462.         return new ODEState(0.0, primary, secondary(initialState));

  463.     }

  464.     /** Create secondary state.
  465.      * @param state spacecraft state
  466.      * @return secondary state
  467.      * @since 11.1
  468.      */
  469.     private double[][] secondary(final SpacecraftState state) {

  470.         if (secondaryOffsets.isEmpty()) {
  471.             return null;
  472.         }

  473.         final double[][] secondary = new double[1][secondaryOffsets.get(SECONDARY_DIMENSION)];
  474.         for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
  475.             final String   name       = provider.getName();
  476.             final int      offset     = secondaryOffsets.get(name);
  477.             final double[] additional = state.getAdditionalState(name);
  478.             System.arraycopy(additional, 0, secondary[0], offset, additional.length);
  479.         }

  480.         return secondary;

  481.     }

  482.     /** Create secondary state derivative.
  483.      * @param state spacecraft state
  484.      * @return secondary state derivative
  485.      * @since 11.1
  486.      */
  487.     private double[][] secondaryDerivative(final SpacecraftState state) {

  488.         if (secondaryOffsets.isEmpty()) {
  489.             return null;
  490.         }

  491.         final double[][] secondaryDerivative = new double[1][secondaryOffsets.get(SECONDARY_DIMENSION)];
  492.         for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
  493.             final String   name       = provider.getName();
  494.             final int      offset     = secondaryOffsets.get(name);
  495.             final double[] additionalDerivative = state.getAdditionalStateDerivative(name);
  496.             System.arraycopy(additionalDerivative, 0, secondaryDerivative[0], offset, additionalDerivative.length);
  497.         }

  498.         return secondaryDerivative;

  499.     }

  500.     /** Create an ODE with all equations.
  501.      * @param integ numerical integrator to use for propagation.
  502.      * @param mathInitialState initial state
  503.      * @return a new ode
  504.      */
  505.     private ExpandableODE createODE(final ODEIntegrator integ,
  506.                                     final ODEState mathInitialState) {

  507.         final ExpandableODE ode =
  508.                 new ExpandableODE(new ConvertedMainStateEquations(getMainStateEquations(integ)));

  509.         // secondary part of the ODE
  510.         if (!additionalDerivativesProviders.isEmpty()) {
  511.             ode.addSecondaryEquations(new ConvertedSecondaryStateEquations());
  512.         }

  513.         return ode;

  514.     }

  515.     /** Method called just before integration.
  516.      * <p>
  517.      * The default implementation does nothing, it may be specialized in subclasses.
  518.      * </p>
  519.      * @param initialState initial state
  520.      * @param tEnd target date at which state should be propagated
  521.      */
  522.     protected void beforeIntegration(final SpacecraftState initialState,
  523.                                      final AbsoluteDate tEnd) {
  524.         // do nothing by default
  525.     }

  526.     /** Method called just after integration.
  527.      * <p>
  528.      * The default implementation does nothing, it may be specialized in subclasses.
  529.      * </p>
  530.      */
  531.     protected void afterIntegration() {
  532.         // do nothing by default
  533.     }

  534.     /** Get state vector dimension without additional parameters.
  535.      * @return state vector dimension without additional parameters.
  536.      */
  537.     public int getBasicDimension() {
  538.         return 7;
  539.     }

  540.     /** Get the integrator used by the propagator.
  541.      * @return the integrator.
  542.      */
  543.     protected ODEIntegrator getIntegrator() {
  544.         return integrator;
  545.     }

  546.     /** Convert a state from mathematical world to space flight dynamics world.
  547.      * @param os mathematical state
  548.      * @return space flight dynamics state
  549.      */
  550.     private SpacecraftState convert(final ODEStateAndDerivative os) {

  551.         SpacecraftState s = stateMapper.mapArrayToState(os.getTime(),
  552.                                                         os.getPrimaryState(),
  553.                                                         os.getPrimaryDerivative(),
  554.                                                         propagationType);
  555.         if (os.getNumberOfSecondaryStates() > 0) {
  556.             final double[] secondary           = os.getSecondaryState(1);
  557.             final double[] secondaryDerivative = os.getSecondaryDerivative(1);
  558.             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
  559.                 final String name      = provider.getName();
  560.                 final int    offset    = secondaryOffsets.get(name);
  561.                 final int    dimension = provider.getDimension();
  562.                 s = s.addAdditionalState(name, Arrays.copyOfRange(secondary, offset, offset + dimension));
  563.                 s = s.addAdditionalStateDerivative(name, Arrays.copyOfRange(secondaryDerivative, offset, offset + dimension));
  564.             }
  565.         }
  566.         s = updateAdditionalStates(s);

  567.         return s;

  568.     }

  569.     /** Convert a state from space flight dynamics world to mathematical world.
  570.      * @param state space flight dynamics state
  571.      * @return mathematical state
  572.      */
  573.     private ODEStateAndDerivative convert(final SpacecraftState state) {

  574.         // retrieve initial state
  575.         final double[] primary    = new double[getBasicDimension()];
  576.         final double[] primaryDot = new double[getBasicDimension()];
  577.         stateMapper.mapStateToArray(state, primary, primaryDot);

  578.         // secondary part of the ODE
  579.         final double[][] secondary           = secondary(state);
  580.         final double[][] secondaryDerivative = secondaryDerivative(state);

  581.         return new ODEStateAndDerivative(stateMapper.mapDateToDouble(state.getDate()),
  582.                                          primary, primaryDot,
  583.                                          secondary, secondaryDerivative);

  584.     }

  585.     /** Differential equations for the main state (orbit, attitude and mass). */
  586.     public interface MainStateEquations {

  587.         /**
  588.          * Initialize the equations at the start of propagation. This method will be
  589.          * called before any calls to {@link #computeDerivatives(SpacecraftState)}.
  590.          *
  591.          * <p> The default implementation of this method does nothing.
  592.          *
  593.          * @param initialState initial state information at the start of propagation.
  594.          * @param target       date of propagation. Not equal to {@code
  595.          *                     initialState.getDate()}.
  596.          */
  597.         default void init(final SpacecraftState initialState, final AbsoluteDate target) {
  598.         }

  599.         /** Compute differential equations for main state.
  600.          * @param state current state
  601.          * @return derivatives of main state
  602.          */
  603.         double[] computeDerivatives(SpacecraftState state);

  604.     }

  605.     /** Differential equations for the main state (orbit, attitude and mass), with converted API. */
  606.     private class ConvertedMainStateEquations implements OrdinaryDifferentialEquation {

  607.         /** Main state equations. */
  608.         private final MainStateEquations main;

  609.         /** Simple constructor.
  610.          * @param main main state equations
  611.          */
  612.         ConvertedMainStateEquations(final MainStateEquations main) {
  613.             this.main = main;
  614.             calls = 0;
  615.         }

  616.         /** {@inheritDoc} */
  617.         public int getDimension() {
  618.             return getBasicDimension();
  619.         }

  620.         @Override
  621.         public void init(final double t0, final double[] y0, final double finalTime) {
  622.             // update space dynamics view
  623.             SpacecraftState initialState = stateMapper.mapArrayToState(t0, y0, null, PropagationType.MEAN);
  624.             initialState = updateAdditionalStates(initialState);
  625.             final AbsoluteDate target = stateMapper.mapDoubleToDate(finalTime);
  626.             main.init(initialState, target);
  627.         }

  628.         /** {@inheritDoc} */
  629.         public double[] computeDerivatives(final double t, final double[] y) {

  630.             // increment calls counter
  631.             ++calls;

  632.             // update space dynamics view
  633.             SpacecraftState currentState = stateMapper.mapArrayToState(t, y, null, PropagationType.MEAN);
  634.             currentState = updateAdditionalStates(currentState);

  635.             // compute main state differentials
  636.             return main.computeDerivatives(currentState);

  637.         }

  638.     }

  639.     /** Differential equations for the secondary state (Jacobians, user variables ...), with converted API. */
  640.     private class ConvertedSecondaryStateEquations implements SecondaryODE {

  641.         /** Dimension of the combined additional states. */
  642.         private final int combinedDimension;

  643.         /** Simple constructor.
  644.           */
  645.         ConvertedSecondaryStateEquations() {
  646.             this.combinedDimension = secondaryOffsets.get(SECONDARY_DIMENSION);
  647.         }

  648.         /** {@inheritDoc} */
  649.         @Override
  650.         public int getDimension() {
  651.             return combinedDimension;
  652.         }

  653.         /** {@inheritDoc} */
  654.         @Override
  655.         public void init(final double t0, final double[] primary0,
  656.                          final double[] secondary0, final double finalTime) {
  657.             // update space dynamics view
  658.             final SpacecraftState initialState = convert(t0, primary0, null, secondary0);

  659.             final AbsoluteDate target = stateMapper.mapDoubleToDate(finalTime);
  660.             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
  661.                 provider.init(initialState, target);
  662.             }

  663.         }

  664.         /** {@inheritDoc} */
  665.         @Override
  666.         public double[] computeDerivatives(final double t, final double[] primary,
  667.                                            final double[] primaryDot, final double[] secondary) {

  668.             // update space dynamics view
  669.             // the integrable generators generate method will be called here,
  670.             // according to the generators yield order
  671.             SpacecraftState updated = convert(t, primary, primaryDot, secondary);

  672.             // set up queue for equations
  673.             final Queue<AdditionalDerivativesProvider> pending = new LinkedList<>(additionalDerivativesProviders);

  674.             // gather the derivatives from all additional equations, taking care of dependencies
  675.             final double[] secondaryDot = new double[combinedDimension];
  676.             int yieldCount = 0;
  677.             while (!pending.isEmpty()) {
  678.                 final AdditionalDerivativesProvider provider = pending.remove();
  679.                 if (provider.yield(updated)) {
  680.                     // this provider has to wait for another one,
  681.                     // we put it again in the pending queue
  682.                     pending.add(provider);
  683.                     if (++yieldCount >= pending.size()) {
  684.                         // all pending providers yielded!, they probably need data not yet initialized
  685.                         // we let the propagation proceed, if these data are really needed right now
  686.                         // an appropriate exception will be triggered when caller tries to access them
  687.                         break;
  688.                     }
  689.                 } else {
  690.                     // we can use these equations right now
  691.                     final String              name           = provider.getName();
  692.                     final int                 offset         = secondaryOffsets.get(name);
  693.                     final int                 dimension      = provider.getDimension();
  694.                     final CombinedDerivatives derivatives    = provider.combinedDerivatives(updated);
  695.                     final double[]            additionalPart = derivatives.getAdditionalDerivatives();
  696.                     final double[]            mainPart       = derivatives.getMainStateDerivativesIncrements();
  697.                     System.arraycopy(additionalPart, 0, secondaryDot, offset, dimension);
  698.                     updated = updated.addAdditionalStateDerivative(name, additionalPart);
  699.                     if (mainPart != null) {
  700.                         // this equation does change the main state derivatives
  701.                         for (int i = 0; i < mainPart.length; ++i) {
  702.                             primaryDot[i] += mainPart[i];
  703.                         }
  704.                     }
  705.                     yieldCount = 0;
  706.                 }
  707.             }

  708.             return secondaryDot;

  709.         }

  710.         /** Convert mathematical view to space view.
  711.          * @param t current value of the independent <I>time</I> variable
  712.          * @param primary array containing the current value of the primary state vector
  713.          * @param primaryDot array containing the derivative of the primary state vector
  714.          * @param secondary array containing the current value of the secondary state vector
  715.          * @return space view of the state
  716.          */
  717.         private SpacecraftState convert(final double t, final double[] primary,
  718.                                         final double[] primaryDot, final double[] secondary) {

  719.             SpacecraftState initialState = stateMapper.mapArrayToState(t, primary, primaryDot, PropagationType.MEAN);

  720.             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
  721.                 final String name      = provider.getName();
  722.                 final int    offset    = secondaryOffsets.get(name);
  723.                 final int    dimension = provider.getDimension();
  724.                 initialState = initialState.addAdditionalState(name, Arrays.copyOfRange(secondary, offset, offset + dimension));
  725.             }

  726.             return updateAdditionalStates(initialState);

  727.         }

  728.     }

  729.     /** Adapt an {@link org.orekit.propagation.events.EventDetector}
  730.      * to Hipparchus {@link org.hipparchus.ode.events.ODEEventHandler} interface.
  731.      * @author Fabien Maussion
  732.      */
  733.     private class AdaptedEventDetector implements ODEEventHandler {

  734.         /** Underlying event detector. */
  735.         private final EventDetector detector;

  736.         /** Time of the previous call to g. */
  737.         private double lastT;

  738.         /** Value from the previous call to g. */
  739.         private double lastG;

  740.         /** Build a wrapped event detector.
  741.          * @param detector event detector to wrap
  742.         */
  743.         AdaptedEventDetector(final EventDetector detector) {
  744.             this.detector = detector;
  745.             this.lastT    = Double.NaN;
  746.             this.lastG    = Double.NaN;
  747.         }

  748.         /** {@inheritDoc} */
  749.         public void init(final ODEStateAndDerivative s0, final double t) {
  750.             detector.init(convert(s0), stateMapper.mapDoubleToDate(t));
  751.             this.lastT = Double.NaN;
  752.             this.lastG = Double.NaN;
  753.         }

  754.         /** {@inheritDoc} */
  755.         public double g(final ODEStateAndDerivative s) {
  756.             if (!Precision.equals(lastT, s.getTime(), 0)) {
  757.                 lastT = s.getTime();
  758.                 lastG = detector.g(convert(s));
  759.             }
  760.             return lastG;
  761.         }

  762.         /** {@inheritDoc} */
  763.         public Action eventOccurred(final ODEStateAndDerivative s, final boolean increasing) {
  764.             return detector.eventOccurred(convert(s), increasing);
  765.         }

  766.         /** {@inheritDoc} */
  767.         public ODEState resetState(final ODEStateAndDerivative s) {

  768.             final SpacecraftState oldState = convert(s);
  769.             final SpacecraftState newState = detector.resetState(oldState);
  770.             stateChanged(newState);

  771.             // main part
  772.             final double[] primary    = new double[s.getPrimaryStateDimension()];
  773.             stateMapper.mapStateToArray(newState, primary, null);

  774.             // secondary part
  775.             final double[][] secondary = new double[1][secondaryOffsets.get(SECONDARY_DIMENSION)];
  776.             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
  777.                 final String name      = provider.getName();
  778.                 final int    offset    = secondaryOffsets.get(name);
  779.                 final int    dimension = provider.getDimension();
  780.                 System.arraycopy(newState.getAdditionalState(name), 0, secondary[0], offset, dimension);
  781.             }

  782.             return new ODEState(newState.getDate().durationFrom(getStartDate()),
  783.                                 primary, secondary);

  784.         }

  785.     }

  786.     /** Adapt an {@link org.orekit.propagation.sampling.OrekitStepHandler}
  787.      * to Hipparchus {@link ODEStepHandler} interface.
  788.      * @author Luc Maisonobe
  789.      */
  790.     private class AdaptedStepHandler implements ODEStepHandler {

  791.         /** Underlying handler. */
  792.         private final OrekitStepHandler handler;

  793.         /** Build an instance.
  794.          * @param handler underlying handler to wrap
  795.          */
  796.         AdaptedStepHandler(final OrekitStepHandler handler) {
  797.             this.handler = handler;
  798.         }

  799.         /** {@inheritDoc} */
  800.         public void init(final ODEStateAndDerivative s0, final double t) {
  801.             handler.init(convert(s0), stateMapper.mapDoubleToDate(t));
  802.         }

  803.         /** {@inheritDoc} */
  804.         @Override
  805.         public void handleStep(final ODEStateInterpolator interpolator) {
  806.             handler.handleStep(new AdaptedStepInterpolator(interpolator));
  807.         }

  808.         /** {@inheritDoc} */
  809.         @Override
  810.         public void finish(final ODEStateAndDerivative finalState) {
  811.             handler.finish(convert(finalState));
  812.         }

  813.     }

  814.     /** Adapt an Hipparchus {@link ODEStateInterpolator}
  815.      * to an orekit {@link OrekitStepInterpolator} interface.
  816.      * @author Luc Maisonobe
  817.      */
  818.     private class AdaptedStepInterpolator implements OrekitStepInterpolator {

  819.         /** Underlying raw rawInterpolator. */
  820.         private final ODEStateInterpolator mathInterpolator;

  821.         /** Simple constructor.
  822.          * @param mathInterpolator underlying raw interpolator
  823.          */
  824.         AdaptedStepInterpolator(final ODEStateInterpolator mathInterpolator) {
  825.             this.mathInterpolator = mathInterpolator;
  826.         }

  827.         /** {@inheritDoc}} */
  828.         @Override
  829.         public SpacecraftState getPreviousState() {
  830.             return convert(mathInterpolator.getPreviousState());
  831.         }

  832.         /** {@inheritDoc}} */
  833.         @Override
  834.         public boolean isPreviousStateInterpolated() {
  835.             return mathInterpolator.isPreviousStateInterpolated();
  836.         }

  837.         /** {@inheritDoc}} */
  838.         @Override
  839.         public SpacecraftState getCurrentState() {
  840.             return convert(mathInterpolator.getCurrentState());
  841.         }

  842.         /** {@inheritDoc}} */
  843.         @Override
  844.         public boolean isCurrentStateInterpolated() {
  845.             return mathInterpolator.isCurrentStateInterpolated();
  846.         }

  847.         /** {@inheritDoc}} */
  848.         @Override
  849.         public SpacecraftState getInterpolatedState(final AbsoluteDate date) {
  850.             return convert(mathInterpolator.getInterpolatedState(date.durationFrom(stateMapper.getReferenceDate())));
  851.         }

  852.         /** {@inheritDoc}} */
  853.         @Override
  854.         public boolean isForward() {
  855.             return mathInterpolator.isForward();
  856.         }

  857.         /** {@inheritDoc}} */
  858.         @Override
  859.         public AdaptedStepInterpolator restrictStep(final SpacecraftState newPreviousState,
  860.                                                     final SpacecraftState newCurrentState) {
  861.             try {
  862.                 final AbstractODEStateInterpolator aosi = (AbstractODEStateInterpolator) mathInterpolator;
  863.                 return new AdaptedStepInterpolator(aosi.restrictStep(convert(newPreviousState),
  864.                                                                      convert(newCurrentState)));
  865.             } catch (ClassCastException cce) {
  866.                 // this should never happen
  867.                 throw new OrekitInternalError(cce);
  868.             }
  869.         }

  870.     }

  871.     /** Specialized step handler storing interpolators for ephemeris generation.
  872.      * @since 11.0
  873.      */
  874.     private class StoringStepHandler implements ODEStepHandler, EphemerisGenerator {

  875.         /** Underlying raw mathematical model. */
  876.         private DenseOutputModel model;

  877.         /** the user supplied end date. Propagation may not end on this date. */
  878.         private AbsoluteDate endDate;

  879.         /** Generated ephemeris. */
  880.         private BoundedPropagator ephemeris;

  881.         /** Last interpolator handled by the object.*/
  882.         private  ODEStateInterpolator lastInterpolator;

  883.         /** Set the end date.
  884.          * @param endDate end date
  885.          */
  886.         public void setEndDate(final AbsoluteDate endDate) {
  887.             this.endDate = endDate;
  888.         }

  889.         /** {@inheritDoc} */
  890.         @Override
  891.         public void init(final ODEStateAndDerivative s0, final double t) {

  892.             this.model = new DenseOutputModel();
  893.             model.init(s0, t);

  894.             // ephemeris will be generated when last step is processed
  895.             this.ephemeris = null;

  896.             this.lastInterpolator = null;

  897.         }

  898.         /** {@inheritDoc} */
  899.         @Override
  900.         public BoundedPropagator getGeneratedEphemeris() {
  901.             // Each time we try to get the ephemeris, rebuild it using the last data.
  902.             buildEphemeris();
  903.             return ephemeris;
  904.         }

  905.         /** {@inheritDoc} */
  906.         @Override
  907.         public void handleStep(final ODEStateInterpolator interpolator) {
  908.             model.handleStep(interpolator);
  909.             lastInterpolator = interpolator;
  910.         }

  911.         /** {@inheritDoc} */
  912.         @Override
  913.         public void finish(final ODEStateAndDerivative finalState) {
  914.             buildEphemeris();
  915.         }

  916.         /** Method used to produce ephemeris at a given time.
  917.          * Can be used at multiple times, updating the ephemeris to
  918.          * its last state.
  919.          */
  920.         private void buildEphemeris() {
  921.             // buildEphemeris was built in order to allow access to what was previously the finish method.
  922.             // This now allows to call it through getGeneratedEphemeris, therefore through an external call,
  923.             // which was not previously the case.

  924.             // Update the model's finalTime with the last interpolator.
  925.             model.finish(lastInterpolator.getCurrentState());

  926.             // set up the boundary dates
  927.             final double tI = model.getInitialTime();
  928.             final double tF = model.getFinalTime();
  929.             // tI is almost? always zero
  930.             final AbsoluteDate startDate =
  931.                             stateMapper.mapDoubleToDate(tI);
  932.             final AbsoluteDate finalDate =
  933.                             stateMapper.mapDoubleToDate(tF, this.endDate);
  934.             final AbsoluteDate minDate;
  935.             final AbsoluteDate maxDate;
  936.             if (tF < tI) {
  937.                 minDate = finalDate;
  938.                 maxDate = startDate;
  939.             } else {
  940.                 minDate = startDate;
  941.                 maxDate = finalDate;
  942.             }

  943.             // get the initial additional states that are not managed
  944.             final DoubleArrayDictionary unmanaged = new DoubleArrayDictionary();
  945.             for (final DoubleArrayDictionary.Entry initial : getInitialState().getAdditionalStatesValues().getData()) {
  946.                 if (!isAdditionalStateManaged(initial.getKey())) {
  947.                     // this additional state was in the initial state, but is unknown to the propagator
  948.                     // we simply copy its initial value as is
  949.                     unmanaged.put(initial.getKey(), initial.getValue());
  950.                 }
  951.             }

  952.             // get the names of additional states managed by differential equations
  953.             final String[] names      = new String[additionalDerivativesProviders.size()];
  954.             final int[]    dimensions = new int[additionalDerivativesProviders.size()];
  955.             for (int i = 0; i < names.length; ++i) {
  956.                 names[i] = additionalDerivativesProviders.get(i).getName();
  957.                 dimensions[i] = additionalDerivativesProviders.get(i).getDimension();
  958.             }

  959.             // create the ephemeris
  960.             ephemeris = new IntegratedEphemeris(startDate, minDate, maxDate,
  961.                                                 stateMapper, propagationType, model,
  962.                                                 unmanaged, getAdditionalStateProviders(),
  963.                                                 names, dimensions);

  964.         }

  965.     }

  966.     /** Wrapper for resetting an integrator handlers.
  967.      * <p>
  968.      * This class is intended to be used in a try-with-resource statement.
  969.      * If propagator-specific event handlers and step handlers are added to
  970.      * the integrator in the try block, they will be removed automatically
  971.      * when leaving the block, so the integrator only keeps its own handlers
  972.      * between calls to {@link AbstractIntegratedPropagator#propagate(AbsoluteDate, AbsoluteDate).
  973.      * </p>
  974.      * @since 11.0
  975.      */
  976.     private static class IntegratorResetter implements AutoCloseable {

  977.         /** Wrapped integrator. */
  978.         private final ODEIntegrator integrator;

  979.         /** Initial event handlers list. */
  980.         private final List<EventHandlerConfiguration> eventHandlersConfigurations;

  981.         /** Initial step handlers list. */
  982.         private final List<ODEStepHandler> stepHandlers;

  983.         /** Simple constructor.
  984.          * @param integrator wrapped integrator
  985.          */
  986.         IntegratorResetter(final ODEIntegrator integrator) {
  987.             this.integrator                  = integrator;
  988.             this.eventHandlersConfigurations = new ArrayList<>(integrator.getEventHandlersConfigurations());
  989.             this.stepHandlers                = new ArrayList<>(integrator.getStepHandlers());
  990.         }

  991.         /** {@inheritDoc}
  992.          * <p>
  993.          * Reset event handlers and step handlers back to the initial list
  994.          * </p>
  995.          */
  996.         @Override
  997.         public void close() {

  998.             // reset event handlers
  999.             integrator.clearEventHandlers();
  1000.             eventHandlersConfigurations.forEach(c -> integrator.addEventHandler(c.getEventHandler(),
  1001.                                                                                 c.getMaxCheckInterval(),
  1002.                                                                                 c.getConvergence(),
  1003.                                                                                 c.getMaxIterationCount(),
  1004.                                                                                 c.getSolver()));

  1005.             // reset step handlers
  1006.             integrator.clearStepHandlers();
  1007.             stepHandlers.forEach(stepHandler -> integrator.addStepHandler(stepHandler));

  1008.         }

  1009.     }

  1010. }