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