FieldAbstractIntegratedPropagator.java

  1. /* Copyright 2002-2024 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.propagation.integration;

  18. 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.errors.OrekitException;
  50. import org.orekit.errors.OrekitInternalError;
  51. import org.orekit.errors.OrekitMessages;
  52. import org.orekit.frames.Frame;
  53. import org.orekit.orbits.OrbitType;
  54. import org.orekit.orbits.PositionAngleType;
  55. import org.orekit.propagation.FieldAbstractPropagator;
  56. import org.orekit.propagation.FieldBoundedPropagator;
  57. import org.orekit.propagation.FieldEphemerisGenerator;
  58. import org.orekit.propagation.FieldSpacecraftState;
  59. import org.orekit.propagation.PropagationType;
  60. import org.orekit.propagation.events.FieldEventDetector;
  61. import org.orekit.propagation.events.handlers.FieldEventHandler;
  62. import org.orekit.propagation.sampling.FieldOrekitStepHandler;
  63. import org.orekit.propagation.sampling.FieldOrekitStepInterpolator;
  64. import org.orekit.time.FieldAbsoluteDate;
  65. import org.orekit.utils.FieldArrayDictionary;


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

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

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

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

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

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

  86.     /** Additional derivatives providers.
  87.      * @since 11.1
  88.      */
  89.     private List<FieldAdditionalDerivativesProvider<T>> additionalDerivativesProviders;

  90.     /** Counter for differential equations calls. */
  91.     private int calls;

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

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

  96.     /** Type of orbit to output (mean or osculating) <br/>
  97.      * <p>
  98.      * This is used only in the case of semi-analytical propagators where there is a clear separation between
  99.      * mean and short periodic elements. It is ignored by the Numerical propagator.
  100.      * </p>
  101.      */
  102.     private PropagationType propagationType;

  103.     /** Build a new instance.
  104.      * @param integrator numerical integrator to use for propagation.
  105.      * @param propagationType type of orbit to output (mean or osculating).
  106.      * @param field Field used by default
  107.      */
  108.     protected FieldAbstractIntegratedPropagator(final Field<T> field, final FieldODEIntegrator<T> integrator, final PropagationType propagationType) {
  109.         super(field);
  110.         detectors                      = new ArrayList<>();
  111.         ephemerisGenerators            = new ArrayList<>();
  112.         additionalDerivativesProviders = new ArrayList<>();
  113.         this.secondaryOffsets          = new HashMap<>();
  114.         this.integrator                = integrator;
  115.         this.propagationType           = propagationType;
  116.         this.resetAtEnd                = true;
  117.     }

  118.     /** Allow/disallow resetting the initial state at end of propagation.
  119.      * <p>
  120.      * By default, at the end of the propagation, the propagator resets the initial state
  121.      * to the final state, thus allowing a new propagation to be started from there without
  122.      * recomputing the part already performed. Calling this method with {@code resetAtEnd} set
  123.      * to false changes prevents such reset.
  124.      * </p>
  125.      * @param resetAtEnd if true, at end of each propagation, the {@link
  126.      * #getInitialState() initial state} will be reset to the final state of
  127.      * the propagation, otherwise the initial state will be preserved
  128.      * @since 9.0
  129.      */
  130.     public void setResetAtEnd(final boolean resetAtEnd) {
  131.         this.resetAtEnd = resetAtEnd;
  132.     }

  133.     /** Getter for the resetting flag regarding initial state.
  134.      * @return resetting flag
  135.      * @since 12.0
  136.      */
  137.     public boolean getResetAtEnd() {
  138.         return this.resetAtEnd;
  139.     }

  140.     /** Initialize the mapper.
  141.      * @param field Field used by default
  142.      */
  143.     protected void initMapper(final Field<T> field) {
  144.         final T zero = field.getZero();
  145.         stateMapper = createMapper(null, zero.add(Double.NaN), null, null, null, null);
  146.     }

  147.     /** Get the integrator's name.
  148.      * @return name of underlying integrator
  149.      * @since 12.0
  150.      */
  151.     public String getIntegratorName() {
  152.         return integrator.getName();
  153.     }

  154.     /**  {@inheritDoc} */
  155.     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
  156.         super.setAttitudeProvider(attitudeProvider);
  157.         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
  158.                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
  159.                                    attitudeProvider, stateMapper.getFrame());
  160.     }

  161.     /** Set propagation orbit type.
  162.      * @param orbitType orbit type to use for propagation
  163.      */
  164.     protected void setOrbitType(final OrbitType orbitType) {
  165.         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
  166.                                    orbitType, stateMapper.getPositionAngleType(),
  167.                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
  168.     }

  169.     /** Get propagation parameter type.
  170.      * @return orbit type used for propagation
  171.      */
  172.     protected OrbitType getOrbitType() {
  173.         return stateMapper.getOrbitType();
  174.     }

  175.     /** Check if only the mean elements should be used in a semi-analytical propagation.
  176.      * @return {@link PropagationType MEAN} if only mean elements have to be used or
  177.      *         {@link PropagationType OSCULATING} if osculating elements have to be also used.
  178.      */
  179.     protected PropagationType isMeanOrbit() {
  180.         return propagationType;
  181.     }

  182.     /** Get the propagation type.
  183.      * @return propagation type.
  184.      * @since 11.3.2
  185.      */
  186.     public PropagationType getPropagationType() {
  187.         return propagationType;
  188.     }

  189.     /** Set position angle type.
  190.      * <p>
  191.      * The position parameter type is meaningful only if {@link
  192.      * #getOrbitType() propagation orbit type}
  193.      * support it. As an example, it is not meaningful for propagation
  194.      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
  195.      * </p>
  196.      * @param positionAngleType angle type to use for propagation
  197.      */
  198.     protected void setPositionAngleType(final PositionAngleType positionAngleType) {
  199.         stateMapper = createMapper(stateMapper.getReferenceDate(), stateMapper.getMu(),
  200.                                    stateMapper.getOrbitType(), positionAngleType,
  201.                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
  202.     }

  203.     /** Get propagation parameter type.
  204.      * @return angle type to use for propagation
  205.      */
  206.     protected PositionAngleType getPositionAngleType() {
  207.         return stateMapper.getPositionAngleType();
  208.     }

  209.     /** Set the central attraction coefficient μ.
  210.      * @param mu central attraction coefficient (m³/s²)
  211.      */
  212.     public void setMu(final T mu) {
  213.         stateMapper = createMapper(stateMapper.getReferenceDate(), mu,
  214.                                    stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
  215.                                    stateMapper.getAttitudeProvider(), stateMapper.getFrame());
  216.     }

  217.     /** Get the central attraction coefficient μ.
  218.      * @return mu central attraction coefficient (m³/s²)
  219.      * @see #setMu(CalculusFieldElement)
  220.      */
  221.     public T getMu() {
  222.         return stateMapper.getMu();
  223.     }

  224.     /** Get the number of calls to the differential equations computation method.
  225.      * <p>The number of calls is reset each time the {@link #propagate(FieldAbsoluteDate)}
  226.      * method is called.</p>
  227.      * @return number of calls to the differential equations computation method
  228.      */
  229.     public int getCalls() {
  230.         return calls;
  231.     }

  232.     /** {@inheritDoc} */
  233.     @Override
  234.     public boolean isAdditionalStateManaged(final String name) {

  235.         // first look at already integrated states
  236.         if (super.isAdditionalStateManaged(name)) {
  237.             return true;
  238.         }

  239.         // then look at states we integrate ourselves
  240.         for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  241.             if (provider.getName().equals(name)) {
  242.                 return true;
  243.             }
  244.         }

  245.         return false;
  246.     }

  247.     /** {@inheritDoc} */
  248.     @Override
  249.     public String[] getManagedAdditionalStates() {
  250.         final String[] alreadyIntegrated = super.getManagedAdditionalStates();
  251.         final String[] managed = new String[alreadyIntegrated.length + additionalDerivativesProviders.size()];
  252.         System.arraycopy(alreadyIntegrated, 0, managed, 0, alreadyIntegrated.length);
  253.         for (int i = 0; i < additionalDerivativesProviders.size(); ++i) {
  254.             managed[i + alreadyIntegrated.length] = additionalDerivativesProviders.get(i).getName();
  255.         }
  256.         return managed;
  257.     }

  258.     /** Add a provider for user-specified state derivatives to be integrated along with the orbit propagation.
  259.      * @param provider provider for additional derivatives
  260.      * @see #addAdditionalStateProvider(org.orekit.propagation.FieldAdditionalStateProvider)
  261.      * @since 11.1
  262.      */
  263.     public void addAdditionalDerivativesProvider(final FieldAdditionalDerivativesProvider<T> provider) {
  264.         // check if the name is already used
  265.         if (isAdditionalStateManaged(provider.getName())) {
  266.             // these derivatives are already registered, complain
  267.             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_NAME_ALREADY_IN_USE,
  268.                                       provider.getName());
  269.         }

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

  272.         secondaryOffsets.clear();

  273.     }

  274.     /** Get an unmodifiable list of providers for additional derivatives.
  275.      * @return providers for additional derivatives
  276.      * @since 11.1
  277.      */
  278.     public List<FieldAdditionalDerivativesProvider<T>> getAdditionalDerivativesProviders() {
  279.         return Collections.unmodifiableList(additionalDerivativesProviders);
  280.     }

  281.     /** {@inheritDoc} */
  282.     public <D extends FieldEventDetector<T>> void addEventDetector(final D detector) {
  283.         detectors.add(detector);
  284.     }

  285.     /** {@inheritDoc} */
  286.     public Collection<FieldEventDetector<T>> getEventsDetectors() {
  287.         return Collections.unmodifiableCollection(detectors);
  288.     }

  289.     /** {@inheritDoc} */
  290.     public void clearEventsDetectors() {
  291.         detectors.clear();
  292.     }

  293.     /** Set up all user defined event detectors.
  294.      */
  295.     protected void setUpUserEventDetectors() {
  296.         for (final FieldEventDetector<T> detector : detectors) {
  297.             setUpEventDetector(integrator, detector);
  298.         }
  299.     }

  300.     /** Wrap an Orekit event detector and register it to the integrator.
  301.      * @param integ integrator into which event detector should be registered
  302.      * @param detector event detector to wrap
  303.      */
  304.     protected void setUpEventDetector(final FieldODEIntegrator<T> integ, final FieldEventDetector<T> detector) {
  305.         integ.addEventDetector(new FieldAdaptedEventDetector(detector));
  306.     }

  307.     /** {@inheritDoc} */
  308.     @Override
  309.     public FieldEphemerisGenerator<T> getEphemerisGenerator() {
  310.         final FieldStoringStepHandler storingHandler = new FieldStoringStepHandler();
  311.         ephemerisGenerators.add(storingHandler);
  312.         return storingHandler;
  313.     }

  314.     /** Create a mapper between raw double components and spacecraft state.
  315.     /** Simple constructor.
  316.      * <p>
  317.      * The position parameter type is meaningful only if {@link
  318.      * #getOrbitType() propagation orbit type}
  319.      * support it. As an example, it is not meaningful for propagation
  320.      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
  321.      * </p>
  322.      * @param referenceDate reference date
  323.      * @param mu central attraction coefficient (m³/s²)
  324.      * @param orbitType orbit type to use for mapping
  325.      * @param positionAngleType angle type to use for propagation
  326.      * @param attitudeProvider attitude provider
  327.      * @param frame inertial frame
  328.      * @return new mapper
  329.      */
  330.     protected abstract FieldStateMapper<T> createMapper(FieldAbsoluteDate<T> referenceDate, T mu,
  331.                                                         OrbitType orbitType, PositionAngleType positionAngleType,
  332.                                                         AttitudeProvider attitudeProvider, Frame frame);

  333.     /** Get the differential equations to integrate (for main state only).
  334.      * @param integ numerical integrator to use for propagation.
  335.      * @return differential equations for main state
  336.      */
  337.     protected abstract MainStateEquations<T> getMainStateEquations(FieldODEIntegrator<T> integ);

  338.     /** {@inheritDoc} */
  339.     public FieldSpacecraftState<T> propagate(final FieldAbsoluteDate<T> target) {
  340.         if (getStartDate() == null) {
  341.             if (getInitialState() == null) {
  342.                 throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
  343.             }
  344.             setStartDate(getInitialState().getDate());
  345.         }
  346.         return propagate(getStartDate(), target);
  347.     }

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

  350.         if (getInitialState() == null) {
  351.             throw new OrekitException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION);
  352.         }

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

  355.             // Initialize additional states
  356.             initializeAdditionalStates(tEnd);

  357.             if (!tStart.equals(getInitialState().getDate())) {
  358.                 // if propagation start date is not initial date,
  359.                 // propagate from initial to start date without event detection
  360.                 try (IntegratorResetter<T> startResetter = new IntegratorResetter<>(integrator)) {
  361.                     integrateDynamics(tStart);
  362.                 }
  363.             }

  364.             // set up events added by user
  365.             setUpUserEventDetectors();

  366.             // set up step handlers
  367.             for (final FieldOrekitStepHandler<T> handler : getMultiplexer().getHandlers()) {
  368.                 integrator.addStepHandler(new FieldAdaptedStepHandler(handler));
  369.             }
  370.             for (final FieldStoringStepHandler generator : ephemerisGenerators) {
  371.                 generator.setEndDate(tEnd);
  372.                 integrator.addStepHandler(generator);
  373.             }

  374.             // propagate from start date to end date with event detection
  375.             return integrateDynamics(tEnd);

  376.         }

  377.     }

  378.     /** Propagation with or without event detection.
  379.      * @param tEnd target date to which orbit should be propagated
  380.      * @return state at end of propagation
  381.      */
  382.     private FieldSpacecraftState<T> integrateDynamics(final FieldAbsoluteDate<T> tEnd) {
  383.         try {

  384.             initializePropagation();

  385.             if (getInitialState().getDate().equals(tEnd)) {
  386.                 // don't extrapolate
  387.                 return getInitialState();
  388.             }
  389.             // space dynamics view
  390.             stateMapper = createMapper(getInitialState().getDate(), stateMapper.getMu(),
  391.                                        stateMapper.getOrbitType(), stateMapper.getPositionAngleType(),
  392.                                        stateMapper.getAttitudeProvider(), getInitialState().getFrame());


  393.             // set propagation orbit type
  394.             //final FieldOrbit<T> initialOrbit = stateMapper.getOrbitType().convertType(getInitialState().getOrbit());
  395.             if (Double.isNaN(getMu().getReal())) {
  396.                 setMu(getInitialState().getMu());
  397.             }
  398.             if (getInitialState().getMass().getReal() <= 0.0) {
  399.                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS,
  400.                                                getInitialState().getMass());
  401.             }

  402.             // convert space flight dynamics API to math API
  403.             final FieldSpacecraftState<T> initialIntegrationState = getInitialIntegrationState();
  404.             final FieldODEState<T> mathInitialState = createInitialState(initialIntegrationState);
  405.             final FieldExpandableODE<T> mathODE = createODE(integrator);

  406.             // mathematical integration
  407.             final FieldODEStateAndDerivative<T> mathFinalState;
  408.             beforeIntegration(initialIntegrationState, tEnd);
  409.             mathFinalState = integrator.integrate(mathODE, mathInitialState,
  410.                                                   tEnd.durationFrom(getInitialState().getDate()));

  411.             afterIntegration();

  412.             // get final state
  413.             FieldSpacecraftState<T> finalState =
  414.                             stateMapper.mapArrayToState(stateMapper.mapDoubleToDate(mathFinalState.getTime(), tEnd),
  415.                                                         mathFinalState.getPrimaryState(),
  416.                                                         mathFinalState.getPrimaryDerivative(),
  417.                                                         propagationType);
  418.             if (!additionalDerivativesProviders.isEmpty()) {
  419.                 final T[] secondary            = mathFinalState.getSecondaryState(1);
  420.                 final T[] secondaryDerivatives = mathFinalState.getSecondaryDerivative(1);
  421.                 for (FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  422.                     final String   name        = provider.getName();
  423.                     final int      offset      = secondaryOffsets.get(name);
  424.                     final int      dimension   = provider.getDimension();
  425.                     finalState = finalState.
  426.                                  addAdditionalState(name, Arrays.copyOfRange(secondary, offset, offset + dimension)).
  427.                                  addAdditionalStateDerivative(name, Arrays.copyOfRange(secondaryDerivatives, offset, offset + dimension));
  428.                 }
  429.             }
  430.             finalState = updateAdditionalStates(finalState);

  431.             if (resetAtEnd) {
  432.                 resetInitialState(finalState);
  433.                 setStartDate(finalState.getDate());
  434.             }

  435.             return finalState;

  436.         } catch (OrekitException pe) {
  437.             throw pe;
  438.         } catch (MathIllegalArgumentException | MathIllegalStateException me) {
  439.             throw OrekitException.unwrap(me);
  440.         }
  441.     }

  442.     /** Get the initial state for integration.
  443.      * @return initial state for integration
  444.      */
  445.     protected FieldSpacecraftState<T> getInitialIntegrationState() {
  446.         return getInitialState();
  447.     }

  448.     /** Create an initial state.
  449.      * @param initialState initial state in flight dynamics world
  450.      * @return initial state in mathematics world
  451.      */
  452.     private FieldODEState<T> createInitialState(final FieldSpacecraftState<T> initialState) {

  453.         // retrieve initial state
  454.         final T[] primary  = MathArrays.buildArray(initialState.getA().getField(), getBasicDimension());
  455.         stateMapper.mapStateToArray(initialState, primary, null);

  456.         if (secondaryOffsets.isEmpty()) {
  457.             // compute dimension of the secondary state
  458.             int offset = 0;
  459.             for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  460.                 secondaryOffsets.put(provider.getName(), offset);
  461.                 offset += provider.getDimension();
  462.             }
  463.             secondaryOffsets.put(SECONDARY_DIMENSION, offset);
  464.         }

  465.         return new FieldODEState<>(initialState.getA().getField().getZero(), primary, secondary(initialState));

  466.     }

  467.     /** Create secondary state.
  468.      * @param state spacecraft state
  469.      * @return secondary state
  470.      * @since 11.1
  471.      */
  472.     private T[][] secondary(final FieldSpacecraftState<T> state) {

  473.         if (secondaryOffsets.isEmpty()) {
  474.             return null;
  475.         }

  476.         final T[][] secondary = MathArrays.buildArray(state.getDate().getField(), 1, secondaryOffsets.get(SECONDARY_DIMENSION));
  477.         for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  478.             final String name       = provider.getName();
  479.             final int    offset     = secondaryOffsets.get(name);
  480.             final T[]    additional = state.getAdditionalState(name);
  481.             System.arraycopy(additional, 0, secondary[0], offset, additional.length);
  482.         }

  483.         return secondary;

  484.     }

  485.     /** Create secondary state derivative.
  486.      * @param state spacecraft state
  487.      * @return secondary state derivative
  488.      * @since 11.1
  489.      */
  490.     private T[][] secondaryDerivative(final FieldSpacecraftState<T> state) {

  491.         if (secondaryOffsets.isEmpty()) {
  492.             return null;
  493.         }

  494.         final T[][] secondaryDerivative = MathArrays.buildArray(state.getDate().getField(), 1, secondaryOffsets.get(SECONDARY_DIMENSION));
  495.         for (final FieldAdditionalDerivativesProvider<T> providcer : additionalDerivativesProviders) {
  496.             final String name       = providcer.getName();
  497.             final int    offset     = secondaryOffsets.get(name);
  498.             final T[]    additionalDerivative = state.getAdditionalStateDerivative(name);
  499.             System.arraycopy(additionalDerivative, 0, secondaryDerivative[0], offset, additionalDerivative.length);
  500.         }

  501.         return secondaryDerivative;

  502.     }

  503.     /** Create an ODE with all equations.
  504.      * @param integ numerical integrator to use for propagation.
  505.      * @return a new ode
  506.      */
  507.     private FieldExpandableODE<T> createODE(final FieldODEIntegrator<T> integ) {

  508.         final FieldExpandableODE<T> ode =
  509.                 new FieldExpandableODE<>(new ConvertedMainStateEquations(getMainStateEquations(integ)));

  510.         // secondary part of the ODE
  511.         if (!additionalDerivativesProviders.isEmpty()) {
  512.             ode.addSecondaryEquations(new ConvertedSecondaryStateEquations());
  513.         }

  514.         return ode;

  515.     }

  516.     /** Method called just before integration.
  517.      * <p>
  518.      * The default implementation does nothing, it may be specialized in subclasses.
  519.      * </p>
  520.      * @param initialState initial state
  521.      * @param tEnd target date at which state should be propagated
  522.      */
  523.     protected void beforeIntegration(final FieldSpacecraftState<T> initialState,
  524.                                      final FieldAbsoluteDate<T> tEnd) {
  525.         // do nothing by default
  526.     }

  527.     /** Method called just after integration.
  528.      * <p>
  529.      * The default implementation does nothing, it may be specialized in subclasses.
  530.      * </p>
  531.      */
  532.     protected void afterIntegration() {
  533.         // do nothing by default
  534.     }

  535.     /** Get state vector dimension without additional parameters.
  536.      * @return state vector dimension without additional parameters.
  537.      */
  538.     public int getBasicDimension() {
  539.         return 7;

  540.     }

  541.     /** Get the integrator used by the propagator.
  542.      * @return the integrator.
  543.      */
  544.     protected FieldODEIntegrator<T> getIntegrator() {
  545.         return integrator;
  546.     }

  547.     /** Convert a state from mathematical world to space flight dynamics world.
  548.      * @param os mathematical state
  549.      * @return space flight dynamics state
  550.      */
  551.     private FieldSpacecraftState<T> convert(final FieldODEStateAndDerivative<T> os) {

  552.         FieldSpacecraftState<T> s =
  553.                         stateMapper.mapArrayToState(os.getTime(),
  554.                                                     os.getPrimaryState(),
  555.                                                     os.getPrimaryDerivative(),
  556.                                                     propagationType);
  557.         if (os.getNumberOfSecondaryStates() > 0) {
  558.             final T[] secondary           = os.getSecondaryState(1);
  559.             final T[] secondaryDerivative = os.getSecondaryDerivative(1);
  560.             for (final FieldAdditionalDerivativesProvider<T> equations : additionalDerivativesProviders) {
  561.                 final String name      = equations.getName();
  562.                 final int    offset    = secondaryOffsets.get(name);
  563.                 final int    dimension = equations.getDimension();
  564.                 s = s.addAdditionalState(name, Arrays.copyOfRange(secondary, offset, offset + dimension));
  565.                 s = s.addAdditionalStateDerivative(name, Arrays.copyOfRange(secondaryDerivative, offset, offset + dimension));
  566.             }
  567.         }
  568.         s = updateAdditionalStates(s);

  569.         return s;

  570.     }

  571.     /** Convert a state from space flight dynamics world to mathematical world.
  572.      * @param state space flight dynamics state
  573.      * @return mathematical state
  574.      */
  575.     private FieldODEStateAndDerivative<T> convert(final FieldSpacecraftState<T> state) {

  576.         // retrieve initial state
  577.         final T[] primary    = MathArrays.buildArray(getField(), getBasicDimension());
  578.         final T[] primaryDot = MathArrays.buildArray(getField(), getBasicDimension());
  579.         stateMapper.mapStateToArray(state, primary, primaryDot);

  580.         // secondary part of the ODE
  581.         final T[][] secondary           = secondary(state);
  582.         final T[][] secondaryDerivative = secondaryDerivative(state);

  583.         return new FieldODEStateAndDerivative<>(stateMapper.mapDateToDouble(state.getDate()),
  584.                                                 primary, primaryDot,
  585.                                                 secondary, secondaryDerivative);

  586.     }

  587.     /** Differential equations for the main state (orbit, attitude and mass).
  588.      * @param <T> type of the field element
  589.      */
  590.     public interface MainStateEquations<T extends CalculusFieldElement<T>> {

  591.         /**
  592.          * Initialize the equations at the start of propagation. This method will be
  593.          * called before any calls to {@link #computeDerivatives(FieldSpacecraftState)}.
  594.          *
  595.          * <p> The default implementation of this method does nothing.
  596.          *
  597.          * @param initialState initial state information at the start of propagation.
  598.          * @param target       date of propagation. Not equal to {@code
  599.          *                     initialState.getDate()}.
  600.          */
  601.         void init(FieldSpacecraftState<T> initialState, FieldAbsoluteDate<T> target);

  602.         /** Compute differential equations for main state.
  603.          * @param state current state
  604.          * @return derivatives of main state
  605.          */
  606.         T[] computeDerivatives(FieldSpacecraftState<T> state);

  607.     }

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

  610.         /** Main state equations. */
  611.         private final MainStateEquations<T> main;

  612.         /** Simple constructor.
  613.          * @param main main state equations
  614.          */
  615.         ConvertedMainStateEquations(final MainStateEquations<T> main) {
  616.             this.main = main;
  617.             calls = 0;
  618.         }

  619.         /** {@inheritDoc} */
  620.         public int getDimension() {
  621.             return getBasicDimension();
  622.         }

  623.         @Override
  624.         public void init(final T t0, final T[] y0, final T finalTime) {
  625.             // update space dynamics view
  626.             FieldSpacecraftState<T> initialState = stateMapper.mapArrayToState(t0, y0, null, PropagationType.MEAN);
  627.             initialState = updateAdditionalStates(initialState);
  628.             final FieldAbsoluteDate<T> target = stateMapper.mapDoubleToDate(finalTime);
  629.             main.init(initialState, target);
  630.         }
  631.         /** {@inheritDoc} */
  632.         public T[] computeDerivatives(final T t, final T[] y) {

  633.             // increment calls counter
  634.             ++calls;

  635.             // update space dynamics view
  636.             FieldSpacecraftState<T> currentState = stateMapper.mapArrayToState(t, y, null, PropagationType.MEAN);
  637.             currentState = updateAdditionalStates(currentState);

  638.             // compute main state differentials
  639.             return main.computeDerivatives(currentState);

  640.         }

  641.     }

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

  644.         /** Dimension of the combined additional states. */
  645.         private final int combinedDimension;

  646.         /** Simple constructor.
  647.          */
  648.         ConvertedSecondaryStateEquations() {
  649.             this.combinedDimension = secondaryOffsets.get(SECONDARY_DIMENSION);
  650.         }

  651.         /** {@inheritDoc} */
  652.         @Override
  653.         public int getDimension() {
  654.             return combinedDimension;
  655.         }

  656.         /** {@inheritDoc} */
  657.         @Override
  658.         public void init(final T t0, final T[] primary0,
  659.                          final T[] secondary0, final T finalTime) {
  660.             // update space dynamics view
  661.             final FieldSpacecraftState<T> initialState = convert(t0, primary0, null, secondary0);

  662.             final FieldAbsoluteDate<T> target = stateMapper.mapDoubleToDate(finalTime);
  663.             for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  664.                 provider.init(initialState, target);
  665.             }

  666.         }

  667.         /** {@inheritDoc} */
  668.         @Override
  669.         public T[] computeDerivatives(final T t, final T[] primary,
  670.                                       final T[] primaryDot, final T[] secondary) {

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

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

  677.             // gather the derivatives from all additional equations, taking care of dependencies
  678.             final T[] secondaryDot = MathArrays.buildArray(t.getField(), combinedDimension);
  679.             int yieldCount = 0;
  680.             while (!pending.isEmpty()) {
  681.                 final FieldAdditionalDerivativesProvider<T> equations = pending.remove();
  682.                 if (equations.yields(updated)) {
  683.                     // these equations have to wait for another set,
  684.                     // we put them again in the pending queue
  685.                     pending.add(equations);
  686.                     if (++yieldCount >= pending.size()) {
  687.                         // all pending equations yielded!, they probably need data not yet initialized
  688.                         // we let the propagation proceed, if these data are really needed right now
  689.                         // an appropriate exception will be triggered when caller tries to access them
  690.                         break;
  691.                     }
  692.                 } else {
  693.                     // we can use these equations right now
  694.                     final String                      name           = equations.getName();
  695.                     final int                         offset         = secondaryOffsets.get(name);
  696.                     final int                         dimension      = equations.getDimension();
  697.                     final FieldCombinedDerivatives<T> derivatives    = equations.combinedDerivatives(updated);
  698.                     final T[]                         additionalPart = derivatives.getAdditionalDerivatives();
  699.                     final T[]                         mainPart       = derivatives.getMainStateDerivativesIncrements();
  700.                     System.arraycopy(additionalPart, 0, secondaryDot, offset, dimension);
  701.                     updated = updated.addAdditionalStateDerivative(name, additionalPart);
  702.                     if (mainPart != null) {
  703.                         // this equation does change the main state derivatives
  704.                         for (int i = 0; i < mainPart.length; ++i) {
  705.                             primaryDot[i] = primaryDot[i].add(mainPart[i]);
  706.                         }
  707.                     }
  708.                     yieldCount = 0;
  709.                 }
  710.             }

  711.             return secondaryDot;

  712.         }

  713.         /** Convert mathematical view to space view.
  714.          * @param t current value of the independent <I>time</I> variable
  715.          * @param primary array containing the current value of the primary state vector
  716.          * @param primaryDot array containing the derivative of the primary state vector
  717.          * @param secondary array containing the current value of the secondary state vector
  718.          * @return space view of the state
  719.          */
  720.         private FieldSpacecraftState<T> convert(final T t, final T[] primary,
  721.                                                 final T[] primaryDot, final T[] secondary) {

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

  723.             for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  724.                 final String name      = provider.getName();
  725.                 final int    offset    = secondaryOffsets.get(name);
  726.                 final int    dimension = provider.getDimension();
  727.                 initialState = initialState.addAdditionalState(name, Arrays.copyOfRange(secondary, offset, offset + dimension));
  728.             }

  729.             return updateAdditionalStates(initialState);

  730.         }

  731.     }

  732.     /** Adapt an {@link org.orekit.propagation.events.FieldEventDetector<T>}
  733.      * to Hipparchus {@link org.hipparchus.ode.events.FieldODEEventDetector<T>} interface.
  734.      * @param <T> class type for the generic version
  735.      * @author Fabien Maussion
  736.      */
  737.     private class FieldAdaptedEventDetector implements FieldODEEventDetector<T> {

  738.         /** Underlying event detector. */
  739.         private final FieldEventDetector<T> detector;

  740.         /** Underlying event handler.
  741.          * @since 12.0
  742.          */
  743.         private final FieldEventHandler<T> handler;

  744.         /** Time of the previous call to g. */
  745.         private T lastT;

  746.         /** Value from the previous call to g. */
  747.         private T lastG;

  748.         /** Build a wrapped event detector.
  749.          * @param detector event detector to wrap
  750.         */
  751.         FieldAdaptedEventDetector(final FieldEventDetector<T> detector) {
  752.             this.detector = detector;
  753.             this.handler  = detector.getHandler();
  754.             this.lastT    = getField().getZero().add(Double.NaN);
  755.             this.lastG    = getField().getZero().add(Double.NaN);
  756.         }

  757.         /** {@inheritDoc} */
  758.         @Override
  759.         public FieldAdaptableInterval<T> getMaxCheckInterval() {
  760.             return s -> detector.getMaxCheckInterval().currentInterval(convert(s));
  761.         }

  762.         /** {@inheritDoc} */
  763.         @Override
  764.         public int getMaxIterationCount() {
  765.             return detector.getMaxIterationCount();
  766.         }

  767.         /** {@inheritDoc} */
  768.         @Override
  769.         public FieldBracketingNthOrderBrentSolver<T> getSolver() {
  770.             final T zero = detector.getThreshold().getField().getZero();
  771.             return new FieldBracketingNthOrderBrentSolver<>(zero, detector.getThreshold(), zero, 5);
  772.         }

  773.         /** {@inheritDoc} */
  774.         public void init(final FieldODEStateAndDerivative<T> s0, final T t) {
  775.             detector.init(convert(s0), stateMapper.mapDoubleToDate(t));
  776.             this.lastT = getField().getZero().add(Double.NaN);
  777.             this.lastG = getField().getZero().add(Double.NaN);
  778.         }

  779.         /** {@inheritDoc} */
  780.         public T g(final FieldODEStateAndDerivative<T> s) {
  781.             if (!Precision.equals(lastT.getReal(), s.getTime().getReal(), 0)) {
  782.                 lastT = s.getTime();
  783.                 lastG = detector.g(convert(s));
  784.             }
  785.             return lastG;
  786.         }

  787.         /** {@inheritDoc} */
  788.         public FieldODEEventHandler<T> getHandler() {

  789.             return new FieldODEEventHandler<T>() {

  790.                 /** {@inheritDoc} */
  791.                 public Action eventOccurred(final FieldODEStateAndDerivative<T> s,
  792.                                             final FieldODEEventDetector<T> d,
  793.                                             final boolean increasing) {
  794.                     return handler.eventOccurred(convert(s), detector, increasing);
  795.                 }

  796.                 /** {@inheritDoc} */
  797.                 public FieldODEState<T> resetState(final FieldODEEventDetector<T> d,
  798.                                                    final FieldODEStateAndDerivative<T> s) {

  799.                     final FieldSpacecraftState<T> oldState = convert(s);
  800.                     final FieldSpacecraftState<T> newState = handler.resetState(detector, oldState);
  801.                     stateChanged(newState);

  802.                     // main part
  803.                     final T[] primary    = MathArrays.buildArray(getField(), s.getPrimaryStateDimension());
  804.                     stateMapper.mapStateToArray(newState, primary, null);

  805.                     // secondary part
  806.                     final T[][] secondary = MathArrays.buildArray(getField(), 1, additionalDerivativesProviders.size());
  807.                     for (final FieldAdditionalDerivativesProvider<T> provider : additionalDerivativesProviders) {
  808.                         final String name      = provider.getName();
  809.                         final int    offset    = secondaryOffsets.get(name);
  810.                         final int    dimension = provider.getDimension();
  811.                         System.arraycopy(newState.getAdditionalState(name), 0, secondary[0], offset, dimension);
  812.                     }

  813.                     return new FieldODEState<>(newState.getDate().durationFrom(getStartDate()),
  814.                                                primary, secondary);
  815.                 }
  816.             };

  817.         }

  818.     }

  819.     /** Adapt an {@link org.orekit.propagation.sampling.FieldOrekitStepHandler<T>}
  820.      * to Hipparchus {@link FieldODEStepHandler<T>} interface.
  821.      * @author Luc Maisonobe
  822.      */
  823.     private class FieldAdaptedStepHandler implements FieldODEStepHandler<T> {

  824.         /** Underlying handler. */
  825.         private final FieldOrekitStepHandler<T> handler;

  826.         /** Build an instance.
  827.          * @param handler underlying handler to wrap
  828.          */
  829.         FieldAdaptedStepHandler(final FieldOrekitStepHandler<T> handler) {
  830.             this.handler = handler;
  831.         }

  832.         /** {@inheritDoc} */
  833.         public void init(final FieldODEStateAndDerivative<T> s0, final T t) {
  834.             handler.init(convert(s0), stateMapper.mapDoubleToDate(t));
  835.         }

  836.         /** {@inheritDoc} */
  837.         public void handleStep(final FieldODEStateInterpolator<T> interpolator) {
  838.             handler.handleStep(new FieldAdaptedStepInterpolator(interpolator));
  839.         }

  840.         /** {@inheritDoc} */
  841.         @Override
  842.         public void finish(final FieldODEStateAndDerivative<T> finalState) {
  843.             handler.finish(convert(finalState));
  844.         }

  845.     }

  846.     /** Adapt an {@link org.orekit.propagation.sampling.FieldOrekitStepInterpolator<T>}
  847.      * to Hipparchus {@link FieldODEStepInterpolator<T>} interface.
  848.      * @author Luc Maisonobe
  849.      */
  850.     private class FieldAdaptedStepInterpolator implements FieldOrekitStepInterpolator<T> {

  851.         /** Underlying raw rawInterpolator. */
  852.         private final FieldODEStateInterpolator<T> mathInterpolator;

  853.         /** Build an instance.
  854.          * @param mathInterpolator underlying raw interpolator
  855.          */
  856.         FieldAdaptedStepInterpolator(final FieldODEStateInterpolator<T> mathInterpolator) {
  857.             this.mathInterpolator = mathInterpolator;
  858.         }

  859.         /** {@inheritDoc}} */
  860.         @Override
  861.         public FieldSpacecraftState<T> getPreviousState() {
  862.             return convert(mathInterpolator.getPreviousState());
  863.         }

  864.         /** {@inheritDoc}} */
  865.         @Override
  866.         public FieldSpacecraftState<T> getCurrentState() {
  867.             return convert(mathInterpolator.getCurrentState());
  868.         }

  869.         /** {@inheritDoc}} */
  870.         @Override
  871.         public FieldSpacecraftState<T> getInterpolatedState(final FieldAbsoluteDate<T> date) {
  872.             return convert(mathInterpolator.getInterpolatedState(date.durationFrom(getStartDate())));
  873.         }

  874.         /** Check is integration direction is forward in date.
  875.          * @return true if integration is forward in date
  876.          */
  877.         public boolean isForward() {
  878.             return mathInterpolator.isForward();
  879.         }

  880.         /** {@inheritDoc}} */
  881.         @Override
  882.         public FieldAdaptedStepInterpolator restrictStep(final FieldSpacecraftState<T> newPreviousState,
  883.                                                          final FieldSpacecraftState<T> newCurrentState) {
  884.             try {
  885.                 final AbstractFieldODEStateInterpolator<T> aosi = (AbstractFieldODEStateInterpolator<T>) mathInterpolator;
  886.                 return new FieldAdaptedStepInterpolator(aosi.restrictStep(convert(newPreviousState),
  887.                                                                           convert(newCurrentState)));
  888.             } catch (ClassCastException cce) {
  889.                 // this should never happen
  890.                 throw new OrekitInternalError(cce);
  891.             }
  892.         }

  893.     }

  894.     /** Specialized step handler storing interpolators for ephemeris generation.
  895.      * @since 11.0
  896.      */
  897.     private class FieldStoringStepHandler implements FieldODEStepHandler<T>, FieldEphemerisGenerator<T> {

  898.         /** Underlying raw mathematical model. */
  899.         private FieldDenseOutputModel<T> model;

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

  902.         /** Generated ephemeris. */
  903.         private FieldBoundedPropagator<T> ephemeris;

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

  906.         /** Set the end date.
  907.          * @param endDate end date
  908.          */
  909.         public void setEndDate(final FieldAbsoluteDate<T> endDate) {
  910.             this.endDate = endDate;
  911.         }

  912.         /** {@inheritDoc} */
  913.         @Override
  914.         public void init(final FieldODEStateAndDerivative<T> s0, final T t) {
  915.             this.model = new FieldDenseOutputModel<>();
  916.             model.init(s0, t);

  917.             // ephemeris will be generated when last step is processed
  918.             this.ephemeris = null;

  919.             this.lastInterpolator = null;

  920.         }

  921.         /** {@inheritDoc} */
  922.         @Override
  923.         public FieldBoundedPropagator<T> getGeneratedEphemeris() {
  924.             // Each time we try to get the ephemeris, rebuild it using the last data.
  925.             buildEphemeris();
  926.             return ephemeris;
  927.         }

  928.         /** {@inheritDoc} */
  929.         @Override
  930.         public void handleStep(final FieldODEStateInterpolator<T> interpolator) {
  931.             model.handleStep(interpolator);
  932.             lastInterpolator = interpolator;
  933.         }

  934.         /** {@inheritDoc} */
  935.         @Override
  936.         public void finish(final FieldODEStateAndDerivative<T> finalState) {
  937.             buildEphemeris();
  938.         }

  939.         /** Method used to produce ephemeris at a given time.
  940.          * Can be used at multiple times, updating the ephemeris to
  941.          * its last state.
  942.          */
  943.         private void buildEphemeris() {
  944.             // buildEphemeris was built in order to allow access to what was previously the finish method.
  945.             // This now allows to call it through getGeneratedEphemeris, therefore through an external call,
  946.             // which was not previously the case.

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

  949.             // set up the boundary dates
  950.             final T tI = model.getInitialTime();
  951.             final T tF = model.getFinalTime();
  952.             // tI is almost? always zero
  953.             final FieldAbsoluteDate<T> startDate =
  954.                             stateMapper.mapDoubleToDate(tI);
  955.             final FieldAbsoluteDate<T> finalDate =
  956.                             stateMapper.mapDoubleToDate(tF, this.endDate);
  957.             final FieldAbsoluteDate<T> minDate;
  958.             final FieldAbsoluteDate<T> maxDate;
  959.             if (tF.getReal() < tI.getReal()) {
  960.                 minDate = finalDate;
  961.                 maxDate = startDate;
  962.             } else {
  963.                 minDate = startDate;
  964.                 maxDate = finalDate;
  965.             }

  966.             // get the initial additional states that are not managed
  967.             final FieldArrayDictionary<T> unmanaged = new FieldArrayDictionary<>(startDate.getField());
  968.             for (final FieldArrayDictionary<T>.Entry initial : getInitialState().getAdditionalStatesValues().getData()) {
  969.                 if (!isAdditionalStateManaged(initial.getKey())) {
  970.                     // this additional state was in the initial state, but is unknown to the propagator
  971.                     // we simply copy its initial value as is
  972.                     unmanaged.put(initial.getKey(), initial.getValue());
  973.                 }
  974.             }

  975.             // get the names of additional states managed by differential equations
  976.             final String[] names      = new String[additionalDerivativesProviders.size()];
  977.             final int[]    dimensions = new int[additionalDerivativesProviders.size()];
  978.             for (int i = 0; i < names.length; ++i) {
  979.                 names[i] = additionalDerivativesProviders.get(i).getName();
  980.                 dimensions[i] = additionalDerivativesProviders.get(i).getDimension();
  981.             }

  982.             // create the ephemeris
  983.             ephemeris = new FieldIntegratedEphemeris<>(startDate, minDate, maxDate,
  984.                                                        stateMapper, propagationType, model,
  985.                                                        unmanaged, getAdditionalStateProviders(),
  986.                                                        names, dimensions);

  987.         }

  988.     }

  989.     /** Wrapper for resetting an integrator handlers.
  990.      * <p>
  991.      * This class is intended to be used in a try-with-resource statement.
  992.      * If propagator-specific event handlers and step handlers are added to
  993.      * the integrator in the try block, they will be removed automatically
  994.      * when leaving the block, so the integrator only keep its own handlers
  995.      * between calls to {@link AbstractIntegratedPropagator#propagate(FieldAbsoluteDate, FieldAbsoluteDate).
  996.      * </p>
  997.      * @param <T> the type of the field elements
  998.      * @since 11.0
  999.      */
  1000.     private static class IntegratorResetter<T extends CalculusFieldElement<T>> implements AutoCloseable {

  1001.         /** Wrapped integrator. */
  1002.         private final FieldODEIntegrator<T> integrator;

  1003.         /** Initial event detectors list. */
  1004.         private final List<FieldODEEventDetector<T>> detectors;

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

  1007.         /** Simple constructor.
  1008.          * @param integrator wrapped integrator
  1009.          */
  1010.         IntegratorResetter(final FieldODEIntegrator<T> integrator) {
  1011.             this.integrator   = integrator;
  1012.             this.detectors    = new ArrayList<>(integrator.getEventDetectors());
  1013.             this.stepHandlers = new ArrayList<>(integrator.getStepHandlers());
  1014.         }

  1015.         /** {@inheritDoc}
  1016.          * <p>
  1017.          * Reset event handlers and step handlers back to the initial list
  1018.          * </p>
  1019.          */
  1020.         @Override
  1021.         public void close() {

  1022.             // reset event handlers
  1023.             integrator.clearEventDetectors();
  1024.             detectors.forEach(integrator::addEventDetector);

  1025.             // reset step handlers
  1026.             integrator.clearStepHandlers();
  1027.             stepHandlers.forEach(integrator::addStepHandler);

  1028.         }

  1029.     }

  1030. }