FieldAbstractIntegratedPropagator.java

  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. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.LinkedList;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Queue;

  27. import org.hipparchus.CalculusFieldElement;
  28. import org.hipparchus.Field;
  29. import org.hipparchus.analysis.solvers.FieldBracketingNthOrderBrentSolver;
  30. import org.hipparchus.exception.MathIllegalArgumentException;
  31. import org.hipparchus.exception.MathIllegalStateException;
  32. import org.hipparchus.ode.FieldDenseOutputModel;
  33. import org.hipparchus.ode.FieldExpandableODE;
  34. import org.hipparchus.ode.FieldODEIntegrator;
  35. import org.hipparchus.ode.FieldODEState;
  36. import org.hipparchus.ode.FieldODEStateAndDerivative;
  37. import org.hipparchus.ode.FieldOrdinaryDifferentialEquation;
  38. import org.hipparchus.ode.FieldSecondaryODE;
  39. import org.hipparchus.ode.events.Action;
  40. import org.hipparchus.ode.events.FieldAdaptableInterval;
  41. import org.hipparchus.ode.events.FieldODEEventDetector;
  42. import org.hipparchus.ode.events.FieldODEEventHandler;
  43. import org.hipparchus.ode.sampling.AbstractFieldODEStateInterpolator;
  44. import org.hipparchus.ode.sampling.FieldODEStateInterpolator;
  45. import org.hipparchus.ode.sampling.FieldODEStepHandler;
  46. import org.hipparchus.util.MathArrays;
  47. import org.hipparchus.util.Precision;
  48. import org.orekit.attitudes.AttitudeProvider;
  49. import org.orekit.attitudes.AttitudeProviderModifier;
  50. import org.orekit.errors.OrekitException;
  51. import org.orekit.errors.OrekitInternalError;
  52. import org.orekit.errors.OrekitMessages;
  53. import org.orekit.frames.Frame;
  54. import org.orekit.orbits.OrbitType;
  55. import org.orekit.orbits.PositionAngleType;
  56. import org.orekit.propagation.FieldAbstractPropagator;
  57. import org.orekit.propagation.FieldAdditionalDataProvider;
  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.FieldDataDictionary;


  68. /** Common handling of {@link org.orekit.propagation.FieldPropagator FieldPropagator}
  69.  *  methods for both numerical and semi-analytical propagators.
  70.  * @author Luc Maisonobe
  71.  * @param <T> type of the field element
  72.  */
  73. public abstract class FieldAbstractIntegratedPropagator<T extends CalculusFieldElement<T>> extends FieldAbstractPropagator<T> {

  74.     /** Internal name used for complete secondary state dimension.
  75.      * @since 11.1
  76.      */
  77.     private static final String SECONDARY_DIMENSION = "Orekit-secondary-dimension";

  78.     /** Event detectors not related to force models. */
  79.     private final List<FieldEventDetector<T>> detectors;

  80.     /** Step handlers dedicated to ephemeris generation. */
  81.     private final List<FieldStoringStepHandler> ephemerisGenerators;

  82.     /** Integrator selected by the user for the orbital extrapolation process. */
  83.     private final FieldODEIntegrator<T> integrator;

  84.     /** Offsets of secondary states managed by {@link FieldAdditionalDerivativesProvider}.
  85.      * @since 11.1
  86.      */
  87.     private final Map<String, Integer> secondaryOffsets;

  88.     /** Additional derivatives providers.
  89.      * @since 11.1
  90.      */
  91.     private final List<FieldAdditionalDerivativesProvider<T>> additionalDerivativesProviders;

  92.     /** Counter for differential equations calls. */
  93.     private int calls;

  94.     /** Mapper between raw double components and space flight dynamics objects. */
  95.     private FieldStateMapper<T> stateMapper;

  96.     /**
  97.      * Attitude provider when evaluating derivatives. Can be a frozen one for performance.
  98.      * @since 12.1
  99.      */
  100.     private AttitudeProvider attitudeProviderForDerivatives;

  101.     /**
  102.      * Attitude provider with frozen rates, used when possible for performance.
  103.      * @since 13.1
  104.      */
  105.     private AttitudeProvider frozenAttitudeProvider;

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

  108.     /** Type of orbit to output (mean or osculating) <br/>
  109.      * <p>
  110.      * This is used only in the case of semi-analytical propagators where there is a clear separation between
  111.      * mean and short periodic elements. It is ignored by the Numerical propagator.
  112.      * </p>
  113.      */
  114.     private final PropagationType propagationType;

  115.     /** Build a new instance.
  116.      * @param integrator numerical integrator to use for propagation.
  117.      * @param propagationType type of orbit to output (mean or osculating).
  118.      * @param field Field used by default
  119.      */
  120.     protected FieldAbstractIntegratedPropagator(final Field<T> field, final FieldODEIntegrator<T> integrator, final PropagationType propagationType) {
  121.         super(field);
  122.         detectors                      = new ArrayList<>();
  123.         ephemerisGenerators            = new ArrayList<>();
  124.         additionalDerivativesProviders = new ArrayList<>();
  125.         this.secondaryOffsets          = new HashMap<>();
  126.         this.integrator                = integrator;
  127.         this.propagationType           = propagationType;
  128.         this.resetAtEnd                = true;
  129.     }

  130.     /** Allow/disallow resetting the initial state at end of propagation.
  131.      * <p>
  132.      * By default, at the end of the propagation, the propagator resets the initial state
  133.      * to the final state, thus allowing a new propagation to be started from there without
  134.      * recomputing the part already performed. Calling this method with {@code resetAtEnd} set
  135.      * to false changes prevents such reset.
  136.      * </p>
  137.      * @param resetAtEnd if true, at end of each propagation, the {@link
  138.      * #getInitialState() initial state} will be reset to the final state of
  139.      * the propagation, otherwise the initial state will be preserved
  140.      * @since 9.0
  141.      */
  142.     public void setResetAtEnd(final boolean resetAtEnd) {
  143.         this.resetAtEnd = resetAtEnd;
  144.     }

  145.     /** Getter for the resetting flag regarding initial state.
  146.      * @return resetting flag
  147.      * @since 12.0
  148.      */
  149.     public boolean getResetAtEnd() {
  150.         return this.resetAtEnd;
  151.     }

  152.     /**
  153.      * Getter for the frozen attitude provider, used for performance when possible.
  154.      * @return frozen attitude provider
  155.      * @since 13.1
  156.      */
  157.     protected AttitudeProvider getFrozenAttitudeProvider() {
  158.         return frozenAttitudeProvider;
  159.     }

  160.     /**
  161.      * Method called when initializing the attitude provider used when evaluating derivatives.
  162.      * @return attitude provider for derivatives
  163.      */
  164.     protected AttitudeProvider initializeAttitudeProviderForDerivatives() {
  165.         return getAttitudeProvider();
  166.     }

  167.     /** Initialize the mapper.
  168.      * @param field Field used by default
  169.      */
  170.     protected void initMapper(final Field<T> field) {
  171.         final T zero = field.getZero();
  172.         stateMapper = createMapper(null, zero.add(Double.NaN), null, null, null, null);
  173.     }

  174.     /** Get the integrator's name.
  175.      * @return name of underlying integrator
  176.      * @since 12.0
  177.      */
  178.     public String getIntegratorName() {
  179.         return integrator.getName();
  180.     }

  181.     /**  {@inheritDoc} */
  182.     @Override
  183.     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
  184.         super.setAttitudeProvider(attitudeProvider);
  185.         frozenAttitudeProvider = AttitudeProviderModifier.getFrozenAttitudeProvider(attitudeProvider);
  186.         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
  187.                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
  188.                                    attitudeProvider, stateMapper.getFrame());
  189.     }

  190.     /** Set propagation orbit type.
  191.      * @param orbitType orbit type to use for propagation
  192.      */
  193.     protected void setOrbitType(final OrbitType orbitType) {
  194.         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
  195.                                    orbitType, stateMapper.getPositionAngleType(),
  196.                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
  197.     }

  198.     /** Get propagation parameter type.
  199.      * @return orbit type used for propagation
  200.      */
  201.     protected OrbitType getOrbitType() {
  202.         return stateMapper.getOrbitType();
  203.     }

  204.     /** Check if only the mean elements should be used in a semi-analytical propagation.
  205.      * @return {@link PropagationType MEAN} if only mean elements have to be used or
  206.      *         {@link PropagationType OSCULATING} if osculating elements have to be also used.
  207.      */
  208.     protected PropagationType isMeanOrbit() {
  209.         return propagationType;
  210.     }

  211.     /** Get the propagation type.
  212.      * @return propagation type.
  213.      * @since 11.3.2
  214.      */
  215.     public PropagationType getPropagationType() {
  216.         return propagationType;
  217.     }

  218.     /** Set position angle type.
  219.      * <p>
  220.      * The position parameter type is meaningful only if {@link
  221.      * #getOrbitType() propagation orbit type}
  222.      * support it. As an example, it is not meaningful for propagation
  223.      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
  224.      * </p>
  225.      * @param positionAngleType angle type to use for propagation
  226.      */
  227.     protected void setPositionAngleType(final PositionAngleType positionAngleType) {
  228.         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
  229.                                    stateMapper.getOrbitType(), positionAngleType,
  230.                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
  231.     }

  232.     /** Get propagation parameter type.
  233.      * @return angle type to use for propagation
  234.      */
  235.     protected PositionAngleType getPositionAngleType() {
  236.         return stateMapper.getPositionAngleType();
  237.     }

  238.     /** Set the central attraction coefficient μ.
  239.      * @param mu central attraction coefficient (m³/s²)
  240.      */
  241.     public void setMu(final T mu) {
  242.         stateMapper = createMapper(stateMapper.getReferenceDate(), mu,
  243.                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
  244.                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
  245.     }

  246.     /** Get the central attraction coefficient μ.
  247.      * @return mu central attraction coefficient (m³/s²)
  248.      * @see #setMu(CalculusFieldElement)
  249.      */
  250.     public T getMu() {
  251.         return stateMapper.getMu();
  252.     }

  253.     /** Get the number of calls to the differential equations computation method.
  254.      * <p>The number of calls is reset each time the {@link #propagate(FieldAbsoluteDate)}
  255.      * method is called.</p>
  256.      * @return number of calls to the differential equations computation method
  257.      */
  258.     public int getCalls() {
  259.         return calls;
  260.     }

  261.     /** {@inheritDoc} */
  262.     @Override
  263.     public boolean isAdditionalDataManaged(final String name) {

  264.         // first look at already integrated data
  265.         if (super.isAdditionalDataManaged(name)) {
  266.             return true;
  267.         }

  268.         // then look at states we integrate ourselves
  269.         for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  270.             if (provider.getName().equals(name)) {
  271.                 return true;
  272.             }
  273.         }

  274.         return false;
  275.     }

  276.     /** {@inheritDoc} */
  277.     @Override
  278.     public String[] getManagedAdditionalData() {
  279.         final String[] alreadyIntegrated = super.getManagedAdditionalData();
  280.         final String[] managed = new String[alreadyIntegrated.length + additionalDerivativesProviders.size()];
  281.         System.arraycopy(alreadyIntegrated, 0, managed, 0, alreadyIntegrated.length);
  282.         for (int i = 0; i < additionalDerivativesProviders.size(); ++i) {
  283.             managed[i + alreadyIntegrated.length] = additionalDerivativesProviders.get(i).getName();
  284.         }
  285.         return managed;
  286.     }

  287.     /** Add a provider for user-specified state derivatives to be integrated along with the orbit propagation.
  288.      * @param provider provider for additional derivatives
  289.      * @see #addAdditionalDataProvider(FieldAdditionalDataProvider)
  290.      * @since 11.1
  291.      */
  292.     public void addAdditionalDerivativesProvider(final FieldAdditionalDerivativesProvider<T> provider) {
  293.         // check if the name is already used
  294.         if (this.isAdditionalDataManaged(provider.getName())) {
  295.             // these derivatives are already registered, complain
  296.             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_NAME_ALREADY_IN_USE,
  297.                                       provider.getName());
  298.         }

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

  301.         secondaryOffsets.clear();

  302.     }

  303.     /** Get an unmodifiable list of providers for additional derivatives.
  304.      * @return providers for additional derivatives
  305.      * @since 11.1
  306.      */
  307.     public List<FieldAdditionalDerivativesProvider<T>> getAdditionalDerivativesProviders() {
  308.         return Collections.unmodifiableList(additionalDerivativesProviders);
  309.     }

  310.     /** {@inheritDoc} */
  311.     public <D extends FieldEventDetector<T>> void addEventDetector(final D detector) {
  312.         detectors.add(detector);
  313.     }

  314.     /** {@inheritDoc} */
  315.     public Collection<FieldEventDetector<T>> getEventDetectors() {
  316.         return Collections.unmodifiableCollection(detectors);
  317.     }

  318.     /** {@inheritDoc} */
  319.     public void clearEventsDetectors() {
  320.         detectors.clear();
  321.     }

  322.     /** Set up all user defined event detectors.
  323.      */
  324.     protected void setUpUserEventDetectors() {
  325.         for (final FieldEventDetector<T> detector : detectors) {
  326.             setUpEventDetector(integrator, detector);
  327.         }
  328.     }

  329.     /** Wrap an Orekit event detector and register it to the integrator.
  330.      * @param integ integrator into which event detector should be registered
  331.      * @param detector event detector to wrap
  332.      */
  333.     protected void setUpEventDetector(final FieldODEIntegrator<T> integ, final FieldEventDetector<T> detector) {
  334.         integ.addEventDetector(new FieldAdaptedEventDetector(detector));
  335.     }

  336.     /**
  337.      * Clear the ephemeris generators.
  338.      * @since 13.0
  339.      */
  340.     public void clearEphemerisGenerators() {
  341.         ephemerisGenerators.clear();
  342.     }

  343.     /** {@inheritDoc} */
  344.     @Override
  345.     public FieldEphemerisGenerator<T> getEphemerisGenerator() {
  346.         final FieldStoringStepHandler storingHandler = new FieldStoringStepHandler();
  347.         ephemerisGenerators.add(storingHandler);
  348.         return storingHandler;
  349.     }

  350.     /** Create a mapper between raw double components and spacecraft state.
  351.     /** Simple constructor.
  352.      * <p>
  353.      * The position parameter type is meaningful only if {@link
  354.      * #getOrbitType() propagation orbit type}
  355.      * support it. As an example, it is not meaningful for propagation
  356.      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
  357.      * </p>
  358.      * @param referenceDate reference date
  359.      * @param mu central attraction coefficient (m³/s²)
  360.      * @param orbitType orbit type to use for mapping
  361.      * @param positionAngleType angle type to use for propagation
  362.      * @param attitudeProvider attitude provider
  363.      * @param frame inertial frame
  364.      * @return new mapper
  365.      */
  366.     protected abstract FieldStateMapper<T> createMapper(FieldAbsoluteDate<T> referenceDate, T mu,
  367.                                                         OrbitType orbitType, PositionAngleType positionAngleType,
  368.                                                         AttitudeProvider attitudeProvider, Frame frame);

  369.     /** Get the differential equations to integrate (for main state only).
  370.      * @param integ numerical integrator to use for propagation.
  371.      * @return differential equations for main state
  372.      */
  373.     protected abstract MainStateEquations<T> getMainStateEquations(FieldODEIntegrator<T> integ);

  374.     /** {@inheritDoc} */
  375.     @Override
  376.     public FieldSpacecraftState<T> propagate(final FieldAbsoluteDate<T> target) {
  377.         if (getStartDate() == null) {
  378.             if (getInitialState() == null) {
  379.                 throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
  380.             }
  381.             setStartDate(getInitialState().getDate());
  382.         }
  383.         return propagate(getStartDate(), target);
  384.     }

  385.     /** {@inheritDoc} */
  386.     public FieldSpacecraftState<T> propagate(final FieldAbsoluteDate<T> tStart, final FieldAbsoluteDate<T> tEnd) {

  387.         if (getInitialState() == null) {
  388.             throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
  389.         }

  390.         // make sure the integrator will be reset properly even if we change its events handlers and step handlers
  391.         try (IntegratorResetter<T> resetter = new IntegratorResetter<>(integrator)) {

  392.             // Initialize additional states
  393.             initializeAdditionalData(tEnd);

  394.             if (!tStart.equals(getInitialState().getDate())) {
  395.                 // if propagation start date is not initial date,
  396.                 // propagate from initial to start date without event detection
  397.                 try (IntegratorResetter<T> startResetter = new IntegratorResetter<>(integrator)) {
  398.                     integrateDynamics(tStart);
  399.                 }
  400.             }

  401.             // set up events added by user
  402.             setUpUserEventDetectors();

  403.             // set up step handlers
  404.             for (final FieldOrekitStepHandler<T> handler : getMultiplexer().getHandlers()) {
  405.                 integrator.addStepHandler(new FieldAdaptedStepHandler(handler));
  406.             }
  407.             for (final FieldStoringStepHandler generator : ephemerisGenerators) {
  408.                 generator.setEndDate(tEnd);
  409.                 integrator.addStepHandler(generator);
  410.             }

  411.             // propagate from start date to end date with event detection
  412.             final FieldSpacecraftState<T> state = integrateDynamics(tEnd);

  413.             // Finalize event detectors
  414.             getEventDetectors().forEach(detector -> detector.finish(state));

  415.             return state;
  416.         }

  417.     }

  418.     /** Reset initial state with a given propagation type.
  419.      *
  420.      * <p> By default this method returns the same as method resetInitialState(FieldSpacecraftState)
  421.      * <p> Its purpose is mostly to be derived in FieldDSSTPropagator
  422.      *
  423.      * @param state new initial state to consider
  424.      * @param stateType type of the new state (mean or osculating)
  425.      * @since 12.1.3
  426.      */
  427.     public void resetInitialState(final FieldSpacecraftState<T> state, final PropagationType stateType) {
  428.         // Default behavior, do not take propagation type into account
  429.         resetInitialState(state);
  430.     }

  431.     /** Propagation with or without event detection.
  432.      * @param tEnd target date to which orbit should be propagated
  433.      * @return state at end of propagation
  434.      */
  435.     private FieldSpacecraftState<T> integrateDynamics(final FieldAbsoluteDate<T> tEnd) {
  436.         try {

  437.             initializePropagation();

  438.             if (getInitialState().getDate().equals(tEnd)) {
  439.                 // don't extrapolate
  440.                 return getInitialState();
  441.             }
  442.             // space dynamics view
  443.             stateMapper = createMapper(getInitialState().getDate(), stateMapper.getMu(),
  444.                                        stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
  445.                                        stateMapper.getAttitudeProvider(), getInitialState().getFrame());

  446.             // set propagation orbit type
  447.             if (Double.isNaN(getMu().getReal()) && getInitialState().isOrbitDefined()) {
  448.                 setMu(getInitialState().getOrbit().getMu());
  449.             }

  450.             if (getInitialState().getMass().getReal() <= 0.0) {
  451.                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS,
  452.                                                getInitialState().getMass());
  453.             }

  454.             // convert space flight dynamics API to math API
  455.             final FieldSpacecraftState<T> initialIntegrationState = getInitialIntegrationState();
  456.             final FieldODEState<T> mathInitialState = createInitialState(initialIntegrationState);
  457.             final FieldExpandableODE<T> mathODE = createODE(integrator);

  458.             // mathematical integration
  459.             final FieldODEStateAndDerivative<T> mathFinalState;
  460.             beforeIntegration(initialIntegrationState, tEnd);
  461.             mathFinalState = integrator.integrate(mathODE, mathInitialState,
  462.                                                   tEnd.durationFrom(getInitialState().getDate()));

  463.             afterIntegration();

  464.             // get final state
  465.             FieldSpacecraftState<T> finalState =
  466.                             stateMapper.mapArrayToState(stateMapper.mapDoubleToDate(mathFinalState.getTime(), tEnd),
  467.                                                         mathFinalState.getPrimaryState(),
  468.                                                         mathFinalState.getPrimaryDerivative(),
  469.                                                         propagationType);

  470.             finalState = updateAdditionalStatesAndDerivatives(finalState, mathFinalState);

  471.             if (resetAtEnd) {
  472.                 resetInitialState(finalState, propagationType);
  473.                 setStartDate(finalState.getDate());
  474.             }

  475.             return finalState;

  476.         } catch (OrekitException pe) {
  477.             throw pe;
  478.         } catch (MathIllegalArgumentException | MathIllegalStateException me) {
  479.             throw OrekitException.unwrap(me);
  480.         }
  481.     }

  482.     /**
  483.      * Returns an updated version of the inputted state with additional states, including
  484.      * from derivatives providers.
  485.      * @param originalState input state
  486.      * @param os ODE state and derivative
  487.      * @return new state
  488.      * @since 12.1
  489.      */
  490.     private FieldSpacecraftState<T> updateAdditionalStatesAndDerivatives(final FieldSpacecraftState<T> originalState,
  491.                                                                          final FieldODEStateAndDerivative<T> os) {
  492.         FieldSpacecraftState<T> updatedState = originalState;
  493.         if (os.getNumberOfSecondaryStates() > 0) {
  494.             final T[] secondary           = os.getSecondaryState(1);
  495.             final T[] secondaryDerivative = os.getSecondaryDerivative(1);
  496.             for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  497.                 final String name      = provider.getName();
  498.                 final int    offset    = secondaryOffsets.get(name);
  499.                 final int    dimension = provider.getDimension();
  500.                 updatedState = updatedState.addAdditionalData(name, Arrays.copyOfRange(secondary, offset, offset + dimension));
  501.                 updatedState = updatedState.addAdditionalStateDerivative(name, Arrays.copyOfRange(secondaryDerivative, offset, offset + dimension));
  502.             }
  503.         }
  504.         return updateAdditionalData(updatedState);
  505.     }

  506.     /** Get the initial state for integration.
  507.      * @return initial state for integration
  508.      */
  509.     protected FieldSpacecraftState<T> getInitialIntegrationState() {
  510.         return getInitialState();
  511.     }

  512.     /** Create an initial state.
  513.      * @param initialState initial state in flight dynamics world
  514.      * @return initial state in mathematics world
  515.      */
  516.     private FieldODEState<T> createInitialState(final FieldSpacecraftState<T> initialState) {

  517.         // retrieve initial state
  518.         final T[] primary  = MathArrays.buildArray(initialState.getMass().getField(), getBasicDimension());
  519.         stateMapper.mapStateToArray(initialState, primary, null);

  520.         if (secondaryOffsets.isEmpty()) {
  521.             // compute dimension of the secondary state
  522.             int offset = 0;
  523.             for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  524.                 secondaryOffsets.put(provider.getName(), offset);
  525.                 offset += provider.getDimension();
  526.             }
  527.             secondaryOffsets.put(SECONDARY_DIMENSION, offset);
  528.         }

  529.         return new FieldODEState<>(initialState.getMass().getField().getZero(), primary, secondary(initialState));

  530.     }

  531.     /** Create secondary state.
  532.      * @param state spacecraft state
  533.      * @return secondary state
  534.      * @since 11.1
  535.      */
  536.     private T[][] secondary(final FieldSpacecraftState<T> state) {

  537.         if (secondaryOffsets.isEmpty()) {
  538.             return null;
  539.         }

  540.         final T[][] secondary = MathArrays.buildArray(state.getDate().getField(), 1, secondaryOffsets.get(SECONDARY_DIMENSION));
  541.         for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  542.             final String name       = provider.getName();
  543.             final int    offset     = secondaryOffsets.get(name);
  544.             final T[]    additional = state.getAdditionalState(name);
  545.             System.arraycopy(additional, 0, secondary[0], offset, additional.length);
  546.         }

  547.         return secondary;

  548.     }

  549.     /** Create an ODE with all equations.
  550.      * @param integ numerical integrator to use for propagation.
  551.      * @return a new ode
  552.      */
  553.     private FieldExpandableODE<T> createODE(final FieldODEIntegrator<T> integ) {

  554.         final FieldExpandableODE<T> ode =
  555.                 new FieldExpandableODE<>(new ConvertedMainStateEquations(getMainStateEquations(integ)));

  556.         // secondary part of the ODE
  557.         if (!additionalDerivativesProviders.isEmpty()) {
  558.             ode.addSecondaryEquations(new ConvertedSecondaryStateEquations());
  559.         }

  560.         return ode;

  561.     }

  562.     /** Method called just before integration.
  563.      * <p>
  564.      * The default implementation does nothing, it may be specialized in subclasses.
  565.      * </p>
  566.      * @param initialState initial state
  567.      * @param tEnd target date at which state should be propagated
  568.      */
  569.     protected void beforeIntegration(final FieldSpacecraftState<T> initialState,
  570.                                      final FieldAbsoluteDate<T> tEnd) {
  571.         // do nothing by default
  572.     }

  573.     /** Method called just after integration.
  574.      * <p>
  575.      * The default implementation does nothing, it may be specialized in subclasses.
  576.      * </p>
  577.      */
  578.     protected void afterIntegration() {
  579.         // do nothing by default
  580.     }

  581.     /** Get state vector dimension without additional parameters.
  582.      * @return state vector dimension without additional parameters.
  583.      */
  584.     public int getBasicDimension() {
  585.         return 7;

  586.     }

  587.     /** Get the integrator used by the propagator.
  588.      * @return the integrator.
  589.      */
  590.     protected FieldODEIntegrator<T> getIntegrator() {
  591.         return integrator;
  592.     }

  593.     /** Convert a state from mathematical world to space flight dynamics world without additional data and derivatives.
  594.      * @param os mathematical state
  595.      * @return space flight dynamics state
  596.      */
  597.     private FieldSpacecraftState<T> convertToOrekitWithoutAdditional(final FieldODEStateAndDerivative<T> os) {
  598.         return stateMapper.mapArrayToState(os.getTime(),
  599.                 os.getPrimaryState(),
  600.                 os.getPrimaryDerivative(),
  601.                 propagationType);
  602.     }

  603.     /** Convert a state from mathematical world to space flight dynamics world.
  604.      * @param os mathematical state
  605.      * @return space flight dynamics state
  606.      */
  607.     private FieldSpacecraftState<T> convertToOrekit(final FieldODEStateAndDerivative<T> os) {

  608.         FieldSpacecraftState<T> s = convertToOrekitWithoutAdditional(os);

  609.         if (os.getNumberOfSecondaryStates() > 0) {
  610.             final T[] secondary           = os.getSecondaryState(1);
  611.             final T[] secondaryDerivative = os.getSecondaryDerivative(1);
  612.             for (final FieldAdditionalDerivativesProvider<T> equations : additionalDerivativesProviders) {
  613.                 final String name      = equations.getName();
  614.                 final int    offset    = secondaryOffsets.get(name);
  615.                 final int    dimension = equations.getDimension();
  616.                 s = s.addAdditionalData(name, Arrays.copyOfRange(secondary, offset, offset + dimension));
  617.                 s = s.addAdditionalStateDerivative(name, Arrays.copyOfRange(secondaryDerivative, offset, offset + dimension));
  618.             }
  619.         }
  620.         return updateAdditionalData(s);

  621.     }

  622.     /** Differential equations for the main state (orbit, attitude and mass).
  623.      * @param <T> type of the field element
  624.      */
  625.     public interface MainStateEquations<T extends CalculusFieldElement<T>> {

  626.         /**
  627.          * Initialize the equations at the start of propagation. This method will be
  628.          * called before any calls to {@link #computeDerivatives(FieldSpacecraftState)}.
  629.          *
  630.          * <p> The default implementation of this method does nothing.
  631.          *
  632.          * @param initialState initial state information at the start of propagation.
  633.          * @param target       date of propagation. Not equal to {@code
  634.          *                     initialState.getDate()}.
  635.          */
  636.         void init(FieldSpacecraftState<T> initialState, FieldAbsoluteDate<T> target);

  637.         /** Compute differential equations for main state.
  638.          * @param state current state
  639.          * @return derivatives of main state
  640.          */
  641.         T[] computeDerivatives(FieldSpacecraftState<T> state);

  642.     }

  643.     /** Differential equations for the main state (orbit, attitude and mass), with converted API. */
  644.     private class ConvertedMainStateEquations implements FieldOrdinaryDifferentialEquation<T> {

  645.         /** Main state equations. */
  646.         private final MainStateEquations<T> main;

  647.         /** Simple constructor.
  648.          * @param main main state equations
  649.          */
  650.         ConvertedMainStateEquations(final MainStateEquations<T> main) {
  651.             this.main = main;
  652.             calls = 0;
  653.         }

  654.         /** {@inheritDoc} */
  655.         public int getDimension() {
  656.             return getBasicDimension();
  657.         }

  658.         @Override
  659.         public void init(final T t0, final T[] y0, final T finalTime) {
  660.             // update space dynamics view
  661.             FieldSpacecraftState<T> initialState = stateMapper.mapArrayToState(t0, y0, null, PropagationType.MEAN);
  662.             initialState = updateAdditionalData(initialState);
  663.             initialState = updateStatesFromAdditionalDerivativesIfKnown(initialState);
  664.             final FieldAbsoluteDate<T> target = stateMapper.mapDoubleToDate(finalTime);
  665.             main.init(initialState, target);
  666.             attitudeProviderForDerivatives = initializeAttitudeProviderForDerivatives();
  667.         }

  668.         /**
  669.          * Returns an updated version of the inputted state, with additional states from
  670.          * derivatives providers as given in the stored initial state.
  671.          * @param originalState input state
  672.          * @return new state
  673.          * @since 12.1
  674.          */
  675.         private FieldSpacecraftState<T> updateStatesFromAdditionalDerivativesIfKnown(final FieldSpacecraftState<T> originalState) {
  676.             FieldSpacecraftState<T> updatedState = originalState;
  677.             final FieldSpacecraftState<T> storedInitialState = getInitialState();
  678.             final T originalTime = stateMapper.mapDateToDouble(originalState.getDate());
  679.             if (storedInitialState != null && stateMapper.mapDateToDouble(storedInitialState.getDate()).subtract(originalTime).isZero()) {
  680.                 for (final FieldAdditionalDerivativesProvider<T> provider: additionalDerivativesProviders) {
  681.                     final String name = provider.getName();
  682.                     final T[] value = storedInitialState.getAdditionalState(name);
  683.                     updatedState = updatedState.addAdditionalData(name, value);
  684.                 }
  685.             }
  686.             return updatedState;
  687.         }

  688.         /** {@inheritDoc} */
  689.         public T[] computeDerivatives(final T t, final T[] y) {

  690.             // increment calls counter
  691.             ++calls;

  692.             // update space dynamics view
  693.             stateMapper.setAttitudeProvider(attitudeProviderForDerivatives);
  694.             FieldSpacecraftState<T> currentState = stateMapper.mapArrayToState(t, y, null, PropagationType.MEAN);
  695.             stateMapper.setAttitudeProvider(getAttitudeProvider());
  696.             currentState = updateAdditionalData(currentState);

  697.             // compute main state differentials
  698.             return main.computeDerivatives(currentState);

  699.         }

  700.     }

  701.     /** Differential equations for the secondary state (Jacobians, user variables ...), with converted API. */
  702.     private class ConvertedSecondaryStateEquations implements FieldSecondaryODE<T> {

  703.         /** Dimension of the combined additional states. */
  704.         private final int combinedDimension;

  705.         /** Simple constructor.
  706.          */
  707.         ConvertedSecondaryStateEquations() {
  708.             this.combinedDimension = secondaryOffsets.get(SECONDARY_DIMENSION);
  709.         }

  710.         /** {@inheritDoc} */
  711.         @Override
  712.         public int getDimension() {
  713.             return combinedDimension;
  714.         }

  715.         /** {@inheritDoc} */
  716.         @Override
  717.         public void init(final T t0, final T[] primary0,
  718.                          final T[] secondary0, final T finalTime) {
  719.             // update space dynamics view
  720.             final FieldSpacecraftState<T> initialState = convert(t0, primary0, null, secondary0);

  721.             final FieldAbsoluteDate<T> target = stateMapper.mapDoubleToDate(finalTime);
  722.             for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  723.                 provider.init(initialState, target);
  724.             }

  725.         }

  726.         /** {@inheritDoc} */
  727.         @Override
  728.         public T[] computeDerivatives(final T t, final T[] primary,
  729.                                       final T[] primaryDot, final T[] secondary) {

  730.             // update space dynamics view
  731.             // the integrable generators generate method will be called here,
  732.             // according to the generators yield order
  733.             FieldSpacecraftState<T> updated = convert(t, primary, primaryDot, secondary);

  734.             // set up queue for equations
  735.             final Queue<FieldAdditionalDerivativesProvider<T>> pending = new LinkedList<>(additionalDerivativesProviders);

  736.             // gather the derivatives from all additional equations, taking care of dependencies
  737.             final T[] secondaryDot = MathArrays.buildArray(t.getField(), combinedDimension);
  738.             int yieldCount = 0;
  739.             while (!pending.isEmpty()) {
  740.                 final FieldAdditionalDerivativesProvider<T> equations = pending.remove();
  741.                 if (equations.yields(updated)) {
  742.                     // these equations have to wait for another set,
  743.                     // we put them again in the pending queue
  744.                     pending.add(equations);
  745.                     if (++yieldCount >= pending.size()) {
  746.                         // all pending equations yielded!, they probably need data not yet initialized
  747.                         // we let the propagation proceed, if these data are really needed right now
  748.                         // an appropriate exception will be triggered when caller tries to access them
  749.                         break;
  750.                     }
  751.                 } else {
  752.                     // we can use these equations right now
  753.                     final String                      name           = equations.getName();
  754.                     final int                         offset         = secondaryOffsets.get(name);
  755.                     final int                         dimension      = equations.getDimension();
  756.                     final FieldCombinedDerivatives<T> derivatives    = equations.combinedDerivatives(updated);
  757.                     final T[]                         additionalPart = derivatives.getAdditionalDerivatives();
  758.                     final T[]                         mainPart       = derivatives.getMainStateDerivativesIncrements();
  759.                     System.arraycopy(additionalPart, 0, secondaryDot, offset, dimension);
  760.                     updated = updated.addAdditionalStateDerivative(name, additionalPart);
  761.                     if (mainPart != null) {
  762.                         // this equation does change the main state derivatives
  763.                         for (int i = 0; i < mainPart.length; ++i) {
  764.                             primaryDot[i] = primaryDot[i].add(mainPart[i]);
  765.                         }
  766.                     }
  767.                     yieldCount = 0;
  768.                 }
  769.             }

  770.             return secondaryDot;

  771.         }

  772.         /** Convert mathematical view to space view.
  773.          * @param t current value of the independent <I>time</I> variable
  774.          * @param primary array containing the current value of the primary state vector
  775.          * @param primaryDot array containing the derivative of the primary state vector
  776.          * @param secondary array containing the current value of the secondary state vector
  777.          * @return space view of the state
  778.          */
  779.         private FieldSpacecraftState<T> convert(final T t, final T[] primary,
  780.                                                 final T[] primaryDot, final T[] secondary) {

  781.             FieldSpacecraftState<T> initialState = stateMapper.mapArrayToState(t, primary, primaryDot, PropagationType.MEAN);

  782.             for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  783.                 final String name      = provider.getName();
  784.                 final int    offset    = secondaryOffsets.get(name);
  785.                 final int    dimension = provider.getDimension();
  786.                 initialState = initialState.addAdditionalData(name, Arrays.copyOfRange(secondary, offset, offset + dimension));
  787.             }

  788.             return updateAdditionalData(initialState);

  789.         }

  790.     }

  791.     /** Adapt an {@link org.orekit.propagation.events.FieldEventDetector<T>}
  792.      * to Hipparchus {@link org.hipparchus.ode.events.FieldODEEventDetector<T>} interface.
  793.      * @author Fabien Maussion
  794.      */
  795.     private class FieldAdaptedEventDetector implements FieldODEEventDetector<T> {

  796.         /** Underlying event detector. */
  797.         private final FieldEventDetector<T> detector;

  798.         /** Underlying event handler.
  799.          * @since 12.0
  800.          */
  801.         private final FieldEventHandler<T> handler;

  802.         /** Time of the previous call to g. */
  803.         private T lastT;

  804.         /** Value from the previous call to g. */
  805.         private T lastG;

  806.         /** Build a wrapped event detector.
  807.          * @param detector event detector to wrap
  808.         */
  809.         FieldAdaptedEventDetector(final FieldEventDetector<T> detector) {
  810.             this.detector = detector;
  811.             this.handler  = detector.getHandler();
  812.             this.lastT    = getField().getZero().add(Double.NaN);
  813.             this.lastG    = getField().getZero().add(Double.NaN);
  814.         }

  815.         /** {@inheritDoc} */
  816.         @Override
  817.         public FieldAdaptableInterval<T> getMaxCheckInterval() {
  818.             return (state, isForward) -> detector.getMaxCheckInterval().currentInterval(convertToOrekitForEventFunction(state), isForward);
  819.         }

  820.         /** {@inheritDoc} */
  821.         @Override
  822.         public int getMaxIterationCount() {
  823.             return detector.getMaxIterationCount();
  824.         }

  825.         /** {@inheritDoc} */
  826.         @Override
  827.         public FieldBracketingNthOrderBrentSolver<T> getSolver() {
  828.             final T zero = detector.getThreshold().getField().getZero();
  829.             return new FieldBracketingNthOrderBrentSolver<>(zero, detector.getThreshold(), zero, 5);
  830.         }

  831.         /** {@inheritDoc} */
  832.         @Override
  833.         public void init(final FieldODEStateAndDerivative<T> s0, final T t) {
  834.             detector.init(convertToOrekit(s0), stateMapper.mapDoubleToDate(t));
  835.             this.lastT = getField().getZero().add(Double.NaN);
  836.             this.lastG = getField().getZero().add(Double.NaN);
  837.         }

  838.         /** {@inheritDoc} */
  839.         @Override
  840.         public void reset(final FieldODEStateAndDerivative<T> intermediateState, final T finalTime) {
  841.             detector.reset(convertToOrekit(intermediateState), stateMapper.mapDoubleToDate(finalTime));
  842.             this.lastT = getField().getZero().add(Double.NaN);
  843.             this.lastG = getField().getZero().add(Double.NaN);
  844.         }

  845.         /** {@inheritDoc} */
  846.         public T g(final FieldODEStateAndDerivative<T> s) {
  847.             if (!Precision.equals(lastT.getReal(), s.getTime().getReal(), 0)) {
  848.                 lastT = s.getTime();
  849.                 lastG = detector.g(convertToOrekitForEventFunction(s));
  850.             }
  851.             return lastG;
  852.         }

  853.         /**
  854.          * Convert state from Hipparchus to Orekit format.
  855.          * @param s state vector
  856.          * @return Orekit state
  857.          */
  858.         private FieldSpacecraftState<T> convertToOrekitForEventFunction(final FieldODEStateAndDerivative<T> s) {
  859.             if (!this.detector.dependsOnTimeOnly()) {
  860.                 return convertToOrekit(s);
  861.             } else {
  862.                 // event function only needs time
  863.                 stateMapper.setAttitudeProvider(getFrozenAttitudeProvider());
  864.                 final FieldSpacecraftState<T> converted = convertToOrekitWithoutAdditional(s);
  865.                 stateMapper.setAttitudeProvider(getAttitudeProvider());
  866.                 return converted;
  867.             }
  868.         }

  869.         /** {@inheritDoc} */
  870.         public FieldODEEventHandler<T> getHandler() {

  871.             return new FieldODEEventHandler<T>() {

  872.                 /** {@inheritDoc} */
  873.                 public Action eventOccurred(final FieldODEStateAndDerivative<T> s,
  874.                                             final FieldODEEventDetector<T> d,
  875.                                             final boolean increasing) {
  876.                     return handler.eventOccurred(convertToOrekit(s), detector, increasing);
  877.                 }

  878.                 /** {@inheritDoc} */
  879.                 @Override
  880.                 public FieldODEState<T> resetState(final FieldODEEventDetector<T> d,
  881.                                                    final FieldODEStateAndDerivative<T> s) {

  882.                     final FieldSpacecraftState<T> oldState = convertToOrekit(s);
  883.                     final FieldSpacecraftState<T> newState = handler.resetState(detector, oldState);
  884.                     stateChanged(newState);

  885.                     // main part
  886.                     final T[] primary    = MathArrays.buildArray(getField(), s.getPrimaryStateDimension());
  887.                     stateMapper.mapStateToArray(newState, primary, null);

  888.                     // secondary part
  889.                     final T[][] secondary = MathArrays.buildArray(getField(), 1, additionalDerivativesProviders.size());
  890.                     for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  891.                         final String name      = provider.getName();
  892.                         final int    offset    = secondaryOffsets.get(name);
  893.                         final int    dimension = provider.getDimension();
  894.                         System.arraycopy(newState.getAdditionalState(name), 0, secondary[0], offset, dimension);
  895.                     }

  896.                     return new FieldODEState<>(newState.getDate().durationFrom(getStartDate()),
  897.                                                primary, secondary);
  898.                 }
  899.             };

  900.         }

  901.     }

  902.     /** Adapt an {@link org.orekit.propagation.sampling.FieldOrekitStepHandler<T>}
  903.      * to Hipparchus {@link FieldODEStepHandler<T>} interface.
  904.      * @author Luc Maisonobe
  905.      */
  906.     private class FieldAdaptedStepHandler implements FieldODEStepHandler<T> {

  907.         /** Underlying handler. */
  908.         private final FieldOrekitStepHandler<T> handler;

  909.         /** Build an instance.
  910.          * @param handler underlying handler to wrap
  911.          */
  912.         FieldAdaptedStepHandler(final FieldOrekitStepHandler<T> handler) {
  913.             this.handler = handler;
  914.         }

  915.         /** {@inheritDoc} */
  916.         @Override
  917.         public void init(final FieldODEStateAndDerivative<T> s0, final T t) {
  918.             handler.init(convertToOrekit(s0), stateMapper.mapDoubleToDate(t));
  919.         }

  920.         /** {@inheritDoc} */
  921.         public void handleStep(final FieldODEStateInterpolator<T> interpolator) {
  922.             handler.handleStep(new FieldAdaptedStepInterpolator(interpolator));
  923.         }

  924.         /** {@inheritDoc} */
  925.         @Override
  926.         public void finish(final FieldODEStateAndDerivative<T> finalState) {
  927.             handler.finish(convertToOrekit(finalState));
  928.         }

  929.     }

  930.     /** Adapt an {@link org.orekit.propagation.sampling.FieldOrekitStepInterpolator<T>}
  931.      * to Hipparchus {@link FieldODEStateInterpolator<T>} interface.
  932.      * @author Luc Maisonobe
  933.      */
  934.     private class FieldAdaptedStepInterpolator implements FieldOrekitStepInterpolator<T> {

  935.         /** Underlying raw rawInterpolator. */
  936.         private final FieldODEStateInterpolator<T> mathInterpolator;

  937.         /** Build an instance.
  938.          * @param mathInterpolator underlying raw interpolator
  939.          */
  940.         FieldAdaptedStepInterpolator(final FieldODEStateInterpolator<T> mathInterpolator) {
  941.             this.mathInterpolator = mathInterpolator;
  942.         }

  943.         /** {@inheritDoc}} */
  944.         @Override
  945.         public FieldSpacecraftState<T> getPreviousState() {
  946.             return convertToOrekit(mathInterpolator.getPreviousState());
  947.         }

  948.         /** {@inheritDoc}} */
  949.         @Override
  950.         public FieldSpacecraftState<T> getCurrentState() {
  951.             return convertToOrekit(mathInterpolator.getCurrentState());
  952.         }

  953.         /** {@inheritDoc}} */
  954.         @Override
  955.         public FieldSpacecraftState<T> getInterpolatedState(final FieldAbsoluteDate<T> date) {
  956.             return convertToOrekit(mathInterpolator.getInterpolatedState(date.durationFrom(getStartDate())));
  957.         }

  958.         /** Check is integration direction is forward in date.
  959.          * @return true if integration is forward in date
  960.          */
  961.         public boolean isForward() {
  962.             return mathInterpolator.isForward();
  963.         }

  964.         /** {@inheritDoc}} */
  965.         @Override
  966.         public FieldAdaptedStepInterpolator restrictStep(final FieldSpacecraftState<T> newPreviousState,
  967.                                                          final FieldSpacecraftState<T> newCurrentState) {
  968.             try {
  969.                 final AbstractFieldODEStateInterpolator<T> aosi = (AbstractFieldODEStateInterpolator<T>) mathInterpolator;
  970.                 return new FieldAdaptedStepInterpolator(aosi.restrictStep(convertFromOrekit(newPreviousState),
  971.                         convertFromOrekit(newCurrentState)));
  972.             } catch (ClassCastException cce) {
  973.                 // this should never happen
  974.                 throw new OrekitInternalError(cce);
  975.             }
  976.         }

  977.         /** Convert a state from space flight dynamics world to mathematical world.
  978.          * @param state space flight dynamics state
  979.          * @return mathematical state
  980.          */
  981.         private FieldODEStateAndDerivative<T> convertFromOrekit(final FieldSpacecraftState<T> state) {

  982.             // retrieve initial state
  983.             final T[] primary    = MathArrays.buildArray(getField(), getBasicDimension());
  984.             final T[] primaryDot = MathArrays.buildArray(getField(), getBasicDimension());
  985.             stateMapper.mapStateToArray(state, primary, primaryDot);

  986.             // secondary part of the ODE
  987.             final T[][] secondary           = secondary(state);
  988.             final T[][] secondaryDerivative = secondaryDerivative(state);

  989.             return new FieldODEStateAndDerivative<>(stateMapper.mapDateToDouble(state.getDate()),
  990.                     primary, primaryDot,
  991.                     secondary, secondaryDerivative);

  992.         }

  993.         /** Create secondary state derivative.
  994.          * @param state spacecraft state
  995.          * @return secondary state derivative
  996.          * @since 11.1
  997.          */
  998.         private T[][] secondaryDerivative(final FieldSpacecraftState<T> state) {

  999.             if (secondaryOffsets.isEmpty()) {
  1000.                 return null;
  1001.             }

  1002.             final T[][] secondaryDerivative = MathArrays.buildArray(state.getDate().getField(), 1, secondaryOffsets.get(SECONDARY_DIMENSION));
  1003.             for (final FieldAdditionalDerivativesProvider<T> providcer : additionalDerivativesProviders) {
  1004.                 final String name       = providcer.getName();
  1005.                 final int    offset     = secondaryOffsets.get(name);
  1006.                 final T[]    additionalDerivative = state.getAdditionalStateDerivative(name);
  1007.                 System.arraycopy(additionalDerivative, 0, secondaryDerivative[0], offset, additionalDerivative.length);
  1008.             }

  1009.             return secondaryDerivative;

  1010.         }

  1011.     }

  1012.     /** Specialized step handler storing interpolators for ephemeris generation.
  1013.      * @since 11.0
  1014.      */
  1015.     private class FieldStoringStepHandler implements FieldODEStepHandler<T>, FieldEphemerisGenerator<T> {

  1016.         /** Underlying raw mathematical model. */
  1017.         private FieldDenseOutputModel<T> model;

  1018.         /** the user supplied end date. Propagation may not end on this date. */
  1019.         private FieldAbsoluteDate<T> endDate;

  1020.         /** Generated ephemeris. */
  1021.         private FieldBoundedPropagator<T> ephemeris;

  1022.         /** Last interpolator handled by the object.*/
  1023.         private  FieldODEStateInterpolator<T> lastInterpolator;

  1024.         /** Set the end date.
  1025.          * @param endDate end date
  1026.          */
  1027.         public void setEndDate(final FieldAbsoluteDate<T> endDate) {
  1028.             this.endDate = endDate;
  1029.         }

  1030.         /** {@inheritDoc} */
  1031.         @Override
  1032.         public void init(final FieldODEStateAndDerivative<T> s0, final T t) {
  1033.             this.model = new FieldDenseOutputModel<>();
  1034.             model.init(s0, t);

  1035.             // ephemeris will be generated when last step is processed
  1036.             this.ephemeris = null;

  1037.             this.lastInterpolator = null;

  1038.         }

  1039.         /** {@inheritDoc} */
  1040.         @Override
  1041.         public FieldBoundedPropagator<T> getGeneratedEphemeris() {
  1042.             // Each time we try to get the ephemeris, rebuild it using the last data.
  1043.             buildEphemeris();
  1044.             return ephemeris;
  1045.         }

  1046.         /** {@inheritDoc} */
  1047.         @Override
  1048.         public void handleStep(final FieldODEStateInterpolator<T> interpolator) {
  1049.             model.handleStep(interpolator);
  1050.             lastInterpolator = interpolator;
  1051.         }

  1052.         /** {@inheritDoc} */
  1053.         @Override
  1054.         public void finish(final FieldODEStateAndDerivative<T> finalState) {
  1055.             buildEphemeris();
  1056.         }

  1057.         /** Method used to produce ephemeris at a given time.
  1058.          * Can be used at multiple times, updating the ephemeris to
  1059.          * its last state.
  1060.          */
  1061.         private void buildEphemeris() {
  1062.             // buildEphemeris was built in order to allow access to what was previously the finish method.
  1063.             // This now allows to call it through getGeneratedEphemeris, therefore through an external call,
  1064.             // which was not previously the case.

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

  1067.             // set up the boundary dates
  1068.             final T tI = model.getInitialTime();
  1069.             final T tF = model.getFinalTime();
  1070.             // tI is almost? always zero
  1071.             final FieldAbsoluteDate<T> startDate =
  1072.                             stateMapper.mapDoubleToDate(tI);
  1073.             final FieldAbsoluteDate<T> finalDate =
  1074.                             stateMapper.mapDoubleToDate(tF, this.endDate);
  1075.             final FieldAbsoluteDate<T> minDate;
  1076.             final FieldAbsoluteDate<T> maxDate;
  1077.             if (tF.getReal() < tI.getReal()) {
  1078.                 minDate = finalDate;
  1079.                 maxDate = startDate;
  1080.             } else {
  1081.                 minDate = startDate;
  1082.                 maxDate = finalDate;
  1083.             }

  1084.             // get the initial additional data that are not managed
  1085.             final FieldDataDictionary<T> unmanaged = new FieldDataDictionary<>(startDate.getField());
  1086.             for (final FieldDataDictionary<T>.Entry initial : getInitialState().getAdditionalDataValues().getData()) {
  1087.                 if (!FieldAbstractIntegratedPropagator.this.isAdditionalDataManaged(initial.getKey())) {
  1088.                     // this additional state was in the initial state, but is unknown to the propagator
  1089.                     // we simply copy its initial value as is
  1090.                     unmanaged.put(initial.getKey(), initial.getValue());
  1091.                 }
  1092.             }

  1093.             // get the names of additional states managed by differential equations
  1094.             final String[] names      = new String[additionalDerivativesProviders.size()];
  1095.             final int[]    dimensions = new int[additionalDerivativesProviders.size()];
  1096.             for (int i = 0; i < names.length; ++i) {
  1097.                 names[i] = additionalDerivativesProviders.get(i).getName();
  1098.                 dimensions[i] = additionalDerivativesProviders.get(i).getDimension();
  1099.             }

  1100.             // create the ephemeris
  1101.             ephemeris = new FieldIntegratedEphemeris<>(startDate, minDate, maxDate,
  1102.                                                        stateMapper, getAttitudeProvider(), propagationType, model,
  1103.                                                        unmanaged, getAdditionalDataProviders(),
  1104.                                                        names, dimensions);

  1105.         }

  1106.     }

  1107.     /** Wrapper for resetting an integrator handlers.
  1108.      * <p>
  1109.      * This class is intended to be used in a try-with-resource statement.
  1110.      * If propagator-specific event handlers and step handlers are added to
  1111.      * the integrator in the try block, they will be removed automatically
  1112.      * when leaving the block, so the integrator only keep its own handlers
  1113.      * between calls to {@link FieldAbstractIntegratedPropagator#propagate(FieldAbsoluteDate, FieldAbsoluteDate).
  1114.      * </p>
  1115.      * @param <T> the type of the field elements
  1116.      * @since 11.0
  1117.      */
  1118.     private static class IntegratorResetter<T extends CalculusFieldElement<T>> implements AutoCloseable {

  1119.         /** Wrapped integrator. */
  1120.         private final FieldODEIntegrator<T> integrator;

  1121.         /** Initial event detectors list. */
  1122.         private final List<FieldODEEventDetector<T>> detectors;

  1123.         /** Initial step handlers list. */
  1124.         private final List<FieldODEStepHandler<T>> stepHandlers;

  1125.         /** Simple constructor.
  1126.          * @param integrator wrapped integrator
  1127.          */
  1128.         IntegratorResetter(final FieldODEIntegrator<T> integrator) {
  1129.             this.integrator   = integrator;
  1130.             this.detectors    = new ArrayList<>(integrator.getEventDetectors());
  1131.             this.stepHandlers = new ArrayList<>(integrator.getStepHandlers());
  1132.         }

  1133.         /** {@inheritDoc}
  1134.          * <p>
  1135.          * Reset event handlers and step handlers back to the initial list
  1136.          * </p>
  1137.          */
  1138.         @Override
  1139.         public void close() {

  1140.             // reset event handlers
  1141.             integrator.clearEventDetectors();
  1142.             detectors.forEach(integrator::addEventDetector);

  1143.             // reset step handlers
  1144.             integrator.clearStepHandlers();
  1145.             stepHandlers.forEach(integrator::addStepHandler);

  1146.         }

  1147.     }

  1148. }