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