1   /* Copyright 2002-2025 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 org.hipparchus.analysis.UnivariateFunction;
20  import org.hipparchus.analysis.solvers.BracketedUnivariateSolver;
21  import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
22  import org.hipparchus.exception.MathRuntimeException;
23  import org.hipparchus.ode.DenseOutputModel;
24  import org.hipparchus.ode.ExpandableODE;
25  import org.hipparchus.ode.ODEIntegrator;
26  import org.hipparchus.ode.ODEState;
27  import org.hipparchus.ode.ODEStateAndDerivative;
28  import org.hipparchus.ode.OrdinaryDifferentialEquation;
29  import org.hipparchus.ode.SecondaryODE;
30  import org.hipparchus.ode.events.Action;
31  import org.hipparchus.ode.events.AdaptableInterval;
32  import org.hipparchus.ode.events.ODEEventDetector;
33  import org.hipparchus.ode.events.ODEEventHandler;
34  import org.hipparchus.ode.sampling.AbstractODEStateInterpolator;
35  import org.hipparchus.ode.sampling.ODEStateInterpolator;
36  import org.hipparchus.ode.sampling.ODEStepHandler;
37  import org.hipparchus.util.Precision;
38  import org.orekit.attitudes.AttitudeProvider;
39  import org.orekit.attitudes.AttitudeProviderModifier;
40  import org.orekit.errors.OrekitException;
41  import org.orekit.errors.OrekitInternalError;
42  import org.orekit.errors.OrekitMessages;
43  import org.orekit.frames.Frame;
44  import org.orekit.orbits.OrbitType;
45  import org.orekit.orbits.PositionAngleType;
46  import org.orekit.propagation.AbstractPropagator;
47  import org.orekit.propagation.BoundedPropagator;
48  import org.orekit.propagation.EphemerisGenerator;
49  import org.orekit.propagation.PropagationType;
50  import org.orekit.propagation.SpacecraftState;
51  import org.orekit.propagation.events.EventDetector;
52  import org.orekit.propagation.events.handlers.EventHandler;
53  import org.orekit.propagation.sampling.OrekitStepHandler;
54  import org.orekit.propagation.sampling.OrekitStepInterpolator;
55  import org.orekit.time.AbsoluteDate;
56  import org.orekit.utils.DataDictionary;
57  
58  import java.util.ArrayList;
59  import java.util.Arrays;
60  import java.util.Collection;
61  import java.util.Collections;
62  import java.util.HashMap;
63  import java.util.LinkedList;
64  import java.util.List;
65  import java.util.Map;
66  import java.util.Queue;
67  
68  
69  /** Common handling of {@link org.orekit.propagation.Propagator Propagator}
70   *  methods for both numerical and semi-analytical propagators.
71   *  @author Luc Maisonobe
72   */
73  public abstract class AbstractIntegratedPropagator extends AbstractPropagator {
74  
75      /** Internal name used for complete secondary state dimension.
76       * @since 11.1
77       */
78      private static final String SECONDARY_DIMENSION = "Orekit-secondary-dimension";
79  
80      /** Event detectors not related to force models. */
81      private final List<EventDetector> detectors;
82  
83      /** Step handlers dedicated to ephemeris generation. */
84      private final List<StoringStepHandler> ephemerisGenerators;
85  
86      /** Integrator selected by the user for the orbital extrapolation process. */
87      private final ODEIntegrator integrator;
88  
89      /** Offsets of secondary states managed by {@link AdditionalDerivativesProvider}.
90       * @since 11.1
91       */
92      private final Map<String, Integer> secondaryOffsets;
93  
94      /** Additional derivatives providers.
95       * @since 11.1
96       */
97      private final List<AdditionalDerivativesProvider> additionalDerivativesProviders;
98  
99      /** Map of secondary equation offset in main
100     /** Counter for differential equations calls. */
101     private int calls;
102 
103     /** Mapper between raw double components and space flight dynamics objects. */
104     private StateMapper stateMapper;
105 
106     /**
107      * Attitude provider when evaluating derivatives. Can be a frozen one for performance.
108      * @since 12.1
109      */
110     private AttitudeProvider attitudeProviderForDerivatives;
111 
112     /**
113      * Attitude provider with frozen rates, used when possible for performance.
114      * @since 13.1
115      */
116     private AttitudeProvider frozenAttitudeProvider;
117 
118     /** Flag for resetting the state at end of propagation. */
119     private boolean resetAtEnd;
120 
121     /** Type of orbit to output (mean or osculating) <br/>
122      * <p>
123      * This is used only in the case of semi-analytical propagators where there is a clear separation between
124      * mean and short periodic elements. It is ignored by the Numerical propagator.
125      * </p>
126      */
127     private final PropagationType propagationType;
128 
129     /** Build a new instance.
130      * @param integrator numerical integrator to use for propagation.
131      * @param propagationType type of orbit to output (mean or osculating).
132      */
133     protected AbstractIntegratedPropagator(final ODEIntegrator integrator, final PropagationType propagationType) {
134         detectors                      = new ArrayList<>();
135         ephemerisGenerators            = new ArrayList<>();
136         additionalDerivativesProviders = new ArrayList<>();
137         this.secondaryOffsets          = new HashMap<>();
138         this.integrator                = integrator;
139         this.propagationType           = propagationType;
140         this.resetAtEnd                = true;
141     }
142 
143     /** Allow/disallow resetting the initial state at end of propagation.
144      * <p>
145      * By default, at the end of the propagation, the propagator resets the initial state
146      * to the final state, thus allowing a new propagation to be started from there without
147      * recomputing the part already performed. Calling this method with {@code resetAtEnd} set
148      * to false changes prevents such reset.
149      * </p>
150      * @param resetAtEnd if true, at end of each propagation, the {@link
151      * #getInitialState() initial state} will be reset to the final state of
152      * the propagation, otherwise the initial state will be preserved
153      * @since 9.0
154      */
155     public void setResetAtEnd(final boolean resetAtEnd) {
156         this.resetAtEnd = resetAtEnd;
157     }
158 
159     /** Getter for the resetting flag regarding initial state.
160      * @return resetting flag
161      * @since 12.0
162      */
163     public boolean getResetAtEnd() {
164         return this.resetAtEnd;
165     }
166 
167     /**
168      * Getter for the frozen attitude provider, used for performance when possible.
169      * @return frozen attitude provider
170      * @since 13.1
171      */
172     protected AttitudeProvider getFrozenAttitudeProvider() {
173         return frozenAttitudeProvider;
174     }
175 
176     /**
177      * Method called when initializing the attitude provider used when evaluating derivatives.
178      * @return attitude provider for derivatives
179      */
180     protected AttitudeProvider initializeAttitudeProviderForDerivatives() {
181         return getAttitudeProvider();
182     }
183 
184     /** Initialize the mapper. */
185     protected void initMapper() {
186         stateMapper = createMapper(null, Double.NaN, null, null, null, null);
187     }
188 
189     /** Get the integrator's name.
190      * @return name of underlying integrator
191      * @since 12.0
192      */
193     public String getIntegratorName() {
194         return integrator.getName();
195     }
196 
197     /**  {@inheritDoc} */
198     @Override
199     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
200         super.setAttitudeProvider(attitudeProvider);
201         frozenAttitudeProvider = AttitudeProviderModifier.getFrozenAttitudeProvider(attitudeProvider);
202         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
203                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
204                                    attitudeProvider, stateMapper.getFrame());
205     }
206 
207     /** Set propagation orbit type.
208      * @param orbitType orbit type to use for propagation, null for
209      * propagating using {@link org.orekit.utils.AbsolutePVCoordinates AbsolutePVCoordinates}
210      * rather than {@link org.orekit.orbits Orbit}
211      */
212     protected void setOrbitType(final OrbitType orbitType) {
213         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
214                                    orbitType, stateMapper.getPositionAngleType(),
215                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
216     }
217 
218     /** Get propagation parameter type.
219      * @return orbit type used for propagation, null for
220      * propagating using {@link org.orekit.utils.AbsolutePVCoordinates AbsolutePVCoordinates}
221      * rather than {@link org.orekit.orbits Orbit}
222      */
223     protected OrbitType getOrbitType() {
224         return stateMapper.getOrbitType();
225     }
226 
227     /** Get the propagation type.
228      * @return propagation type.
229      * @since 11.1
230      */
231     public PropagationType getPropagationType() {
232         return propagationType;
233     }
234 
235     /** Set position angle type.
236      * <p>
237      * The position parameter type is meaningful only if {@link
238      * #getOrbitType() propagation orbit type}
239      * support it. As an example, it is not meaningful for propagation
240      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
241      * </p>
242      * @param positionAngleType angle type to use for propagation
243      */
244     protected void setPositionAngleType(final PositionAngleType positionAngleType) {
245         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
246                                    stateMapper.getOrbitType(), positionAngleType,
247                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
248     }
249 
250     /** Get propagation parameter type.
251      * @return angle type to use for propagation
252      */
253     protected PositionAngleType getPositionAngleType() {
254         return stateMapper.getPositionAngleType();
255     }
256 
257     /** Set the central attraction coefficient μ.
258      * @param mu central attraction coefficient (m³/s²)
259      */
260     public void setMu(final double mu) {
261         stateMapper = createMapper(stateMapper.getReferenceDate(), mu,
262                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
263                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
264     }
265 
266     /** Get the central attraction coefficient μ.
267      * @return mu central attraction coefficient (m³/s²)
268      * @see #setMu(double)
269      */
270     public double getMu() {
271         return stateMapper.getMu();
272     }
273 
274     /** Get the number of calls to the differential equations computation method.
275      * <p>The number of calls is reset each time the {@link #propagate(AbsoluteDate)}
276      * method is called.</p>
277      * @return number of calls to the differential equations computation method
278      */
279     public int getCalls() {
280         return calls;
281     }
282 
283     /** {@inheritDoc} */
284     @Override
285     public boolean isAdditionalDataManaged(final String name) {
286 
287         // first look at already integrated states
288         if (super.isAdditionalDataManaged(name)) {
289             return true;
290         }
291 
292         // then look at states we integrate ourselves
293         for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
294             if (provider.getName().equals(name)) {
295                 return true;
296             }
297         }
298 
299         return false;
300     }
301 
302     /** {@inheritDoc} */
303     @Override
304     public String[] getManagedAdditionalData() {
305         final String[] alreadyIntegrated = super.getManagedAdditionalData();
306         final String[] managed = new String[alreadyIntegrated.length + additionalDerivativesProviders.size()];
307         System.arraycopy(alreadyIntegrated, 0, managed, 0, alreadyIntegrated.length);
308         for (int i = 0; i < additionalDerivativesProviders.size(); ++i) {
309             managed[i + alreadyIntegrated.length] = additionalDerivativesProviders.get(i).getName();
310         }
311         return managed;
312     }
313 
314     /** Add a provider for user-specified state derivatives to be integrated along with the orbit propagation.
315      * @param provider provider for additional derivatives
316      * @see #addAdditionalDataProvider(org.orekit.propagation.AdditionalDataProvider)
317      * @since 11.1
318      */
319     public void addAdditionalDerivativesProvider(final AdditionalDerivativesProvider provider) {
320 
321         // check if the name is already used
322         if (this.isAdditionalDataManaged(provider.getName())) {
323             // these derivatives are already registered, complain
324             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_NAME_ALREADY_IN_USE,
325                                       provider.getName());
326         }
327 
328         // this is really a new set of derivatives, add it
329         additionalDerivativesProviders.add(provider);
330 
331         secondaryOffsets.clear();
332 
333     }
334 
335     /** Get an unmodifiable list of providers for additional derivatives.
336      * @return providers for the additional derivatives
337      * @since 11.1
338      */
339     public List<AdditionalDerivativesProvider> getAdditionalDerivativesProviders() {
340         return Collections.unmodifiableList(additionalDerivativesProviders);
341     }
342 
343     /**
344      * Remove additional derivatives provider.
345      * @param name provider's name.
346      * @since 13.1
347      */
348     protected void removeAdditionalDerivativesProvider(final String name) {
349         additionalDerivativesProviders.removeIf(provider -> provider.getName().equals(name));
350     }
351 
352     /** {@inheritDoc} */
353     public void addEventDetector(final EventDetector detector) {
354         detectors.add(detector);
355     }
356 
357     /** {@inheritDoc} */
358     public Collection<EventDetector> getEventDetectors() {
359         return Collections.unmodifiableCollection(detectors);
360     }
361 
362     /** {@inheritDoc} */
363     public void clearEventsDetectors() {
364         detectors.clear();
365     }
366 
367     /** Set up all user defined event detectors.
368      */
369     protected void setUpUserEventDetectors() {
370         for (final EventDetector detector : detectors) {
371             setUpEventDetector(integrator, detector);
372         }
373     }
374 
375     /** Wrap an Orekit event detector and register it to the integrator.
376      * @param integ integrator into which event detector should be registered
377      * @param detector event detector to wrap
378      */
379     protected void setUpEventDetector(final ODEIntegrator integ, final EventDetector detector) {
380         integ.addEventDetector(new AdaptedEventDetector(detector));
381     }
382 
383     /**
384      * Clear the ephemeris generators.
385      * @since 13.0
386      */
387     public void clearEphemerisGenerators() {
388         ephemerisGenerators.clear();
389     }
390 
391     /** {@inheritDoc} */
392     @Override
393     public EphemerisGenerator getEphemerisGenerator() {
394         final StoringStepHandler storingHandler = new StoringStepHandler();
395         ephemerisGenerators.add(storingHandler);
396         return storingHandler;
397     }
398 
399     /** Create a mapper between raw double components and spacecraft state.
400     /** Simple constructor.
401      * <p>
402      * The position parameter type is meaningful only if {@link
403      * #getOrbitType() propagation orbit type}
404      * support it. As an example, it is not meaningful for propagation
405      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
406      * </p>
407      * @param referenceDate reference date
408      * @param mu central attraction coefficient (m³/s²)
409      * @param orbitType orbit type to use for mapping
410      * @param positionAngleType angle type to use for propagation
411      * @param attitudeProvider attitude provider
412      * @param frame inertial frame
413      * @return new mapper
414      */
415     protected abstract StateMapper createMapper(AbsoluteDate referenceDate, double mu,
416                                                 OrbitType orbitType, PositionAngleType positionAngleType,
417                                                 AttitudeProvider attitudeProvider, Frame frame);
418 
419     /** Get the differential equations to integrate (for main state only).
420      * @param integ numerical integrator to use for propagation.
421      * @return differential equations for main state
422      */
423     protected abstract MainStateEquations getMainStateEquations(ODEIntegrator integ);
424 
425     /** {@inheritDoc} */
426     @Override
427     public SpacecraftState propagate(final AbsoluteDate target) {
428         if (getStartDate() == null) {
429             if (getInitialState() == null) {
430                 throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
431             }
432             setStartDate(getInitialState().getDate());
433         }
434         return propagate(getStartDate(), target);
435     }
436 
437     /** {@inheritDoc} */
438     public SpacecraftState propagate(final AbsoluteDate tStart, final AbsoluteDate tEnd) {
439 
440         if (getInitialState() == null) {
441             throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
442         }
443 
444         // make sure the integrator will be reset properly even if we change its events handlers and step handlers
445         try (IntegratorResetter resetter = new IntegratorResetter(integrator)) {
446 
447             // prepare handling of STM and Jacobian matrices
448             setUpStmAndJacobianGenerators();
449 
450             // Initialize additional data
451             initializeAdditionalData(tEnd);
452 
453             if (!tStart.equals(getInitialState().getDate())) {
454                 // if propagation start date is not initial date,
455                 // propagate from initial to start date without event detection
456                 try (IntegratorResetter startResetter = new IntegratorResetter(integrator)) {
457                     integrateDynamics(tStart, true);
458                 }
459             }
460 
461             // set up events added by user
462             setUpUserEventDetectors();
463 
464             // set up step handlers
465             for (final OrekitStepHandler handler : getMultiplexer().getHandlers()) {
466                 integrator.addStepHandler(new AdaptedStepHandler(handler));
467             }
468             for (final StoringStepHandler generator : ephemerisGenerators) {
469                 generator.setEndDate(tEnd);
470                 integrator.addStepHandler(generator);
471             }
472 
473             // propagate from start date to end date with event detection
474             final SpacecraftState finalState = integrateDynamics(tEnd, false);
475 
476             // Finalize event detectors
477             getEventDetectors().forEach(detector -> detector.finish(finalState));
478 
479             return finalState;
480         }
481 
482     }
483 
484     /** Reset initial state with a given propagation type.
485      *
486      * <p> By default this method returns the same as {@link #resetInitialState(SpacecraftState)}
487      * <p> Its purpose is mostly to be derived in DSSTPropagator
488      *
489      * @param state new initial state to consider
490      * @param stateType type of the new state (mean or osculating)
491      * @since 12.1.3
492      */
493     public void resetInitialState(final SpacecraftState state, final PropagationType stateType) {
494         // Default behavior, do not take propagation type into account
495         resetInitialState(state);
496     }
497 
498     /** Set up State Transition Matrix and Jacobian matrix handling.
499      * @since 11.1
500      */
501     protected void setUpStmAndJacobianGenerators() {
502         // nothing to do by default
503     }
504 
505     /** Propagation with or without event detection.
506      * @param tEnd target date to which orbit should be propagated
507      * @param forceResetAtEnd flag to force resetting state and date after integration
508      * @return state at end of propagation
509      */
510     private SpacecraftState integrateDynamics(final AbsoluteDate tEnd, final boolean forceResetAtEnd) {
511         try {
512 
513             initializePropagation();
514 
515             if (getInitialState().getDate().equals(tEnd)) {
516                 // don't extrapolate
517                 return getInitialState();
518             }
519 
520             // space dynamics view
521             stateMapper = createMapper(getInitialState().getDate(), stateMapper.getMu(),
522                                        stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
523                                        stateMapper.getAttitudeProvider(), getInitialState().getFrame());
524             attitudeProviderForDerivatives = initializeAttitudeProviderForDerivatives();
525 
526             if (Double.isNaN(getMu())) {
527                 setMu(getInitialState().isOrbitDefined() ? getInitialState().getOrbit().getMu() : Double.NaN);
528             }
529 
530             if (getInitialState().getMass() <= 0.0) {
531                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS,
532                                           getInitialState().getMass());
533             }
534 
535             // convertFromOrekit space flight dynamics API to math API
536             final SpacecraftState initialIntegrationState = getInitialIntegrationState();
537             final ODEState mathInitialState = createInitialState(initialIntegrationState);
538             final ExpandableODE mathODE = createODE(integrator);
539 
540             // mathematical integration
541             final ODEStateAndDerivative mathFinalState;
542             beforeIntegration(initialIntegrationState, tEnd);
543             mathFinalState = integrator.integrate(mathODE, mathInitialState,
544                                                   tEnd.durationFrom(getInitialState().getDate()));
545             afterIntegration();
546 
547             // get final state
548             SpacecraftState finalState =
549                             stateMapper.mapArrayToState(stateMapper.mapDoubleToDate(mathFinalState.getTime(),
550                                                                                     tEnd),
551                                                         mathFinalState.getPrimaryState(),
552                                                         mathFinalState.getPrimaryDerivative(),
553                                                         propagationType);
554 
555             finalState = updateAdditionalStatesAndDerivatives(finalState, mathFinalState);
556 
557             if (resetAtEnd || forceResetAtEnd) {
558                 resetInitialState(finalState, propagationType);
559                 setStartDate(finalState.getDate());
560             }
561 
562             return finalState;
563 
564         } catch (MathRuntimeException mre) {
565             throw OrekitException.unwrap(mre);
566         }
567     }
568 
569     /**
570      * Returns an updated version of the inputted state with additional states, including
571      * from derivatives providers.
572      * @param originalState input state
573      * @param os ODE state and derivative
574      * @return new state
575      * @since 12.1
576      */
577     private SpacecraftState updateAdditionalStatesAndDerivatives(final SpacecraftState originalState,
578                                                                  final ODEStateAndDerivative os) {
579         SpacecraftState updatedState = originalState;
580         if (os.getNumberOfSecondaryStates() > 0) {
581             final double[] secondary           = os.getSecondaryState(1);
582             final double[] secondaryDerivative = os.getSecondaryDerivative(1);
583             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
584                 final String name      = provider.getName();
585                 final int    offset    = secondaryOffsets.get(name);
586                 final int    dimension = provider.getDimension();
587                 updatedState = updatedState.addAdditionalData(name, Arrays.copyOfRange(secondary, offset, offset + dimension));
588                 updatedState = updatedState.addAdditionalStateDerivative(name, Arrays.copyOfRange(secondaryDerivative, offset, offset + dimension));
589             }
590         }
591         return updateAdditionalData(updatedState);
592     }
593 
594     /** Get the initial state for integration.
595      * @return initial state for integration
596      */
597     protected SpacecraftState getInitialIntegrationState() {
598         return getInitialState();
599     }
600 
601     /** Create an initial state.
602      * @param initialState initial state in flight dynamics world
603      * @return initial state in mathematics world
604      */
605     private ODEState createInitialState(final SpacecraftState initialState) {
606 
607         // retrieve initial state
608         final double[] primary = new double[getBasicDimension()];
609         stateMapper.mapStateToArray(initialState, primary, null);
610 
611         if (secondaryOffsets.isEmpty()) {
612             // compute dimension of the secondary state
613             int offset = 0;
614             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
615                 secondaryOffsets.put(provider.getName(), offset);
616                 offset += provider.getDimension();
617             }
618             secondaryOffsets.put(SECONDARY_DIMENSION, offset);
619         }
620 
621         return new ODEState(0.0, primary, secondary(initialState));
622 
623     }
624 
625     /** Create secondary state.
626      * @param state spacecraft state
627      * @return secondary state
628      * @since 11.1
629      */
630     private double[][] secondary(final SpacecraftState state) {
631 
632         if (secondaryOffsets.isEmpty()) {
633             return null;
634         }
635 
636         final double[][] secondary = new double[1][secondaryOffsets.get(SECONDARY_DIMENSION)];
637         for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
638             final String   name       = provider.getName();
639             final int      offset     = secondaryOffsets.get(name);
640             final double[] additional = state.getAdditionalState(name);
641             System.arraycopy(additional, 0, secondary[0], offset, additional.length);
642         }
643 
644         return secondary;
645 
646     }
647 
648     /** Create an ODE with all equations.
649      * @param integ numerical integrator to use for propagation.
650      * @return a new ode
651      */
652     private ExpandableODE createODE(final ODEIntegrator integ) {
653 
654         final ExpandableODE ode =
655                 new ExpandableODE(new ConvertedMainStateEquations(getMainStateEquations(integ)));
656 
657         // secondary part of the ODE
658         if (!additionalDerivativesProviders.isEmpty()) {
659             ode.addSecondaryEquations(new ConvertedSecondaryStateEquations());
660         }
661 
662         return ode;
663 
664     }
665 
666     /** Method called just before integration.
667      * <p>
668      * The default implementation does nothing, it may be specialized in subclasses.
669      * </p>
670      * @param initialState initial state
671      * @param tEnd target date at which state should be propagated
672      */
673     protected void beforeIntegration(final SpacecraftState initialState,
674                                      final AbsoluteDate tEnd) {
675         // do nothing by default
676     }
677 
678     /** Method called just after integration.
679      * <p>
680      * The default implementation does nothing, it may be specialized in subclasses.
681      * </p>
682      */
683     protected void afterIntegration() {
684         // do nothing by default
685     }
686 
687     /** Get state vector dimension without additional parameters.
688      * @return state vector dimension without additional parameters.
689      */
690     public int getBasicDimension() {
691         return 7;
692     }
693 
694     /** Get the integrator used by the propagator.
695      * @return the integrator.
696      */
697     protected ODEIntegrator getIntegrator() {
698         return integrator;
699     }
700 
701     /** Convert a state from mathematical world to space flight dynamics world.
702      * @param os mathematical state
703      * @return space flight dynamics state
704      */
705     private SpacecraftState convertToOrekitWithAdditional(final ODEStateAndDerivative os) {
706         final SpacecraftState s = convertToOrekitWithoutAdditional(os);
707         return updateAdditionalStatesAndDerivatives(s, os);
708     }
709 
710     /** Convert a state from mathematical world to space flight dynamics world without additional data and derivatives.
711      * @param os mathematical state
712      * @return space flight dynamics state
713      * @since 13.1
714      */
715     private SpacecraftState convertToOrekitWithoutAdditional(final ODEStateAndDerivative os) {
716         return stateMapper.mapArrayToState(os.getTime(), os.getPrimaryState(),
717             os.getPrimaryDerivative(), propagationType);
718     }
719 
720     /** Differential equations for the main state (orbit, attitude and mass). */
721     public interface MainStateEquations {
722 
723         /**
724          * Initialize the equations at the start of propagation. This method will be
725          * called before any calls to {@link #computeDerivatives(SpacecraftState)}.
726          *
727          * <p> The default implementation of this method does nothing.
728          *
729          * @param initialState initial state information at the start of propagation.
730          * @param target       date of propagation. Not equal to {@code
731          *                     initialState.getDate()}.
732          */
733         default void init(final SpacecraftState initialState, final AbsoluteDate target) {
734         }
735 
736         /** Compute differential equations for main state.
737          * @param state current state
738          * @return derivatives of main state
739          */
740         double[] computeDerivatives(SpacecraftState state);
741 
742     }
743 
744     /** Differential equations for the main state (orbit, attitude and mass), with converted API. */
745     private class ConvertedMainStateEquations implements OrdinaryDifferentialEquation {
746 
747         /** Main state equations. */
748         private final MainStateEquations main;
749 
750         /** Simple constructor.
751          * @param main main state equations
752          */
753         ConvertedMainStateEquations(final MainStateEquations main) {
754             this.main = main;
755             calls = 0;
756         }
757 
758         /** {@inheritDoc} */
759         public int getDimension() {
760             return getBasicDimension();
761         }
762 
763         @Override
764         public void init(final double t0, final double[] y0, final double finalTime) {
765             // update space dynamics view
766             SpacecraftState initialState = stateMapper.mapArrayToState(t0, y0, null, PropagationType.MEAN);
767             initialState = updateAdditionalData(initialState);
768             initialState = updateStatesFromAdditionalDerivativesIfKnown(initialState);
769             final AbsoluteDate target = stateMapper.mapDoubleToDate(finalTime);
770             main.init(initialState, target);
771             attitudeProviderForDerivatives = initializeAttitudeProviderForDerivatives();
772         }
773 
774         /**
775          * Returns an updated version of the inputted state, with additional states from
776          * derivatives providers as given in the stored initial state.
777          * @param originalState input state
778          * @return new state
779          * @since 12.1
780          */
781         private SpacecraftState updateStatesFromAdditionalDerivativesIfKnown(final SpacecraftState originalState) {
782             SpacecraftState updatedState = originalState;
783             final SpacecraftState storedInitialState = getInitialState();
784             final double originalTime = stateMapper.mapDateToDouble(originalState.getDate());
785             if (storedInitialState != null && stateMapper.mapDateToDouble(storedInitialState.getDate()) == originalTime) {
786                 for (final AdditionalDerivativesProvider provider: additionalDerivativesProviders) {
787                     final String name = provider.getName();
788                     final double[] value = storedInitialState.getAdditionalState(name);
789                     updatedState = updatedState.addAdditionalData(name, value);
790                 }
791             }
792             return updatedState;
793         }
794 
795         /** {@inheritDoc} */
796         public double[] computeDerivatives(final double t, final double[] y) {
797 
798             // increment calls counter
799             ++calls;
800 
801             // update space dynamics view
802             stateMapper.setAttitudeProvider(attitudeProviderForDerivatives);
803             SpacecraftState currentState = stateMapper.mapArrayToState(t, y, null, PropagationType.MEAN);
804             stateMapper.setAttitudeProvider(getAttitudeProvider());
805 
806             currentState = updateAdditionalData(currentState);
807             // compute main state differentials
808             return main.computeDerivatives(currentState);
809 
810         }
811 
812     }
813 
814     /** Differential equations for the secondary state (Jacobians, user variables ...), with converted API. */
815     private class ConvertedSecondaryStateEquations implements SecondaryODE {
816 
817         /** Dimension of the combined additional states. */
818         private final int combinedDimension;
819 
820         /** Simple constructor.
821           */
822         ConvertedSecondaryStateEquations() {
823             this.combinedDimension = secondaryOffsets.get(SECONDARY_DIMENSION);
824         }
825 
826         /** {@inheritDoc} */
827         @Override
828         public int getDimension() {
829             return combinedDimension;
830         }
831 
832         /** {@inheritDoc} */
833         @Override
834         public void init(final double t0, final double[] primary0,
835                          final double[] secondary0, final double finalTime) {
836             // update space dynamics view
837             final SpacecraftState initialState = convert(t0, primary0, null, secondary0);
838 
839             final AbsoluteDate target = stateMapper.mapDoubleToDate(finalTime);
840             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
841                 provider.init(initialState, target);
842             }
843 
844         }
845 
846         /** {@inheritDoc} */
847         @Override
848         public double[] computeDerivatives(final double t, final double[] primary,
849                                            final double[] primaryDot, final double[] secondary) {
850 
851             // update space dynamics view
852             // the integrable generators generate method will be called here,
853             // according to the generators yield order
854             SpacecraftState updated = convert(t, primary, primaryDot, secondary);
855 
856             // set up queue for equations
857             final Queue<AdditionalDerivativesProvider> pending = new LinkedList<>(additionalDerivativesProviders);
858 
859             // gather the derivatives from all additional equations, taking care of dependencies
860             final double[] secondaryDot = new double[combinedDimension];
861             int yieldCount = 0;
862             while (!pending.isEmpty()) {
863                 final AdditionalDerivativesProvider provider = pending.remove();
864                 if (provider.yields(updated)) {
865                     // this provider has to wait for another one,
866                     // we put it again in the pending queue
867                     pending.add(provider);
868                     if (++yieldCount >= pending.size()) {
869                         // all pending providers yielded!, they probably need data not yet initialized
870                         // we let the propagation proceed, if these data are really needed right now
871                         // an appropriate exception will be triggered when caller tries to access them
872                         break;
873                     }
874                 } else {
875                     // we can use these equations right now
876                     final String              name           = provider.getName();
877                     final int                 offset         = secondaryOffsets.get(name);
878                     final int                 dimension      = provider.getDimension();
879                     final CombinedDerivatives derivatives    = provider.combinedDerivatives(updated);
880                     final double[]            additionalPart = derivatives.getAdditionalDerivatives();
881                     final double[]            mainPart       = derivatives.getMainStateDerivativesIncrements();
882                     System.arraycopy(additionalPart, 0, secondaryDot, offset, dimension);
883                     updated = updated.addAdditionalStateDerivative(name, additionalPart);
884                     if (mainPart != null) {
885                         // this equation does change the main state derivatives
886                         for (int i = 0; i < mainPart.length; ++i) {
887                             primaryDot[i] += mainPart[i];
888                         }
889                     }
890                     yieldCount = 0;
891                 }
892             }
893 
894             return secondaryDot;
895 
896         }
897 
898         /** Convert mathematical view to space view.
899          * @param t current value of the independent <I>time</I> variable
900          * @param primary array containing the current value of the primary state vector
901          * @param primaryDot array containing the derivative of the primary state vector
902          * @param secondary array containing the current value of the secondary state vector
903          * @return space view of the state
904          */
905         private SpacecraftState convert(final double t, final double[] primary,
906                                         final double[] primaryDot, final double[] secondary) {
907 
908             SpacecraftState initialState = stateMapper.mapArrayToState(t, primary, primaryDot, PropagationType.MEAN);
909 
910             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
911                 final String name      = provider.getName();
912                 final int    offset    = secondaryOffsets.get(name);
913                 final int    dimension = provider.getDimension();
914                 initialState = initialState.addAdditionalData(name, Arrays.copyOfRange(secondary, offset, offset + dimension));
915             }
916 
917             return updateAdditionalData(initialState);
918 
919         }
920 
921     }
922 
923     /** Adapt an {@link org.orekit.propagation.events.EventDetector}
924      * to Hipparchus {@link org.hipparchus.ode.events.ODEEventDetector} interface.
925      * @author Fabien Maussion
926      */
927     private class AdaptedEventDetector implements ODEEventDetector {
928 
929         /** Underlying event detector. */
930         private final EventDetector detector;
931 
932         /** Underlying event handler.
933          * @since 12.0
934          */
935         private final EventHandler handler;
936 
937         /** Time of the previous call to g. */
938         private double lastT;
939 
940         /** Value from the previous call to g. */
941         private double lastG;
942 
943         /** Build a wrapped event detector.
944          * @param detector event detector to wrap
945         */
946         AdaptedEventDetector(final EventDetector detector) {
947             this.detector = detector;
948             this.handler  = detector.getHandler();
949             this.lastT    = Double.NaN;
950             this.lastG    = Double.NaN;
951         }
952 
953         /** {@inheritDoc} */
954         @Override
955         public AdaptableInterval getMaxCheckInterval() {
956             return (state, isForward) -> detector.getMaxCheckInterval().currentInterval(convertToOrekitForEventFunction(state), isForward);
957         }
958 
959         /** {@inheritDoc} */
960         @Override
961         public int getMaxIterationCount() {
962             return detector.getMaxIterationCount();
963         }
964 
965         /** {@inheritDoc} */
966         @Override
967         public BracketedUnivariateSolver<UnivariateFunction> getSolver() {
968             return new BracketingNthOrderBrentSolver(0, detector.getThreshold(), 0, 5);
969         }
970 
971         /** {@inheritDoc} */
972         @Override
973         public void init(final ODEStateAndDerivative s0, final double t) {
974             detector.init(convertToOrekitWithAdditional(s0), stateMapper.mapDoubleToDate(t));
975             this.lastT = Double.NaN;
976             this.lastG = Double.NaN;
977         }
978 
979         /** {@inheritDoc} */
980         @Override
981         public void reset(final ODEStateAndDerivative intermediateState, final double finalTime) {
982             detector.reset(convertToOrekitWithAdditional(intermediateState), stateMapper.mapDoubleToDate(finalTime));
983             this.lastT = Double.NaN;
984             this.lastG = Double.NaN;
985         }
986 
987         /** {@inheritDoc} */
988         public double g(final ODEStateAndDerivative s) {
989             if (!Precision.equals(lastT, s.getTime(), 0)) {
990                 lastT = s.getTime();
991                 lastG = detector.g(convertToOrekitForEventFunction(s));
992             }
993             return lastG;
994         }
995 
996         /**
997          * Convert state from Hipparchus to Orekit format.
998          * @param s state vector
999          * @return Orekit state
1000          */
1001         private SpacecraftState convertToOrekitForEventFunction(final ODEStateAndDerivative s) {
1002             if (!this.detector.dependsOnTimeOnly()) {
1003                 return convertToOrekitWithAdditional(s);
1004             } else {
1005                 // event function only needs time
1006                 stateMapper.setAttitudeProvider(getFrozenAttitudeProvider());
1007                 final SpacecraftState converted = convertToOrekitWithoutAdditional(s);
1008                 stateMapper.setAttitudeProvider(getAttitudeProvider());
1009                 return converted;
1010             }
1011         }
1012 
1013         /** {@inheritDoc} */
1014         public ODEEventHandler getHandler() {
1015 
1016             return new ODEEventHandler() {
1017 
1018                 /** {@inheritDoc} */
1019                 public Action eventOccurred(final ODEStateAndDerivative s, final ODEEventDetector d, final boolean increasing) {
1020                     return handler.eventOccurred(convertToOrekitWithAdditional(s), detector, increasing);
1021                 }
1022 
1023                 /** {@inheritDoc} */
1024                 @Override
1025                 public ODEState resetState(final ODEEventDetector d, final ODEStateAndDerivative s) {
1026 
1027                     final SpacecraftState oldState = convertToOrekitWithAdditional(s);
1028                     final SpacecraftState newState = handler.resetState(detector, oldState);
1029                     stateChanged(newState);
1030 
1031                     // main part
1032                     final double[] primary    = new double[s.getPrimaryStateDimension()];
1033                     stateMapper.mapStateToArray(newState, primary, null);
1034 
1035                     // secondary part
1036                     final double[][] secondary = new double[1][secondaryOffsets.get(SECONDARY_DIMENSION)];
1037                     for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
1038                         final String name      = provider.getName();
1039                         final int    offset    = secondaryOffsets.get(name);
1040                         final int    dimension = provider.getDimension();
1041                         System.arraycopy(newState.getAdditionalState(name), 0, secondary[0], offset, dimension);
1042                     }
1043 
1044                     return new ODEState(newState.getDate().durationFrom(getStartDate()),
1045                                         primary, secondary);
1046 
1047                 }
1048 
1049             };
1050         }
1051 
1052     }
1053 
1054     /** Adapt an {@link org.orekit.propagation.sampling.OrekitStepHandler}
1055      * to Hipparchus {@link ODEStepHandler} interface.
1056      * @author Luc Maisonobe
1057      */
1058     private class AdaptedStepHandler implements ODEStepHandler {
1059 
1060         /** Underlying handler. */
1061         private final OrekitStepHandler handler;
1062 
1063         /** Build an instance.
1064          * @param handler underlying handler to wrap
1065          */
1066         AdaptedStepHandler(final OrekitStepHandler handler) {
1067             this.handler = handler;
1068         }
1069 
1070         /** {@inheritDoc} */
1071         @Override
1072         public void init(final ODEStateAndDerivative s0, final double t) {
1073             handler.init(convertToOrekitWithAdditional(s0), stateMapper.mapDoubleToDate(t));
1074         }
1075 
1076         /** {@inheritDoc} */
1077         @Override
1078         public void handleStep(final ODEStateInterpolator interpolator) {
1079             handler.handleStep(new AdaptedStepInterpolator(interpolator));
1080         }
1081 
1082         /** {@inheritDoc} */
1083         @Override
1084         public void finish(final ODEStateAndDerivative finalState) {
1085             handler.finish(convertToOrekitWithAdditional(finalState));
1086         }
1087 
1088     }
1089 
1090     /** Adapt an Hipparchus {@link ODEStateInterpolator}
1091      * to an orekit {@link OrekitStepInterpolator} interface.
1092      * @author Luc Maisonobe
1093      */
1094     private class AdaptedStepInterpolator implements OrekitStepInterpolator {
1095 
1096         /** Underlying raw rawInterpolator. */
1097         private final ODEStateInterpolator mathInterpolator;
1098 
1099         /** Simple constructor.
1100          * @param mathInterpolator underlying raw interpolator
1101          */
1102         AdaptedStepInterpolator(final ODEStateInterpolator mathInterpolator) {
1103             this.mathInterpolator = mathInterpolator;
1104         }
1105 
1106         /** {@inheritDoc}} */
1107         @Override
1108         public SpacecraftState getPreviousState() {
1109             return convertToOrekitWithAdditional(mathInterpolator.getPreviousState());
1110         }
1111 
1112         /** {@inheritDoc}} */
1113         @Override
1114         public boolean isPreviousStateInterpolated() {
1115             return mathInterpolator.isPreviousStateInterpolated();
1116         }
1117 
1118         /** {@inheritDoc}} */
1119         @Override
1120         public SpacecraftState getCurrentState() {
1121             return convertToOrekitWithAdditional(mathInterpolator.getCurrentState());
1122         }
1123 
1124         /** {@inheritDoc}} */
1125         @Override
1126         public boolean isCurrentStateInterpolated() {
1127             return mathInterpolator.isCurrentStateInterpolated();
1128         }
1129 
1130         /** {@inheritDoc}} */
1131         @Override
1132         public SpacecraftState getInterpolatedState(final AbsoluteDate date) {
1133             return convertToOrekitWithAdditional(mathInterpolator.getInterpolatedState(date.durationFrom(stateMapper.getReferenceDate())));
1134         }
1135 
1136         /** {@inheritDoc}} */
1137         @Override
1138         public boolean isForward() {
1139             return mathInterpolator.isForward();
1140         }
1141 
1142         /** {@inheritDoc}} */
1143         @Override
1144         public AdaptedStepInterpolator restrictStep(final SpacecraftState newPreviousState,
1145                                                     final SpacecraftState newCurrentState) {
1146             try {
1147                 final AbstractODEStateInterpolator aosi = (AbstractODEStateInterpolator) mathInterpolator;
1148                 return new AdaptedStepInterpolator(aosi.restrictStep(convertFromOrekit(newPreviousState),
1149                                                                      convertFromOrekit(newCurrentState)));
1150             } catch (ClassCastException cce) {
1151                 // this should never happen
1152                 throw new OrekitInternalError(cce);
1153             }
1154         }
1155 
1156 
1157         /** Convert a state from space flight dynamics world to mathematical world.
1158          * @param state space flight dynamics state
1159          * @return mathematical state
1160          */
1161         private ODEStateAndDerivative convertFromOrekit(final SpacecraftState state) {
1162 
1163             // retrieve initial state
1164             final double[] primary    = new double[getBasicDimension()];
1165             final double[] primaryDot = new double[getBasicDimension()];
1166             stateMapper.mapStateToArray(state, primary, primaryDot);
1167 
1168             // secondary part of the ODE
1169             final double[][] secondary           = secondary(state);
1170             final double[][] secondaryDerivative = secondaryDerivative(state);
1171 
1172             return new ODEStateAndDerivative(stateMapper.mapDateToDouble(state.getDate()),
1173                     primary, primaryDot,
1174                     secondary, secondaryDerivative);
1175 
1176         }
1177 
1178         /** Create secondary state derivative.
1179          * @param state spacecraft state
1180          * @return secondary state derivative
1181          * @since 11.1
1182          */
1183         private double[][] secondaryDerivative(final SpacecraftState state) {
1184 
1185             if (secondaryOffsets.isEmpty()) {
1186                 return null;
1187             }
1188 
1189             final double[][] secondaryDerivative = new double[1][secondaryOffsets.get(SECONDARY_DIMENSION)];
1190             for (final AdditionalDerivativesProvider provider : additionalDerivativesProviders) {
1191                 final String   name       = provider.getName();
1192                 final int      offset     = secondaryOffsets.get(name);
1193                 final double[] additionalDerivative = state.getAdditionalStateDerivative(name);
1194                 System.arraycopy(additionalDerivative, 0, secondaryDerivative[0], offset, additionalDerivative.length);
1195             }
1196 
1197             return secondaryDerivative;
1198 
1199         }
1200     }
1201 
1202     /** Specialized step handler storing interpolators for ephemeris generation.
1203      * @since 11.0
1204      */
1205     private class StoringStepHandler implements ODEStepHandler, EphemerisGenerator {
1206 
1207         /** Underlying raw mathematical model. */
1208         private DenseOutputModel model;
1209 
1210         /** the user supplied end date. Propagation may not end on this date. */
1211         private AbsoluteDate endDate;
1212 
1213         /** Generated ephemeris. */
1214         private BoundedPropagator ephemeris;
1215 
1216         /** Last interpolator handled by the object.*/
1217         private  ODEStateInterpolator lastInterpolator;
1218 
1219         /** Set the end date.
1220          * @param endDate end date
1221          */
1222         public void setEndDate(final AbsoluteDate endDate) {
1223             this.endDate = endDate;
1224         }
1225 
1226         /** {@inheritDoc} */
1227         @Override
1228         public void init(final ODEStateAndDerivative s0, final double t) {
1229 
1230             this.model = new DenseOutputModel();
1231             model.init(s0, t);
1232 
1233             // ephemeris will be generated when last step is processed
1234             this.ephemeris = null;
1235 
1236             this.lastInterpolator = null;
1237 
1238         }
1239 
1240         /** {@inheritDoc} */
1241         @Override
1242         public BoundedPropagator getGeneratedEphemeris() {
1243             // Each time we try to get the ephemeris, rebuild it using the last data.
1244             buildEphemeris();
1245             return ephemeris;
1246         }
1247 
1248         /** {@inheritDoc} */
1249         @Override
1250         public void handleStep(final ODEStateInterpolator interpolator) {
1251             model.handleStep(interpolator);
1252             lastInterpolator = interpolator;
1253         }
1254 
1255         /** {@inheritDoc} */
1256         @Override
1257         public void finish(final ODEStateAndDerivative finalState) {
1258             buildEphemeris();
1259         }
1260 
1261         /** Method used to produce ephemeris at a given time.
1262          * Can be used at multiple times, updating the ephemeris to
1263          * its last state.
1264          */
1265         private void buildEphemeris() {
1266             // buildEphemeris was built in order to allow access to what was previously the finish method.
1267             // This now allows to call it through getGeneratedEphemeris, therefore through an external call,
1268             // which was not previously the case.
1269 
1270             // Update the model's finalTime with the last interpolator.
1271             model.finish(lastInterpolator.getCurrentState());
1272 
1273             // set up the boundary dates
1274             final double tI = model.getInitialTime();
1275             final double tF = model.getFinalTime();
1276             // tI is almost? always zero
1277             final AbsoluteDate startDate =
1278                             stateMapper.mapDoubleToDate(tI);
1279             final AbsoluteDate finalDate =
1280                             stateMapper.mapDoubleToDate(tF, this.endDate);
1281             final AbsoluteDate minDate;
1282             final AbsoluteDate maxDate;
1283             if (tF < tI) {
1284                 minDate = finalDate;
1285                 maxDate = startDate;
1286             } else {
1287                 minDate = startDate;
1288                 maxDate = finalDate;
1289             }
1290 
1291             // get the initial additional states that are not managed
1292             final DataDictionary unmanaged = new DataDictionary();
1293             for (final DataDictionary.Entry initial : getInitialState().getAdditionalDataValues().getData()) {
1294                 if (!AbstractIntegratedPropagator.this.isAdditionalDataManaged(initial.getKey())) {
1295                     // this additional state was in the initial state, but is unknown to the propagator
1296                     // we simply copy its initial value as is
1297                     unmanaged.put(initial.getKey(), initial.getValue());
1298                 }
1299             }
1300 
1301             // get the names of additional states managed by differential equations
1302             final String[] names      = new String[additionalDerivativesProviders.size()];
1303             final int[]    dimensions = new int[additionalDerivativesProviders.size()];
1304             for (int i = 0; i < names.length; ++i) {
1305                 names[i] = additionalDerivativesProviders.get(i).getName();
1306                 dimensions[i] = additionalDerivativesProviders.get(i).getDimension();
1307             }
1308 
1309             // create the ephemeris
1310             ephemeris = new IntegratedEphemeris(startDate, minDate, maxDate,
1311                                                 stateMapper, getAttitudeProvider(), propagationType, model,
1312                                                 unmanaged, getAdditionalDataProviders(),
1313                                                 names, dimensions);
1314 
1315         }
1316 
1317     }
1318 
1319     /** Wrapper for resetting an integrator handlers.
1320      * <p>
1321      * This class is intended to be used in a try-with-resource statement.
1322      * If propagator-specific event handlers and step handlers are added to
1323      * the integrator in the try block, they will be removed automatically
1324      * when leaving the block, so the integrator only keeps its own handlers
1325      * between calls to {@link AbstractIntegratedPropagator#propagate(AbsoluteDate, AbsoluteDate).
1326      * </p>
1327      * @since 11.0
1328      */
1329     private static class IntegratorResetter implements AutoCloseable {
1330 
1331         /** Wrapped integrator. */
1332         private final ODEIntegrator integrator;
1333 
1334         /** Initial event detectors list. */
1335         private final List<ODEEventDetector> detectors;
1336 
1337         /** Initial step handlers list. */
1338         private final List<ODEStepHandler> stepHandlers;
1339 
1340         /** Simple constructor.
1341          * @param integrator wrapped integrator
1342          */
1343         IntegratorResetter(final ODEIntegrator integrator) {
1344             this.integrator   = integrator;
1345             this.detectors    = new ArrayList<>(integrator.getEventDetectors());
1346             this.stepHandlers = new ArrayList<>(integrator.getStepHandlers());
1347         }
1348 
1349         /** {@inheritDoc}
1350          * <p>
1351          * Reset event handlers and step handlers back to the initial list
1352          * </p>
1353          */
1354         @Override
1355         public void close() {
1356 
1357             // reset event handlers
1358             integrator.clearEventDetectors();
1359             detectors.forEach(integrator::addEventDetector);
1360 
1361             // reset step handlers
1362             integrator.clearStepHandlers();
1363             stepHandlers.forEach(integrator::addStepHandler);
1364 
1365         }
1366 
1367     }
1368 
1369 }