1   /* Copyright 2002-2021 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  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.hipparchus.exception.MathRuntimeException;
27  import org.hipparchus.ode.DenseOutputModel;
28  import org.hipparchus.ode.EquationsMapper;
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  
59  
60  /** Common handling of {@link org.orekit.propagation.Propagator Propagator}
61   *  methods for both numerical and semi-analytical propagators.
62   *  @author Luc Maisonobe
63   */
64  public abstract class AbstractIntegratedPropagator extends AbstractPropagator {
65  
66      /** Event detectors not related to force models. */
67      private final List<EventDetector> detectors;
68  
69      /** Step handlers dedicated to ephemeris generation. */
70      private final List<StoringStepHandler> generators;
71  
72      /** Integrator selected by the user for the orbital extrapolation process. */
73      private final ODEIntegrator integrator;
74  
75      /** Additional equations. */
76      private List<AdditionalEquations> additionalEquations;
77  
78      /** Counter for differential equations calls. */
79      private int calls;
80  
81      /** Mapper between raw double components and space flight dynamics objects. */
82      private StateMapper stateMapper;
83  
84      /** Equations mapper. */
85      private EquationsMapper equationsMapper;
86  
87      /** Flag for resetting the state at end of propagation. */
88      private boolean resetAtEnd;
89  
90      /** Type of orbit to output (mean or osculating) <br/>
91       * <p>
92       * This is used only in the case of semianalitical propagators where there is a clear separation between
93       * mean and short periodic elements. It is ignored by the Numerical propagator.
94       * </p>
95       */
96      private PropagationType propagationType;
97  
98      /** Build a new instance.
99       * @param integrator numerical integrator to use for propagation.
100      * @param propagationType type of orbit to output (mean or osculating).
101      */
102     protected AbstractIntegratedPropagator(final ODEIntegrator integrator, final PropagationType propagationType) {
103         detectors            = new ArrayList<>();
104         generators           = new ArrayList<>();
105         additionalEquations  = new ArrayList<>();
106         this.integrator      = integrator;
107         this.propagationType = propagationType;
108         this.resetAtEnd      = true;
109     }
110 
111     /** Allow/disallow resetting the initial state at end of propagation.
112      * <p>
113      * By default, at the end of the propagation, the propagator resets the initial state
114      * to the final state, thus allowing a new propagation to be started from there without
115      * recomputing the part already performed. Calling this method with {@code resetAtEnd} set
116      * to false changes prevents such reset.
117      * </p>
118      * @param resetAtEnd if true, at end of each propagation, the {@link
119      * #getInitialState() initial state} will be reset to the final state of
120      * the propagation, otherwise the initial state will be preserved
121      * @since 9.0
122      */
123     public void setResetAtEnd(final boolean resetAtEnd) {
124         this.resetAtEnd = resetAtEnd;
125     }
126 
127     /** Initialize the mapper. */
128     protected void initMapper() {
129         stateMapper = createMapper(null, Double.NaN, null, null, null, null);
130     }
131 
132     /**  {@inheritDoc} */
133     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
134         super.setAttitudeProvider(attitudeProvider);
135         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
136                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
137                                    attitudeProvider, stateMapper.getFrame());
138     }
139 
140     /** Set propagation orbit type.
141      * @param orbitType orbit type to use for propagation, null for
142      * propagating using {@link org.orekit.utils.AbsolutePVCoordinates AbsolutePVCoordinates}
143      * rather than {@link org.orekit.orbits Orbit}
144      */
145     protected void setOrbitType(final OrbitType orbitType) {
146         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
147                                    orbitType, stateMapper.getPositionAngleType(),
148                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
149     }
150 
151     /** Get propagation parameter type.
152      * @return orbit type used for propagation, null for
153      * propagating using {@link org.orekit.utils.AbsolutePVCoordinates AbsolutePVCoordinates}
154      * rather than {@link org.orekit.orbits Orbit}
155      */
156     protected OrbitType getOrbitType() {
157         return stateMapper.getOrbitType();
158     }
159 
160     /** Check if only the mean elements should be used in a semianalitical propagation.
161      * @return {@link PropagationType MEAN} if only mean elements have to be used or
162      *         {@link PropagationType OSCULATING} if osculating elements have to be also used.
163      */
164     protected PropagationType isMeanOrbit() {
165         return propagationType;
166     }
167 
168     /** Set position angle type.
169      * <p>
170      * The position parameter type is meaningful only if {@link
171      * #getOrbitType() propagation orbit type}
172      * support it. As an example, it is not meaningful for propagation
173      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
174      * </p>
175      * @param positionAngleType angle type to use for propagation
176      */
177     protected void setPositionAngleType(final PositionAngle positionAngleType) {
178         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
179                                    stateMapper.getOrbitType(), positionAngleType,
180                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
181     }
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 
190     /** Set the central attraction coefficient μ.
191      * @param mu central attraction coefficient (m³/s²)
192      */
193     public void setMu(final double mu) {
194         stateMapper = createMapper(stateMapper.getReferenceDate(), mu,
195                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
196                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
197     }
198 
199     /** Get the central attraction coefficient μ.
200      * @return mu central attraction coefficient (m³/s²)
201      * @see #setMu(double)
202      */
203     public double getMu() {
204         return stateMapper.getMu();
205     }
206 
207     /** Get the number of calls to the differential equations computation method.
208      * <p>The number of calls is reset each time the {@link #propagate(AbsoluteDate)}
209      * method is called.</p>
210      * @return number of calls to the differential equations computation method
211      */
212     public int getCalls() {
213         return calls;
214     }
215 
216     /** {@inheritDoc} */
217     @Override
218     public boolean isAdditionalStateManaged(final String name) {
219 
220         // first look at already integrated states
221         if (super.isAdditionalStateManaged(name)) {
222             return true;
223         }
224 
225         // then look at states we integrate ourselves
226         for (final AdditionalEquations equation : additionalEquations) {
227             if (equation.getName().equals(name)) {
228                 return true;
229             }
230         }
231 
232         return false;
233     }
234 
235     /** {@inheritDoc} */
236     @Override
237     public String[] getManagedAdditionalStates() {
238         final String[] alreadyIntegrated = super.getManagedAdditionalStates();
239         final String[] managed = new String[alreadyIntegrated.length + additionalEquations.size()];
240         System.arraycopy(alreadyIntegrated, 0, managed, 0, alreadyIntegrated.length);
241         for (int i = 0; i < additionalEquations.size(); ++i) {
242             managed[i + alreadyIntegrated.length] = additionalEquations.get(i).getName();
243         }
244         return managed;
245     }
246 
247     /** Add a set of user-specified equations to be integrated along with the orbit propagation.
248      * @param additional additional equations
249      */
250     public void addAdditionalEquations(final AdditionalEquations additional) {
251 
252         // check if the name is already used
253         if (isAdditionalStateManaged(additional.getName())) {
254             // this set of equations is already registered, complain
255             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_NAME_ALREADY_IN_USE,
256                                       additional.getName());
257         }
258 
259         // this is really a new set of equations, add it
260         additionalEquations.add(additional);
261 
262     }
263 
264     /** {@inheritDoc} */
265     public void addEventDetector(final EventDetector detector) {
266         detectors.add(detector);
267     }
268 
269     /** {@inheritDoc} */
270     public Collection<EventDetector> getEventsDetectors() {
271         return Collections.unmodifiableCollection(detectors);
272     }
273 
274     /** {@inheritDoc} */
275     public void clearEventsDetectors() {
276         detectors.clear();
277     }
278 
279     /** Set up all user defined event detectors.
280      */
281     protected void setUpUserEventDetectors() {
282         for (final EventDetector detector : detectors) {
283             setUpEventDetector(integrator, detector);
284         }
285     }
286 
287     /** Wrap an Orekit event detector and register it to the integrator.
288      * @param integ integrator into which event detector should be registered
289      * @param detector event detector to wrap
290      */
291     protected void setUpEventDetector(final ODEIntegrator integ, final EventDetector detector) {
292         integ.addEventHandler(new AdaptedEventDetector(detector),
293                               detector.getMaxCheckInterval(),
294                               detector.getThreshold(),
295                               detector.getMaxIterationCount());
296     }
297 
298     /** {@inheritDoc} */
299     @Override
300     public EphemerisGenerator getEphemerisGenerator() {
301         final StoringStepHandler storingHandler = new StoringStepHandler();
302         generators.add(storingHandler);
303         return storingHandler;
304     }
305 
306     /** Create a mapper between raw double components and spacecraft state.
307     /** Simple constructor.
308      * <p>
309      * The position parameter type is meaningful only if {@link
310      * #getOrbitType() propagation orbit type}
311      * support it. As an example, it is not meaningful for propagation
312      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
313      * </p>
314      * @param referenceDate reference date
315      * @param mu central attraction coefficient (m³/s²)
316      * @param orbitType orbit type to use for mapping
317      * @param positionAngleType angle type to use for propagation
318      * @param attitudeProvider attitude provider
319      * @param frame inertial frame
320      * @return new mapper
321      */
322     protected abstract StateMapper createMapper(AbsoluteDate referenceDate, double mu,
323                                                 OrbitType orbitType, PositionAngle positionAngleType,
324                                                 AttitudeProvider attitudeProvider, Frame frame);
325 
326     /** Get the differential equations to integrate (for main state only).
327      * @param integ numerical integrator to use for propagation.
328      * @return differential equations for main state
329      */
330     protected abstract MainStateEquations getMainStateEquations(ODEIntegrator integ);
331 
332     /** {@inheritDoc} */
333     public SpacecraftState propagate(final AbsoluteDate target) {
334         if (getStartDate() == null) {
335             if (getInitialState() == null) {
336                 throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
337             }
338             setStartDate(getInitialState().getDate());
339         }
340         return propagate(getStartDate(), target);
341     }
342 
343     /** {@inheritDoc} */
344     public SpacecraftState propagate(final AbsoluteDate tStart, final AbsoluteDate tEnd) {
345 
346         if (getInitialState() == null) {
347             throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
348         }
349 
350         // make sure the integrator will be reset properly even if we change its events handlers and step handlers
351         try (IntegratorResetter resetter = new IntegratorResetter(integrator)) {
352 
353             if (!tStart.equals(getInitialState().getDate())) {
354                 // if propagation start date is not initial date,
355                 // propagate from initial to start date without event detection
356                 integrateDynamics(tStart);
357             }
358 
359             // set up events added by user
360             setUpUserEventDetectors();
361 
362             // set up step handlers
363             for (final OrekitStepHandler handler : getMultiplexer().getHandlers()) {
364                 integrator.addStepHandler(new AdaptedStepHandler(handler));
365             }
366             for (final StoringStepHandler generator : generators) {
367                 generator.setEndDate(tEnd);
368                 integrator.addStepHandler(generator);
369             }
370 
371             // propagate from start date to end date with event detection
372             return integrateDynamics(tEnd);
373 
374         }
375 
376     }
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 
385             initializePropagation();
386 
387             if (getInitialState().getDate().equals(tEnd)) {
388                 // don't extrapolate
389                 return getInitialState();
390             }
391 
392             // space dynamics view
393             stateMapper = createMapper(getInitialState().getDate(), stateMapper.getMu(),
394                                        stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
395                                        stateMapper.getAttitudeProvider(), getInitialState().getFrame());
396 
397 
398             if (Double.isNaN(getMu())) {
399                 setMu(getInitialState().getMu());
400             }
401 
402             if (getInitialState().getMass() <= 0.0) {
403                 throw new OrekitException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE,
404                                           getInitialState().getMass());
405             }
406 
407             // convert space flight dynamics API to math API
408             final SpacecraftState initialIntegrationState = getInitialIntegrationState();
409             final ODEState mathInitialState = createInitialState(initialIntegrationState);
410             final ExpandableODE mathODE = createODE(integrator, mathInitialState);
411             equationsMapper = mathODE.getMapper();
412 
413             // mathematical integration
414             final ODEStateAndDerivative mathFinalState;
415             beforeIntegration(initialIntegrationState, tEnd);
416             mathFinalState = integrator.integrate(mathODE, mathInitialState,
417                                                   tEnd.durationFrom(getInitialState().getDate()));
418             afterIntegration();
419 
420             // get final state
421             SpacecraftState finalState =
422                             stateMapper.mapArrayToState(stateMapper.mapDoubleToDate(mathFinalState.getTime(),
423                                                                                     tEnd),
424                                                         mathFinalState.getPrimaryState(),
425                                                         mathFinalState.getPrimaryDerivative(),
426                                                         propagationType);
427 
428             finalState = updateAdditionalStates(finalState);
429             for (int i = 0; i < additionalEquations.size(); ++i) {
430                 final double[] secondary = mathFinalState.getSecondaryState(i + 1);
431                 finalState = finalState.addAdditionalState(additionalEquations.get(i).getName(),
432                                                            secondary);
433             }
434             if (resetAtEnd) {
435                 resetInitialState(finalState);
436                 setStartDate(finalState.getDate());
437             }
438 
439             return finalState;
440 
441         } catch (MathRuntimeException mre) {
442             throw OrekitException.unwrap(mre);
443         }
444     }
445 
446     /** Get the initial state for integration.
447      * @return initial state for integration
448      */
449     protected SpacecraftState getInitialIntegrationState() {
450         return getInitialState();
451     }
452 
453     /** Create an initial state.
454      * @param initialState initial state in flight dynamics world
455      * @return initial state in mathematics world
456      */
457     private ODEState createInitialState(final SpacecraftState initialState) {
458 
459         // retrieve initial state
460         final double[] primary  = new double[getBasicDimension()];
461         stateMapper.mapStateToArray(initialState, primary, null);
462 
463         // secondary part of the ODE
464         final double[][] secondary = new double[additionalEquations.size()][];
465         for (int i = 0; i < additionalEquations.size(); ++i) {
466             final AdditionalEquations additional = additionalEquations.get(i);
467             secondary[i] = initialState.getAdditionalState(additional.getName());
468         }
469 
470         return new ODEState(0.0, primary, secondary);
471 
472     }
473 
474     /** Create an ODE with all equations.
475      * @param integ numerical integrator to use for propagation.
476      * @param mathInitialState initial state
477      * @return a new ode
478      */
479     private ExpandableODE createODE(final ODEIntegrator integ,
480                                     final ODEState mathInitialState) {
481 
482         final ExpandableODE ode =
483                 new ExpandableODE(new ConvertedMainStateEquations(getMainStateEquations(integ)));
484 
485         // secondary part of the ODE
486         for (int i = 0; i < additionalEquations.size(); ++i) {
487             final AdditionalEquations additional = additionalEquations.get(i);
488             final SecondaryODE secondary =
489                     new ConvertedSecondaryStateEquations(additional,
490                                                          mathInitialState.getSecondaryStateDimension(i + 1));
491             ode.addSecondaryEquations(secondary);
492         }
493 
494         return ode;
495 
496     }
497 
498     /** Method called just before integration.
499      * <p>
500      * The default implementation does nothing, it may be specialized in subclasses.
501      * </p>
502      * @param initialState initial state
503      * @param tEnd target date at which state should be propagated
504      */
505     protected void beforeIntegration(final SpacecraftState initialState,
506                                      final AbsoluteDate tEnd) {
507         // do nothing by default
508     }
509 
510     /** Method called just after integration.
511      * <p>
512      * The default implementation does nothing, it may be specialized in subclasses.
513      * </p>
514      */
515     protected void afterIntegration() {
516         // do nothing by default
517     }
518 
519     /** Get state vector dimension without additional parameters.
520      * @return state vector dimension without additional parameters.
521      */
522     public int getBasicDimension() {
523         return 7;
524 
525     }
526 
527     /** Get the integrator used by the propagator.
528      * @return the integrator.
529      */
530     protected ODEIntegrator getIntegrator() {
531         return integrator;
532     }
533 
534     /** Get a complete state with all additional equations.
535      * @param t current value of the independent <I>time</I> variable
536      * @param y array containing the current value of the state vector
537      * @param yDot array containing the current value of the state vector derivative
538      * @return complete state
539      */
540     private SpacecraftState getCompleteState(final double t, final double[] y, final double[] yDot) {
541 
542         // main state
543         SpacecraftState state = stateMapper.mapArrayToState(t, y, yDot, propagationType);
544 
545         // pre-integrated additional states
546         state = updateAdditionalStates(state);
547 
548         // additional states integrated here
549         if (!additionalEquations.isEmpty()) {
550 
551             for (int i = 0; i < additionalEquations.size(); ++i) {
552                 state = state.addAdditionalState(additionalEquations.get(i).getName(),
553                                                  equationsMapper.extractEquationData(i + 1, y));
554             }
555 
556         }
557 
558         return state;
559 
560     }
561 
562     /** Convert a state from mathematical world to space flight dynamics world.
563      * @param os mathematical state
564      * @return space flight dynamics state
565      */
566     private SpacecraftState convert(final ODEStateAndDerivative os) {
567 
568         SpacecraftState s =
569                         stateMapper.mapArrayToState(os.getTime(),
570                                                     os.getPrimaryState(),
571                                                     os.getPrimaryDerivative(),
572                                                     propagationType);
573         s = updateAdditionalStates(s);
574         for (int i = 0; i < additionalEquations.size(); ++i) {
575             final double[] secondary = os.getSecondaryState(i + 1);
576             s = s.addAdditionalState(additionalEquations.get(i).getName(), secondary);
577         }
578 
579         return s;
580 
581     }
582 
583     /** Convert a state from space flight dynamics world to mathematical world.
584      * @param state space flight dynamics state
585      * @return mathematical state
586      */
587     private ODEStateAndDerivative convert(final SpacecraftState state) {
588 
589         // retrieve initial state
590         final double[] primary    = new double[getBasicDimension()];
591         final double[] primaryDot = new double[getBasicDimension()];
592         stateMapper.mapStateToArray(state, primary, primaryDot);
593 
594         // secondary part of the ODE
595         final double[][] secondary    = new double[additionalEquations.size()][];
596         for (int i = 0; i < additionalEquations.size(); ++i) {
597             final AdditionalEquations additional = additionalEquations.get(i);
598             secondary[i] = state.getAdditionalState(additional.getName());
599         }
600 
601         return new ODEStateAndDerivative(stateMapper.mapDateToDouble(state.getDate()),
602                                          primary, primaryDot,
603                                          secondary, null);
604 
605     }
606 
607     /** Differential equations for the main state (orbit, attitude and mass). */
608     public interface MainStateEquations {
609 
610         /**
611          * Initialize the equations at the start of propagation. This method will be
612          * called before any calls to {@link #computeDerivatives(SpacecraftState)}.
613          *
614          * <p> The default implementation of this method does nothing.
615          *
616          * @param initialState initial state information at the start of propagation.
617          * @param target       date of propagation. Not equal to {@code
618          *                     initialState.getDate()}.
619          */
620         default void init(final SpacecraftState initialState, final AbsoluteDate target) {
621         }
622 
623         /** Compute differential equations for main state.
624          * @param state current state
625          * @return derivatives of main state
626          */
627         double[] computeDerivatives(SpacecraftState state);
628 
629     }
630 
631     /** Differential equations for the main state (orbit, attitude and mass), with converted API. */
632     private class ConvertedMainStateEquations implements OrdinaryDifferentialEquation {
633 
634         /** Main state equations. */
635         private final MainStateEquations main;
636 
637         /** Simple constructor.
638          * @param main main state equations
639          */
640         ConvertedMainStateEquations(final MainStateEquations main) {
641             this.main = main;
642             calls = 0;
643         }
644 
645         /** {@inheritDoc} */
646         public int getDimension() {
647             return getBasicDimension();
648         }
649 
650         @Override
651         public void init(final double t0, final double[] y0, final double finalTime) {
652             // update space dynamics view
653             SpacecraftState initialState = stateMapper.mapArrayToState(t0, y0, null, PropagationType.MEAN);
654             initialState = updateAdditionalStates(initialState);
655             final AbsoluteDate target = stateMapper.mapDoubleToDate(finalTime);
656             main.init(initialState, target);
657         }
658 
659         /** {@inheritDoc} */
660         public double[] computeDerivatives(final double t, final double[] y) {
661 
662             // increment calls counter
663             ++calls;
664 
665             // update space dynamics view
666             SpacecraftState currentState = stateMapper.mapArrayToState(t, y, null, PropagationType.MEAN);
667             currentState = updateAdditionalStates(currentState);
668 
669             // compute main state differentials
670             return main.computeDerivatives(currentState);
671 
672         }
673 
674     }
675 
676     /** Differential equations for the secondary state (Jacobians, user variables ...), with converted API. */
677     private class ConvertedSecondaryStateEquations implements SecondaryODE {
678 
679         /** Additional equations. */
680         private final AdditionalEquations equations;
681 
682         /** Dimension of the additional state. */
683         private final int dimension;
684 
685         /** Simple constructor.
686          * @param equations additional equations
687          * @param dimension dimension of the additional state
688          */
689         ConvertedSecondaryStateEquations(final AdditionalEquations equations,
690                                          final int dimension) {
691             this.equations = equations;
692             this.dimension = dimension;
693         }
694 
695         /** {@inheritDoc} */
696         @Override
697         public int getDimension() {
698             return dimension;
699         }
700 
701         /** {@inheritDoc} */
702         @Override
703         public void init(final double t0, final double[] primary0,
704                          final double[] secondary0, final double finalTime) {
705             // update space dynamics view
706             SpacecraftState initialState = stateMapper.mapArrayToState(t0, primary0, null, PropagationType.MEAN);
707             initialState = updateAdditionalStates(initialState);
708             initialState = initialState.addAdditionalState(equations.getName(), secondary0);
709             final AbsoluteDate target = stateMapper.mapDoubleToDate(finalTime);
710             equations.init(initialState, target);
711 
712         }
713 
714         /** {@inheritDoc} */
715         @Override
716         public double[] computeDerivatives(final double t, final double[] primary,
717                                            final double[] primaryDot, final double[] secondary) {
718 
719             // update space dynamics view
720             SpacecraftState currentState = stateMapper.mapArrayToState(t, primary, primaryDot, PropagationType.MEAN);
721             currentState = updateAdditionalStates(currentState);
722             currentState = currentState.addAdditionalState(equations.getName(), secondary);
723 
724             // compute additional derivatives
725             final double[] secondaryDot = new double[secondary.length];
726             final double[] additionalMainDot =
727                             equations.computeDerivatives(currentState, secondaryDot);
728             if (additionalMainDot != null) {
729                 // the additional equations have an effect on main equations
730                 for (int i = 0; i < additionalMainDot.length; ++i) {
731                     primaryDot[i] += additionalMainDot[i];
732                 }
733             }
734 
735             return secondaryDot;
736 
737         }
738 
739     }
740 
741     /** Adapt an {@link org.orekit.propagation.events.EventDetector}
742      * to Hipparchus {@link org.hipparchus.ode.events.ODEEventHandler} interface.
743      * @author Fabien Maussion
744      */
745     private class AdaptedEventDetector implements ODEEventHandler {
746 
747         /** Underlying event detector. */
748         private final EventDetector detector;
749 
750         /** Time of the previous call to g. */
751         private double lastT;
752 
753         /** Value from the previous call to g. */
754         private double lastG;
755 
756         /** Build a wrapped event detector.
757          * @param detector event detector to wrap
758         */
759         AdaptedEventDetector(final EventDetector detector) {
760             this.detector = detector;
761             this.lastT    = Double.NaN;
762             this.lastG    = Double.NaN;
763         }
764 
765         /** {@inheritDoc} */
766         public void init(final ODEStateAndDerivative s0, final double t) {
767             detector.init(getCompleteState(s0.getTime(), s0.getCompleteState(), s0.getCompleteDerivative()),
768                           stateMapper.mapDoubleToDate(t));
769             this.lastT = Double.NaN;
770             this.lastG = Double.NaN;
771         }
772 
773         /** {@inheritDoc} */
774         public double g(final ODEStateAndDerivative s) {
775             if (!Precision.equals(lastT, s.getTime(), 0)) {
776                 lastT = s.getTime();
777                 lastG = detector.g(getCompleteState(s.getTime(), s.getCompleteState(), s.getCompleteDerivative()));
778             }
779             return lastG;
780         }
781 
782         /** {@inheritDoc} */
783         public Action eventOccurred(final ODEStateAndDerivative s, final boolean increasing) {
784             return detector.eventOccurred(
785                     getCompleteState(
786                             s.getTime(),
787                             s.getCompleteState(),
788                             s.getCompleteDerivative()),
789                     increasing);
790         }
791 
792         /** {@inheritDoc} */
793         public ODEState resetState(final ODEStateAndDerivative s) {
794 
795             final SpacecraftState oldState = getCompleteState(s.getTime(), s.getCompleteState(), s.getCompleteDerivative());
796             final SpacecraftState newState = detector.resetState(oldState);
797             stateChanged(newState);
798 
799             // main part
800             final double[] primary    = new double[s.getPrimaryStateDimension()];
801             stateMapper.mapStateToArray(newState, primary, null);
802 
803             // secondary part
804             final double[][] secondary    = new double[additionalEquations.size()][];
805             for (int i = 0; i < additionalEquations.size(); ++i) {
806                 secondary[i] = newState.getAdditionalState(additionalEquations.get(i).getName());
807             }
808 
809             return new ODEState(newState.getDate().durationFrom(getStartDate()),
810                                 primary, secondary);
811 
812         }
813 
814     }
815 
816     /** Adapt an {@link org.orekit.propagation.sampling.OrekitStepHandler}
817      * to Hipparchus {@link ODEStepHandler} interface.
818      * @author Luc Maisonobe
819      */
820     private class AdaptedStepHandler implements ODEStepHandler {
821 
822         /** Underlying handler. */
823         private final OrekitStepHandler handler;
824 
825         /** Build an instance.
826          * @param handler underlying handler to wrap
827          */
828         AdaptedStepHandler(final OrekitStepHandler handler) {
829             this.handler = handler;
830         }
831 
832         /** {@inheritDoc} */
833         public void init(final ODEStateAndDerivative s0, final double t) {
834             handler.init(getCompleteState(s0.getTime(), s0.getCompleteState(), s0.getCompleteDerivative()),
835                          stateMapper.mapDoubleToDate(t));
836         }
837 
838         /** {@inheritDoc} */
839         @Override
840         public void handleStep(final ODEStateInterpolator interpolator) {
841             handler.handleStep(new AdaptedStepInterpolator(interpolator));
842         }
843 
844         /** {@inheritDoc} */
845         @Override
846         public void finish(final ODEStateAndDerivative finalState) {
847             handler.finish(convert(finalState));
848         }
849 
850     }
851 
852     /** Adapt an Hipparchus {@link ODEStateInterpolator}
853      * to an orekit {@link OrekitStepInterpolator} interface.
854      * @author Luc Maisonobe
855      */
856     private class AdaptedStepInterpolator implements OrekitStepInterpolator {
857 
858         /** Underlying raw rawInterpolator. */
859         private final ODEStateInterpolator mathInterpolator;
860 
861         /** Simple constructor.
862          * @param mathInterpolator underlying raw interpolator
863          */
864         AdaptedStepInterpolator(final ODEStateInterpolator mathInterpolator) {
865             this.mathInterpolator = mathInterpolator;
866         }
867 
868         /** {@inheritDoc}} */
869         @Override
870         public SpacecraftState getPreviousState() {
871             return convert(mathInterpolator.getPreviousState());
872         }
873 
874         /** {@inheritDoc}} */
875         @Override
876         public boolean isPreviousStateInterpolated() {
877             return mathInterpolator.isPreviousStateInterpolated();
878         }
879 
880         /** {@inheritDoc}} */
881         @Override
882         public SpacecraftState getCurrentState() {
883             return convert(mathInterpolator.getCurrentState());
884         }
885 
886         /** {@inheritDoc}} */
887         @Override
888         public boolean isCurrentStateInterpolated() {
889             return mathInterpolator.isCurrentStateInterpolated();
890         }
891 
892         /** {@inheritDoc}} */
893         @Override
894         public SpacecraftState getInterpolatedState(final AbsoluteDate date) {
895             return convert(mathInterpolator.getInterpolatedState(date.durationFrom(stateMapper.getReferenceDate())));
896         }
897 
898         /** {@inheritDoc}} */
899         @Override
900         public boolean isForward() {
901             return mathInterpolator.isForward();
902         }
903 
904         /** {@inheritDoc}} */
905         @Override
906         public AdaptedStepInterpolator restrictStep(final SpacecraftState newPreviousState,
907                                                     final SpacecraftState newCurrentState) {
908             try {
909                 final AbstractODEStateInterpolator aosi = (AbstractODEStateInterpolator) mathInterpolator;
910                 return new AdaptedStepInterpolator(aosi.restrictStep(convert(newPreviousState),
911                                                                      convert(newCurrentState)));
912             } catch (ClassCastException cce) {
913                 // this should never happen
914                 throw new OrekitInternalError(cce);
915             }
916         }
917 
918     }
919 
920     /** Specialized step handler storing interpolators for ephemeris generation.
921      * @since 11.0
922      */
923     private class StoringStepHandler implements ODEStepHandler, EphemerisGenerator {
924 
925         /** Underlying raw mathematical model. */
926         private DenseOutputModel model;
927 
928         /** the user supplied end date. Propagation may not end on this date. */
929         private AbsoluteDate endDate;
930 
931         /** Generated ephemeris. */
932         private BoundedPropagator ephemeris;
933 
934         /** Set the end date.
935          * @param endDate end date
936          */
937         public void setEndDate(final AbsoluteDate endDate) {
938             this.endDate = endDate;
939         }
940 
941         /** {@inheritDoc} */
942         @Override
943         public void init(final ODEStateAndDerivative s0, final double t) {
944 
945             this.model = new DenseOutputModel();
946             model.init(s0, t);
947 
948             // ephemeris will be generated when last step is processed
949             this.ephemeris = null;
950 
951         }
952 
953         /** {@inheritDoc} */
954         @Override
955         public BoundedPropagator getGeneratedEphemeris() {
956             return ephemeris;
957         }
958 
959         /** {@inheritDoc} */
960         @Override
961         public void handleStep(final ODEStateInterpolator interpolator) {
962             model.handleStep(interpolator);
963         }
964 
965         /** {@inheritDoc} */
966         @Override
967         public void finish(final ODEStateAndDerivative finalState) {
968 
969             // set up the boundary dates
970             final double tI = model.getInitialTime();
971             final double tF = model.getFinalTime();
972             // tI is almost? always zero
973             final AbsoluteDate startDate =
974                             stateMapper.mapDoubleToDate(tI);
975             final AbsoluteDate finalDate =
976                             stateMapper.mapDoubleToDate(tF, this.endDate);
977             final AbsoluteDate minDate;
978             final AbsoluteDate maxDate;
979             if (tF < tI) {
980                 minDate = finalDate;
981                 maxDate = startDate;
982             } else {
983                 minDate = startDate;
984                 maxDate = finalDate;
985             }
986 
987             // get the initial additional states that are not managed
988             final Map<String, double[]> unmanaged = new HashMap<String, double[]>();
989             for (final Map.Entry<String, double[]> initial : getInitialState().getAdditionalStates().entrySet()) {
990                 if (!isAdditionalStateManaged(initial.getKey())) {
991                     // this additional state was in the initial state, but is unknown to the propagator
992                     // we simply copy its initial value as is
993                     unmanaged.put(initial.getKey(), initial.getValue());
994                 }
995             }
996 
997             // get the names of additional states managed by differential equations
998             final String[] names = new String[additionalEquations.size()];
999             for (int i = 0; i < names.length; ++i) {
1000                 names[i] = additionalEquations.get(i).getName();
1001             }
1002 
1003             // create the ephemeris
1004             ephemeris = new IntegratedEphemeris(startDate, minDate, maxDate,
1005                                                 stateMapper, propagationType, model, unmanaged,
1006                                                 getAdditionalStateProviders(), names);
1007 
1008         }
1009 
1010     }
1011 
1012     /** Wrapper for resetting an integrator handlers.
1013      * <p>
1014      * This class is intended to be used in a try-with-resource statement.
1015      * If propagator-specific event handlers and step handlers are added to
1016      * the integrator in the try block, they will be removed automatically
1017      * when leaving the block, so the integrator only keep its own handlers
1018      * between calls to {@link AbstractIntegratedPropagator#propagate(AbsoluteDate, AbsoluteDate).
1019      * </p>
1020      * @since 11.0
1021      */
1022     private static class IntegratorResetter implements AutoCloseable {
1023 
1024         /** Wrapped integrator. */
1025         private final ODEIntegrator integrator;
1026 
1027         /** Initial event handlers list. */
1028         private final List<EventHandlerConfiguration> eventHandlersConfigurations;
1029 
1030         /** Initial step handlers list. */
1031         private final List<ODEStepHandler> stepHandlers;
1032 
1033         /** Simple constructor.
1034          * @param integrator wrapped integrator
1035          */
1036         IntegratorResetter(final ODEIntegrator integrator) {
1037             this.integrator                  = integrator;
1038             this.eventHandlersConfigurations = new ArrayList<>(integrator.getEventHandlersConfigurations());
1039             this.stepHandlers                = new ArrayList<>(integrator.getStepHandlers());
1040         }
1041 
1042         /** {@inheritDoc}
1043          * <p>
1044          * Reset event handlers and step handlers back to the initial list
1045          * </p>
1046          */
1047         @Override
1048         public void close() {
1049 
1050             // reset event handlers
1051             integrator.clearEventHandlers();
1052             eventHandlersConfigurations.forEach(c -> integrator.addEventHandler(c.getEventHandler(),
1053                                                                                 c.getMaxCheckInterval(),
1054                                                                                 c.getConvergence(),
1055                                                                                 c.getMaxIterationCount(),
1056                                                                                 c.getSolver()));
1057 
1058             // reset step handlers
1059             integrator.clearStepHandlers();
1060             stepHandlers.forEach(stepHandler -> integrator.addStepHandler(stepHandler));
1061 
1062         }
1063 
1064     }
1065 
1066 }