DSSTPropagator.java

  1. /* Copyright 2002-2022 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.semianalytical.dsst;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Set;

  25. import org.hipparchus.linear.RealMatrix;
  26. import org.hipparchus.ode.ODEIntegrator;
  27. import org.hipparchus.ode.ODEStateAndDerivative;
  28. import org.hipparchus.ode.sampling.ODEStateInterpolator;
  29. import org.hipparchus.ode.sampling.ODEStepHandler;
  30. import org.hipparchus.util.FastMath;
  31. import org.hipparchus.util.MathUtils;
  32. import org.orekit.annotation.DefaultDataContext;
  33. import org.orekit.attitudes.Attitude;
  34. import org.orekit.attitudes.AttitudeProvider;
  35. import org.orekit.data.DataContext;
  36. import org.orekit.errors.OrekitException;
  37. import org.orekit.errors.OrekitInternalError;
  38. import org.orekit.errors.OrekitMessages;
  39. import org.orekit.frames.Frame;
  40. import org.orekit.orbits.EquinoctialOrbit;
  41. import org.orekit.orbits.Orbit;
  42. import org.orekit.orbits.OrbitType;
  43. import org.orekit.orbits.PositionAngle;
  44. import org.orekit.propagation.AbstractMatricesHarvester;
  45. import org.orekit.propagation.MatricesHarvester;
  46. import org.orekit.propagation.PropagationType;
  47. import org.orekit.propagation.Propagator;
  48. import org.orekit.propagation.SpacecraftState;
  49. import org.orekit.propagation.events.EventDetector;
  50. import org.orekit.propagation.integration.AbstractIntegratedPropagator;
  51. import org.orekit.propagation.integration.AdditionalDerivativesProvider;
  52. import org.orekit.propagation.integration.StateMapper;
  53. import org.orekit.propagation.numerical.NumericalPropagator;
  54. import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
  55. import org.orekit.propagation.semianalytical.dsst.forces.DSSTNewtonianAttraction;
  56. import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
  57. import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
  58. import org.orekit.propagation.semianalytical.dsst.utilities.FixedNumberInterpolationGrid;
  59. import org.orekit.propagation.semianalytical.dsst.utilities.InterpolationGrid;
  60. import org.orekit.propagation.semianalytical.dsst.utilities.MaxGapInterpolationGrid;
  61. import org.orekit.time.AbsoluteDate;
  62. import org.orekit.utils.DoubleArrayDictionary;
  63. import org.orekit.utils.ParameterDriver;
  64. import org.orekit.utils.ParameterDriversList;
  65. import org.orekit.utils.ParameterDriversList.DelegatingDriver;
  66. import org.orekit.utils.ParameterObserver;

  67. /**
  68.  * This class propagates {@link org.orekit.orbits.Orbit orbits} using the DSST theory.
  69.  * <p>
  70.  * Whereas analytical propagators are configured only thanks to their various
  71.  * constructors and can be used immediately after construction, such a semianalytical
  72.  * propagator configuration involves setting several parameters between construction
  73.  * time and propagation time, just as numerical propagators.
  74.  * </p>
  75.  * <p>
  76.  * The configuration parameters that can be set are:
  77.  * </p>
  78.  * <ul>
  79.  * <li>the initial spacecraft state ({@link #setInitialState(SpacecraftState)})</li>
  80.  * <li>the various force models ({@link #addForceModel(DSSTForceModel)},
  81.  * {@link #removeForceModels()})</li>
  82.  * <li>the discrete events that should be triggered during propagation (
  83.  * {@link #addEventDetector(org.orekit.propagation.events.EventDetector)},
  84.  * {@link #clearEventsDetectors()})</li>
  85.  * <li>the binding logic with the rest of the application ({@link #getMultiplexer()})</li>
  86.  * </ul>
  87.  * <p>
  88.  * From these configuration parameters, only the initial state is mandatory.
  89.  * The default propagation settings are in {@link OrbitType#EQUINOCTIAL equinoctial}
  90.  * parameters with {@link PositionAngle#TRUE true} longitude argument.
  91.  * The central attraction coefficient used to define the initial orbit will be used.
  92.  * However, specifying only the initial state would mean the propagator would use
  93.  * only Keplerian forces. In this case, the simpler
  94.  * {@link org.orekit.propagation.analytical.KeplerianPropagator KeplerianPropagator}
  95.  * class would be more effective.
  96.  * </p>
  97.  * <p>
  98.  * The underlying numerical integrator set up in the constructor may also have
  99.  * its own configuration parameters. Typical configuration parameters for adaptive
  100.  * stepsize integrators are the min, max and perhaps start step size as well as
  101.  * the absolute and/or relative errors thresholds.
  102.  * </p>
  103.  * <p>
  104.  * The state that is seen by the integrator is a simple six elements double array.
  105.  * These six elements are:
  106.  * <ul>
  107.  * <li>the {@link org.orekit.orbits.EquinoctialOrbit equinoctial orbit parameters}
  108.  * (a, e<sub>x</sub>, e<sub>y</sub>, h<sub>x</sub>, h<sub>y</sub>, λ<sub>m</sub>)
  109.  * in meters and radians,</li>
  110.  * </ul>
  111.  *
  112.  * <p>By default, at the end of the propagation, the propagator resets the initial state to the final state,
  113.  * thus allowing a new propagation to be started from there without recomputing the part already performed.
  114.  * This behaviour can be chenged by calling {@link #setResetAtEnd(boolean)}.
  115.  * </p>
  116.  * <p>Beware the same instance cannot be used simultaneously by different threads, the class is <em>not</em>
  117.  * thread-safe.</p>
  118.  *
  119.  * @see SpacecraftState
  120.  * @see DSSTForceModel
  121.  * @author Romain Di Costanzo
  122.  * @author Pascal Parraud
  123.  */
  124. public class DSSTPropagator extends AbstractIntegratedPropagator {

  125.     /** Retrograde factor I.
  126.      *  <p>
  127.      *  DSST model needs equinoctial orbit as internal representation.
  128.      *  Classical equinoctial elements have discontinuities when inclination
  129.      *  is close to zero. In this representation, I = +1. <br>
  130.      *  To avoid this discontinuity, another representation exists and equinoctial
  131.      *  elements can be expressed in a different way, called "retrograde" orbit.
  132.      *  This implies I = -1. <br>
  133.      *  As Orekit doesn't implement the retrograde orbit, I is always set to +1.
  134.      *  But for the sake of consistency with the theory, the retrograde factor
  135.      *  has been kept in the formulas.
  136.      *  </p>
  137.      */
  138.     private static final int I = 1;

  139.     /** Default value for epsilon. */
  140.     private static final double EPSILON_DEFAULT = 1.0e-13;

  141.     /** Default value for maxIterations. */
  142.     private static final int MAX_ITERATIONS_DEFAULT = 200;

  143.     /** Number of grid points per integration step to be used in interpolation of short periodics coefficients.*/
  144.     private static final int INTERPOLATION_POINTS_PER_STEP = 3;

  145.     /** Flag specifying whether the initial orbital state is given with osculating elements. */
  146.     private boolean initialIsOsculating;

  147.     /** Force models used to compute short periodic terms. */
  148.     private final transient List<DSSTForceModel> forceModels;

  149.     /** State mapper holding the force models. */
  150.     private MeanPlusShortPeriodicMapper mapper;

  151.     /** Generator for the interpolation grid. */
  152.     private InterpolationGrid interpolationgrid;

  153.     /** Create a new instance of DSSTPropagator.
  154.      *  <p>
  155.      *  After creation, there are no perturbing forces at all.
  156.      *  This means that if {@link #addForceModel addForceModel}
  157.      *  is not called after creation, the integrated orbit will
  158.      *  follow a Keplerian evolution only.
  159.      *  </p>
  160.      *
  161.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
  162.      *
  163.      *  @param integrator numerical integrator to use for propagation.
  164.      *  @param propagationType type of orbit to output (mean or osculating).
  165.      * @see #DSSTPropagator(ODEIntegrator, PropagationType, AttitudeProvider)
  166.      */
  167.     @DefaultDataContext
  168.     public DSSTPropagator(final ODEIntegrator integrator, final PropagationType propagationType) {
  169.         this(integrator, propagationType,
  170.                 Propagator.getDefaultLaw(DataContext.getDefault().getFrames()));
  171.     }

  172.     /** Create a new instance of DSSTPropagator.
  173.      *  <p>
  174.      *  After creation, there are no perturbing forces at all.
  175.      *  This means that if {@link #addForceModel addForceModel}
  176.      *  is not called after creation, the integrated orbit will
  177.      *  follow a Keplerian evolution only.
  178.      *  </p>
  179.      * @param integrator numerical integrator to use for propagation.
  180.      * @param propagationType type of orbit to output (mean or osculating).
  181.      * @param attitudeProvider the attitude law.
  182.      * @since 10.1
  183.      */
  184.     public DSSTPropagator(final ODEIntegrator integrator,
  185.                           final PropagationType propagationType,
  186.                           final AttitudeProvider attitudeProvider) {
  187.         super(integrator, propagationType);
  188.         forceModels = new ArrayList<DSSTForceModel>();
  189.         initMapper();
  190.         // DSST uses only equinoctial orbits and mean longitude argument
  191.         setOrbitType(OrbitType.EQUINOCTIAL);
  192.         setPositionAngleType(PositionAngle.MEAN);
  193.         setAttitudeProvider(attitudeProvider);
  194.         setInterpolationGridToFixedNumberOfPoints(INTERPOLATION_POINTS_PER_STEP);
  195.     }


  196.     /** Create a new instance of DSSTPropagator.
  197.      *  <p>
  198.      *  After creation, there are no perturbing forces at all.
  199.      *  This means that if {@link #addForceModel addForceModel}
  200.      *  is not called after creation, the integrated orbit will
  201.      *  follow a Keplerian evolution only. Only the mean orbits
  202.      *  will be generated.
  203.      *  </p>
  204.      *
  205.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
  206.      *
  207.      *  @param integrator numerical integrator to use for propagation.
  208.      * @see #DSSTPropagator(ODEIntegrator, PropagationType, AttitudeProvider)
  209.      */
  210.     @DefaultDataContext
  211.     public DSSTPropagator(final ODEIntegrator integrator) {
  212.         this(integrator, PropagationType.MEAN);
  213.     }

  214.     /** Set the central attraction coefficient μ.
  215.      * <p>
  216.      * Setting the central attraction coefficient is
  217.      * equivalent to {@link #addForceModel(DSSTForceModel) add}
  218.      * a {@link DSSTNewtonianAttraction} force model.
  219.      * </p>
  220.     * @param mu central attraction coefficient (m³/s²)
  221.     * @see #addForceModel(DSSTForceModel)
  222.     * @see #getAllForceModels()
  223.     */
  224.     public void setMu(final double mu) {
  225.         addForceModel(new DSSTNewtonianAttraction(mu));
  226.     }

  227.     /** Set the central attraction coefficient μ only in upper class.
  228.      * @param mu central attraction coefficient (m³/s²)
  229.      */
  230.     private void superSetMu(final double mu) {
  231.         super.setMu(mu);
  232.     }

  233.     /** Check if Newtonian attraction force model is available.
  234.      * <p>
  235.      * Newtonian attraction is always the last force model in the list.
  236.      * </p>
  237.      * @return true if Newtonian attraction force model is available
  238.      */
  239.     private boolean hasNewtonianAttraction() {
  240.         final int last = forceModels.size() - 1;
  241.         return last >= 0 && forceModels.get(last) instanceof DSSTNewtonianAttraction;
  242.     }

  243.     /** Set the initial state with osculating orbital elements.
  244.      *  @param initialState initial state (defined with osculating elements)
  245.      */
  246.     public void setInitialState(final SpacecraftState initialState) {
  247.         setInitialState(initialState, PropagationType.OSCULATING);
  248.     }

  249.     /** Set the initial state.
  250.      *  @param initialState initial state
  251.      *  @param stateType defined if the orbital state is defined with osculating or mean elements
  252.      */
  253.     public void setInitialState(final SpacecraftState initialState,
  254.                                 final PropagationType stateType) {
  255.         switch (stateType) {
  256.             case MEAN:
  257.                 initialIsOsculating = false;
  258.                 break;
  259.             case OSCULATING:
  260.                 initialIsOsculating = true;
  261.                 break;
  262.             default:
  263.                 throw new OrekitInternalError(null);
  264.         }
  265.         resetInitialState(initialState);
  266.     }

  267.     /** Reset the initial state.
  268.      *
  269.      *  @param state new initial state
  270.      */
  271.     @Override
  272.     public void resetInitialState(final SpacecraftState state) {
  273.         super.resetInitialState(state);
  274.         if (!hasNewtonianAttraction()) {
  275.             // use the state to define central attraction
  276.             setMu(state.getMu());
  277.         }
  278.         super.setStartDate(state.getDate());
  279.     }

  280.     /** Set the selected short periodic coefficients that must be stored as additional states.
  281.      * @param selectedCoefficients short periodic coefficients that must be stored as additional states
  282.      * (null means no coefficients are selected, empty set means all coefficients are selected)
  283.      */
  284.     public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
  285.         mapper.setSelectedCoefficients(selectedCoefficients == null ?
  286.                                        null : new HashSet<String>(selectedCoefficients));
  287.     }

  288.     /** Get the selected short periodic coefficients that must be stored as additional states.
  289.      * @return short periodic coefficients that must be stored as additional states
  290.      * (null means no coefficients are selected, empty set means all coefficients are selected)
  291.      */
  292.     public Set<String> getSelectedCoefficients() {
  293.         final Set<String> set = mapper.getSelectedCoefficients();
  294.         return set == null ? null : Collections.unmodifiableSet(set);
  295.     }

  296.     /** Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
  297.      * @return names of the parameters (i.e. columns) of the Jacobian matrix
  298.      */
  299.     protected List<String> getJacobiansColumnsNames() {
  300.         final List<String> columnsNames = new ArrayList<>();
  301.         for (final DSSTForceModel forceModel : getAllForceModels()) {
  302.             for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
  303.                 if (driver.isSelected() && !columnsNames.contains(driver.getName())) {
  304.                     columnsNames.add(driver.getName());
  305.                 }
  306.             }
  307.         }
  308.         Collections.sort(columnsNames);
  309.         return columnsNames;
  310.     }

  311.     /** {@inheritDoc} */
  312.     @Override
  313.     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
  314.                                                         final DoubleArrayDictionary initialJacobianColumns) {
  315.         return new DSSTHarvester(this, stmName, initialStm, initialJacobianColumns);
  316.     }

  317.     /** {@inheritDoc} */
  318.     @Override
  319.     protected void setUpStmAndJacobianGenerators() {

  320.         final AbstractMatricesHarvester harvester = getHarvester();
  321.         if (harvester != null) {

  322.             // set up the additional equations and additional state providers
  323.             final DSSTStateTransitionMatrixGenerator stmGenerator = setUpStmGenerator();
  324.             setUpRegularParametersJacobiansColumns(stmGenerator);

  325.             // as we are now starting the propagation, everything is configured
  326.             // we can freeze the names in the harvester
  327.             harvester.freezeColumnsNames();

  328.         }

  329.     }

  330.     /** Set up the State Transition Matrix Generator.
  331.      * @return State Transition Matrix Generator
  332.      * @since 11.1
  333.      */
  334.     private DSSTStateTransitionMatrixGenerator setUpStmGenerator() {

  335.         final AbstractMatricesHarvester harvester = getHarvester();

  336.         // add the STM generator corresponding to the current settings, and setup state accordingly
  337.         DSSTStateTransitionMatrixGenerator stmGenerator = null;
  338.         for (final AdditionalDerivativesProvider equations : getAdditionalDerivativesProviders()) {
  339.             if (equations instanceof DSSTStateTransitionMatrixGenerator &&
  340.                 equations.getName().equals(harvester.getStmName())) {
  341.                 // the STM generator has already been set up in a previous propagation
  342.                 stmGenerator = (DSSTStateTransitionMatrixGenerator) equations;
  343.                 break;
  344.             }
  345.         }
  346.         if (stmGenerator == null) {
  347.             // this is the first time we need the STM generate, create it
  348.             stmGenerator = new DSSTStateTransitionMatrixGenerator(harvester.getStmName(),
  349.                                                                   getAllForceModels(),
  350.                                                                   getAttitudeProvider());
  351.             addAdditionalDerivativesProvider(stmGenerator);
  352.         }

  353.         if (!getInitialIntegrationState().hasAdditionalState(harvester.getStmName())) {
  354.             // add the initial State Transition Matrix if it is not already there
  355.             // (perhaps due to a previous propagation)
  356.             setInitialState(stmGenerator.setInitialStateTransitionMatrix(getInitialState(),
  357.                                                                          harvester.getInitialStateTransitionMatrix()),
  358.                             getPropagationType());
  359.         }

  360.         return stmGenerator;

  361.     }

  362.     /** Set up the Jacobians columns generator for regular parameters.
  363.      * @param stmGenerator generator for the State Transition Matrix
  364.      * @since 11.1
  365.      */
  366.     private void setUpRegularParametersJacobiansColumns(final DSSTStateTransitionMatrixGenerator stmGenerator) {

  367.         // first pass: gather all parameters (excluding trigger dates), binding similar names together
  368.         final ParameterDriversList selected = new ParameterDriversList();
  369.         for (final DSSTForceModel forceModel : getAllForceModels()) {
  370.             for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
  371.                 selected.add(driver);
  372.             }
  373.         }

  374.         // second pass: now that shared parameter names are bound together,
  375.         // their selections status have been synchronized, we can filter them
  376.         selected.filter(true);

  377.         // third pass: sort parameters lexicographically
  378.         selected.sort();

  379.         // add the Jacobians column generators corresponding to parameters, and setup state accordingly
  380.         for (final DelegatingDriver driver : selected.getDrivers()) {

  381.             DSSTIntegrableJacobianColumnGenerator generator = null;

  382.             // check if we already have set up the providers
  383.             for (final AdditionalDerivativesProvider provider : getAdditionalDerivativesProviders()) {
  384.                 if (provider instanceof DSSTIntegrableJacobianColumnGenerator &&
  385.                     provider.getName().equals(driver.getName())) {
  386.                     // the Jacobian column generator has already been set up in a previous propagation
  387.                     generator = (DSSTIntegrableJacobianColumnGenerator) provider;
  388.                     break;
  389.                 }
  390.             }

  391.             if (generator == null) {
  392.                 // this is the first time we need the Jacobian column generator, create it
  393.                 generator = new DSSTIntegrableJacobianColumnGenerator(stmGenerator, driver.getName());
  394.                 addAdditionalDerivativesProvider(generator);
  395.             }

  396.             if (!getInitialIntegrationState().hasAdditionalState(driver.getName())) {
  397.                 // add the initial Jacobian column if it is not already there
  398.                 // (perhaps due to a previous propagation)
  399.                 setInitialState(getInitialState().addAdditionalState(driver.getName(),
  400.                                                                      getHarvester().getInitialJacobianColumn(driver.getName())),
  401.                                 getPropagationType());
  402.             }

  403.         }

  404.     }

  405.     /** Check if the initial state is provided in osculating elements.
  406.      * @return true if initial state is provided in osculating elements
  407.      */
  408.     public boolean initialIsOsculating() {
  409.         return initialIsOsculating;
  410.     }

  411.     /** Set the interpolation grid generator.
  412.      * <p>
  413.      * The generator will create an interpolation grid with a fixed
  414.      * number of points for each mean element integration step.
  415.      * </p>
  416.      * <p>
  417.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  418.      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
  419.      * by default the propagator is set as to 3 interpolations points per step.
  420.      * </p>
  421.      * @param interpolationPoints number of interpolation points at
  422.      * each integration step
  423.      * @see #setInterpolationGridToMaxTimeGap(double)
  424.      * @since 7.1
  425.      */
  426.     public void setInterpolationGridToFixedNumberOfPoints(final int interpolationPoints) {
  427.         interpolationgrid = new FixedNumberInterpolationGrid(interpolationPoints);
  428.     }

  429.     /** Set the interpolation grid generator.
  430.      * <p>
  431.      * The generator will create an interpolation grid with a maximum
  432.      * time gap between interpolation points.
  433.      * </p>
  434.      * <p>
  435.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  436.      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
  437.      * by default the propagator is set as to 3 interpolations points per step.
  438.      * </p>
  439.      * @param maxGap maximum time gap between interpolation points (seconds)
  440.      * @see #setInterpolationGridToFixedNumberOfPoints(int)
  441.      * @since 7.1
  442.      */
  443.     public void setInterpolationGridToMaxTimeGap(final double maxGap) {
  444.         interpolationgrid = new MaxGapInterpolationGrid(maxGap);
  445.     }

  446.     /** Add a force model to the global perturbation model.
  447.      *  <p>
  448.      *  If this method is not called at all,
  449.      *  the integrated orbit will follow a Keplerian evolution only.
  450.      *  </p>
  451.      *  @param force perturbing {@link DSSTForceModel force} to add
  452.      *  @see #removeForceModels()
  453.      *  @see #setMu(double)
  454.      */
  455.     public void addForceModel(final DSSTForceModel force) {

  456.         if (force instanceof DSSTNewtonianAttraction) {
  457.             // we want to add the central attraction force model

  458.             // ensure we are notified of any mu change
  459.             force.getParametersDrivers().get(0).addObserver(new ParameterObserver() {
  460.                 /** {@inheritDoc} */
  461.                 @Override
  462.                 public void valueChanged(final double previousValue, final ParameterDriver driver) {
  463.                     superSetMu(driver.getValue());
  464.                 }
  465.             });

  466.             if (hasNewtonianAttraction()) {
  467.                 // there is already a central attraction model, replace it
  468.                 forceModels.set(forceModels.size() - 1, force);
  469.             } else {
  470.                 // there are no central attraction model yet, add it at the end of the list
  471.                 forceModels.add(force);
  472.             }
  473.         } else {
  474.             // we want to add a perturbing force model
  475.             if (hasNewtonianAttraction()) {
  476.                 // insert the new force model before Newtonian attraction,
  477.                 // which should always be the last one in the list
  478.                 forceModels.add(forceModels.size() - 1, force);
  479.             } else {
  480.                 // we only have perturbing force models up to now, just append at the end of the list
  481.                 forceModels.add(force);
  482.             }
  483.         }

  484.         force.registerAttitudeProvider(getAttitudeProvider());

  485.     }

  486.     /** Remove all perturbing force models from the global perturbation model
  487.      *  (except central attraction).
  488.      *  <p>
  489.      *  Once all perturbing forces have been removed (and as long as no new force model is added),
  490.      *  the integrated orbit will follow a Keplerian evolution only.
  491.      *  </p>
  492.      *  @see #addForceModel(DSSTForceModel)
  493.      */
  494.     public void removeForceModels() {
  495.         final int last = forceModels.size() - 1;
  496.         if (hasNewtonianAttraction()) {
  497.             // preserve the Newtonian attraction model at the end
  498.             final DSSTForceModel newton = forceModels.get(last);
  499.             forceModels.clear();
  500.             forceModels.add(newton);
  501.         } else {
  502.             forceModels.clear();
  503.         }
  504.     }

  505.     /** Get all the force models, perturbing forces and Newtonian attraction included.
  506.      * @return list of perturbing force models, with Newtonian attraction being the
  507.      * last one
  508.      * @see #addForceModel(DSSTForceModel)
  509.      * @see #setMu(double)
  510.      */
  511.     public List<DSSTForceModel> getAllForceModels() {
  512.         return Collections.unmodifiableList(forceModels);
  513.     }

  514.     /** Get propagation parameter type.
  515.      * @return orbit type used for propagation
  516.      */
  517.     public OrbitType getOrbitType() {
  518.         return super.getOrbitType();
  519.     }

  520.     /** Get propagation parameter type.
  521.      * @return angle type to use for propagation
  522.      */
  523.     public PositionAngle getPositionAngleType() {
  524.         return super.getPositionAngleType();
  525.     }

  526.     /** Conversion from mean to osculating orbit.
  527.      * <p>
  528.      * Compute osculating state <b>in a DSST sense</b>, corresponding to the
  529.      * mean SpacecraftState in input, and according to the Force models taken
  530.      * into account.
  531.      * </p><p>
  532.      * Since the osculating state is obtained by adding short-periodic variation
  533.      * of each force model, the resulting output will depend on the
  534.      * force models parameterized in input.
  535.      * </p>
  536.      * @param mean Mean state to convert
  537.      * @param forces Forces to take into account
  538.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  539.      * like atmospheric drag, radiation pressure or specific user-defined models)
  540.      * @return osculating state in a DSST sense
  541.      */
  542.     public static SpacecraftState computeOsculatingState(final SpacecraftState mean,
  543.                                                          final AttitudeProvider attitudeProvider,
  544.                                                          final Collection<DSSTForceModel> forces) {

  545.         //Create the auxiliary object
  546.         final AuxiliaryElements aux = new AuxiliaryElements(mean.getOrbit(), I);

  547.         // Set the force models
  548.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
  549.         for (final DSSTForceModel force : forces) {
  550.             force.registerAttitudeProvider(attitudeProvider);
  551.             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters()));
  552.             force.updateShortPeriodTerms(force.getParameters(), mean);
  553.         }

  554.         final EquinoctialOrbit osculatingOrbit = computeOsculatingOrbit(mean, shortPeriodTerms);

  555.         return new SpacecraftState(osculatingOrbit, mean.getAttitude(), mean.getMass(),
  556.                                    mean.getAdditionalStatesValues(), mean.getAdditionalStatesDerivatives());

  557.     }

  558.     /** Conversion from osculating to mean orbit.
  559.      * <p>
  560.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  561.      * osculating SpacecraftState in input, and according to the Force models
  562.      * taken into account.
  563.      * </p><p>
  564.      * Since the osculating state is obtained with the computation of
  565.      * short-periodic variation of each force model, the resulting output will
  566.      * depend on the force models parameterized in input.
  567.      * </p><p>
  568.      * The computation is done through a fixed-point iteration process.
  569.      * </p>
  570.      * @param osculating Osculating state to convert
  571.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  572.      * like atmospheric drag, radiation pressure or specific user-defined models)
  573.      * @param forceModels Forces to take into account
  574.      * @return mean state in a DSST sense
  575.      */
  576.     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
  577.                                                    final AttitudeProvider attitudeProvider,
  578.                                                    final Collection<DSSTForceModel> forceModels) {
  579.         return computeMeanState(osculating, attitudeProvider, forceModels, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  580.     }

  581.     /** Conversion from osculating to mean orbit.
  582.      * <p>
  583.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  584.      * osculating SpacecraftState in input, and according to the Force models
  585.      * taken into account.
  586.      * </p><p>
  587.      * Since the osculating state is obtained with the computation of
  588.      * short-periodic variation of each force model, the resulting output will
  589.      * depend on the force models parameterized in input.
  590.      * </p><p>
  591.      * The computation is done through a fixed-point iteration process.
  592.      * </p>
  593.      * @param osculating Osculating state to convert
  594.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  595.      * like atmospheric drag, radiation pressure or specific user-defined models)
  596.      * @param forceModels Forces to take into account
  597.      * @param epsilon convergence threshold for mean parameters conversion
  598.      * @param maxIterations maximum iterations for mean parameters conversion
  599.      * @return mean state in a DSST sense
  600.      * @since 10.1
  601.      */
  602.     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
  603.                                                    final AttitudeProvider attitudeProvider,
  604.                                                    final Collection<DSSTForceModel> forceModels,
  605.                                                    final double epsilon,
  606.                                                    final int maxIterations) {
  607.         final Orbit meanOrbit = computeMeanOrbit(osculating, attitudeProvider, forceModels, epsilon, maxIterations);
  608.         return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(),
  609.                                    osculating.getAdditionalStatesValues(), osculating.getAdditionalStatesDerivatives());
  610.     }

  611.      /** Override the default value of the parameter.
  612.      *  <p>
  613.      *  By default, if the initial orbit is defined as osculating,
  614.      *  it will be averaged over 2 satellite revolutions.
  615.      *  This can be changed by using this method.
  616.      *  </p>
  617.      *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
  618.      *                             elements
  619.      */
  620.     public void setSatelliteRevolution(final int satelliteRevolution) {
  621.         mapper.setSatelliteRevolution(satelliteRevolution);
  622.     }

  623.     /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  624.      *  @return number of satellite revolutions to use for converting osculating to mean elements
  625.      */
  626.     public int getSatelliteRevolution() {
  627.         return mapper.getSatelliteRevolution();
  628.     }

  629.     /** Override the default value short periodic terms.
  630.     *  <p>
  631.     *  By default, short periodic terms are initialized before
  632.     *  the numerical integration of the mean orbital elements.
  633.     *  </p>
  634.     *  @param shortPeriodTerms short periodic terms
  635.     */
  636.     public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
  637.         mapper.setShortPeriodTerms(shortPeriodTerms);
  638.     }

  639.    /** Get the short periodic terms.
  640.     *  @return the short periodic terms
  641.     */
  642.     public List<ShortPeriodTerms> getShortPeriodTerms() {
  643.         return mapper.getShortPeriodTerms();
  644.     }

  645.     /** {@inheritDoc} */
  646.     @Override
  647.     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
  648.         super.setAttitudeProvider(attitudeProvider);

  649.         //Register the attitude provider for each force model
  650.         for (final DSSTForceModel force : forceModels) {
  651.             force.registerAttitudeProvider(attitudeProvider);
  652.         }
  653.     }

  654.     /** Method called just before integration.
  655.      * <p>
  656.      * The default implementation does nothing, it may be specialized in subclasses.
  657.      * </p>
  658.      * @param initialState initial state
  659.      * @param tEnd target date at which state should be propagated
  660.      */
  661.     @Override
  662.     protected void beforeIntegration(final SpacecraftState initialState,
  663.                                      final AbsoluteDate tEnd) {

  664.         // check if only mean elements must be used
  665.         final PropagationType type = getPropagationType();

  666.         // compute common auxiliary elements
  667.         final AuxiliaryElements aux = new AuxiliaryElements(initialState.getOrbit(), I);

  668.         // initialize all perturbing forces
  669.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
  670.         for (final DSSTForceModel force : forceModels) {
  671.             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, type, force.getParameters()));
  672.         }
  673.         mapper.setShortPeriodTerms(shortPeriodTerms);

  674.         // if required, insert the special short periodics step handler
  675.         if (type == PropagationType.OSCULATING) {
  676.             final ShortPeriodicsHandler spHandler = new ShortPeriodicsHandler(forceModels);
  677.             // Compute short periodic coefficients for this point
  678.             for (DSSTForceModel forceModel : forceModels) {
  679.                 forceModel.updateShortPeriodTerms(forceModel.getParameters(), initialState);
  680.             }
  681.             final Collection<ODEStepHandler> stepHandlers = new ArrayList<ODEStepHandler>();
  682.             stepHandlers.add(spHandler);
  683.             final ODEIntegrator integrator = getIntegrator();
  684.             final Collection<ODEStepHandler> existing = integrator.getStepHandlers();
  685.             stepHandlers.addAll(existing);

  686.             integrator.clearStepHandlers();

  687.             // add back the existing handlers after the short periodics one
  688.             for (final ODEStepHandler sp : stepHandlers) {
  689.                 integrator.addStepHandler(sp);
  690.             }
  691.         }
  692.     }

  693.     /** {@inheritDoc} */
  694.     @Override
  695.     protected void afterIntegration() {
  696.         // remove the special short periodics step handler if added before
  697.         if (getPropagationType() == PropagationType.OSCULATING) {
  698.             final List<ODEStepHandler> preserved = new ArrayList<ODEStepHandler>();
  699.             final ODEIntegrator integrator = getIntegrator();
  700.             for (final ODEStepHandler sp : integrator.getStepHandlers()) {
  701.                 if (!(sp instanceof ShortPeriodicsHandler)) {
  702.                     preserved.add(sp);
  703.                 }
  704.             }

  705.             // clear the list
  706.             integrator.clearStepHandlers();

  707.             // add back the step handlers that were important for the user
  708.             for (final ODEStepHandler sp : preserved) {
  709.                 integrator.addStepHandler(sp);
  710.             }
  711.         }
  712.     }

  713.     /** Compute mean state from osculating state.
  714.      * <p>
  715.      * Compute in a DSST sense the mean state corresponding to the input osculating state.
  716.      * </p><p>
  717.      * The computing is done through a fixed-point iteration process.
  718.      * </p>
  719.      * @param osculating initial osculating state
  720.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  721.      * like atmospheric drag, radiation pressure or specific user-defined models)
  722.      * @param forceModels force models
  723.      * @param epsilon convergence threshold for mean parameters conversion
  724.      * @param maxIterations maximum iterations for mean parameters conversion
  725.      * @return mean state
  726.      */
  727.     private static Orbit computeMeanOrbit(final SpacecraftState osculating,
  728.                                           final AttitudeProvider attitudeProvider,
  729.                                           final Collection<DSSTForceModel> forceModels, final double epsilon, final int maxIterations) {

  730.         // rough initialization of the mean parameters
  731.         EquinoctialOrbit meanOrbit = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(osculating.getOrbit());

  732.         // threshold for each parameter
  733.         final double thresholdA = epsilon * (1 + FastMath.abs(meanOrbit.getA()));
  734.         final double thresholdE = epsilon * (1 + meanOrbit.getE());
  735.         final double thresholdI = epsilon * (1 + meanOrbit.getI());
  736.         final double thresholdL = epsilon * FastMath.PI;

  737.         // ensure all Gaussian force models can rely on attitude
  738.         for (final DSSTForceModel force : forceModels) {
  739.             force.registerAttitudeProvider(attitudeProvider);
  740.         }

  741.         int i = 0;
  742.         while (i++ < maxIterations) {

  743.             final SpacecraftState meanState = new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass());

  744.             //Create the auxiliary object
  745.             final AuxiliaryElements aux = new AuxiliaryElements(meanOrbit, I);

  746.             // Set the force models
  747.             final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
  748.             for (final DSSTForceModel force : forceModels) {
  749.                 shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters()));
  750.                 force.updateShortPeriodTerms(force.getParameters(), meanState);
  751.             }

  752.             // recompute the osculating parameters from the current mean parameters
  753.             final EquinoctialOrbit rebuilt = computeOsculatingOrbit(meanState, shortPeriodTerms);

  754.             // adapted parameters residuals
  755.             final double deltaA  = osculating.getA() - rebuilt.getA();
  756.             final double deltaEx = osculating.getEquinoctialEx() - rebuilt.getEquinoctialEx();
  757.             final double deltaEy = osculating.getEquinoctialEy() - rebuilt.getEquinoctialEy();
  758.             final double deltaHx = osculating.getHx() - rebuilt.getHx();
  759.             final double deltaHy = osculating.getHy() - rebuilt.getHy();
  760.             final double deltaLv = MathUtils.normalizeAngle(osculating.getLv() - rebuilt.getLv(), 0.0);

  761.             // check convergence
  762.             if (FastMath.abs(deltaA)  < thresholdA &&
  763.                 FastMath.abs(deltaEx) < thresholdE &&
  764.                 FastMath.abs(deltaEy) < thresholdE &&
  765.                 FastMath.abs(deltaHx) < thresholdI &&
  766.                 FastMath.abs(deltaHy) < thresholdI &&
  767.                 FastMath.abs(deltaLv) < thresholdL) {
  768.                 return meanOrbit;
  769.             }

  770.             // update mean parameters
  771.             meanOrbit = new EquinoctialOrbit(meanOrbit.getA() + deltaA,
  772.                                              meanOrbit.getEquinoctialEx() + deltaEx,
  773.                                              meanOrbit.getEquinoctialEy() + deltaEy,
  774.                                              meanOrbit.getHx() + deltaHx,
  775.                                              meanOrbit.getHy() + deltaHy,
  776.                                              meanOrbit.getLv() + deltaLv,
  777.                                              PositionAngle.TRUE, meanOrbit.getFrame(),
  778.                                              meanOrbit.getDate(), meanOrbit.getMu());
  779.         }

  780.         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_DSST_MEAN_PARAMETERS, i);

  781.     }

  782.     /** Compute osculating state from mean state.
  783.      * <p>
  784.      * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
  785.      * </p>
  786.      * @param meanState initial mean state
  787.      * @param shortPeriodTerms short period terms
  788.      * @return osculating state
  789.      */
  790.     private static EquinoctialOrbit computeOsculatingOrbit(final SpacecraftState meanState,
  791.                                                            final List<ShortPeriodTerms> shortPeriodTerms) {

  792.         final double[] mean = new double[6];
  793.         final double[] meanDot = new double[6];
  794.         OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngle.MEAN, mean, meanDot);
  795.         final double[] y = mean.clone();
  796.         for (final ShortPeriodTerms spt : shortPeriodTerms) {
  797.             final double[] shortPeriodic = spt.value(meanState.getOrbit());
  798.             for (int i = 0; i < shortPeriodic.length; i++) {
  799.                 y[i] += shortPeriodic[i];
  800.             }
  801.         }
  802.         return (EquinoctialOrbit) OrbitType.EQUINOCTIAL.mapArrayToOrbit(y, meanDot,
  803.                                                                         PositionAngle.MEAN, meanState.getDate(),
  804.                                                                         meanState.getMu(), meanState.getFrame());
  805.     }

  806.     /** {@inheritDoc} */
  807.     @Override
  808.     protected SpacecraftState getInitialIntegrationState() {
  809.         if (initialIsOsculating) {
  810.             // the initial state is an osculating state,
  811.             // it must be converted to mean state
  812.             return computeMeanState(getInitialState(), getAttitudeProvider(), forceModels);
  813.         } else {
  814.             // the initial state is already a mean state
  815.             return getInitialState();
  816.         }
  817.     }

  818.     /** {@inheritDoc}
  819.      * <p>
  820.      * Note that for DSST, orbit type is hardcoded to {@link OrbitType#EQUINOCTIAL}
  821.      * and position angle type is hardcoded to {@link PositionAngle#MEAN}, so
  822.      * the corresponding parameters are ignored.
  823.      * </p>
  824.      */
  825.     @Override
  826.     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
  827.                                        final OrbitType ignoredOrbitType, final PositionAngle ignoredPositionAngleType,
  828.                                        final AttitudeProvider attitudeProvider, final Frame frame) {

  829.         // create a mapper with the common settings provided as arguments
  830.         final MeanPlusShortPeriodicMapper newMapper =
  831.                 new MeanPlusShortPeriodicMapper(referenceDate, mu, attitudeProvider, frame);

  832.         // copy the specific settings from the existing mapper
  833.         if (mapper != null) {
  834.             newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
  835.             newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
  836.             newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
  837.         }

  838.         mapper = newMapper;
  839.         return mapper;

  840.     }


  841.     /** Get the short period terms value.
  842.      * @param meanState the mean state
  843.      * @return shortPeriodTerms short period terms
  844.      * @since 7.1
  845.      */
  846.     public double[] getShortPeriodTermsValue(final SpacecraftState meanState) {
  847.         final double[] sptValue = new double[6];

  848.         for (ShortPeriodTerms spt : mapper.getShortPeriodTerms()) {
  849.             final double[] shortPeriodic = spt.value(meanState.getOrbit());
  850.             for (int i = 0; i < shortPeriodic.length; i++) {
  851.                 sptValue[i] += shortPeriodic[i];
  852.             }
  853.         }
  854.         return sptValue;
  855.     }


  856.     /** Internal mapper using mean parameters plus short periodic terms. */
  857.     private static class MeanPlusShortPeriodicMapper extends StateMapper {

  858.         /** Short periodic coefficients that must be stored as additional states. */
  859.         private Set<String>                selectedCoefficients;

  860.         /** Number of satellite revolutions in the averaging interval. */
  861.         private int                        satelliteRevolution;

  862.         /** Short period terms. */
  863.         private List<ShortPeriodTerms>     shortPeriodTerms;

  864.         /** Simple constructor.
  865.          * @param referenceDate reference date
  866.          * @param mu central attraction coefficient (m³/s²)
  867.          * @param attitudeProvider attitude provider
  868.          * @param frame inertial frame
  869.          */
  870.         MeanPlusShortPeriodicMapper(final AbsoluteDate referenceDate, final double mu,
  871.                                     final AttitudeProvider attitudeProvider, final Frame frame) {

  872.             super(referenceDate, mu, OrbitType.EQUINOCTIAL, PositionAngle.MEAN, attitudeProvider, frame);

  873.             this.selectedCoefficients = null;

  874.             // Default averaging period for conversion from osculating to mean elements
  875.             this.satelliteRevolution = 2;

  876.             this.shortPeriodTerms    = Collections.emptyList();

  877.         }

  878.         /** {@inheritDoc} */
  879.         @Override
  880.         public SpacecraftState mapArrayToState(final AbsoluteDate date,
  881.                                                final double[] y, final double[] yDot,
  882.                                                final PropagationType type) {

  883.             // add short periodic variations to mean elements to get osculating elements
  884.             // (the loop may not be performed if there are no force models and in the
  885.             //  case we want to remain in mean parameters only)
  886.             final double[] elements = y.clone();
  887.             final DoubleArrayDictionary coefficients;
  888.             if (type == PropagationType.MEAN) {
  889.                 coefficients = null;
  890.             } else {
  891.                 final Orbit meanOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngle.MEAN, date, getMu(), getFrame());
  892.                 coefficients = selectedCoefficients == null ? null : new DoubleArrayDictionary();
  893.                 for (final ShortPeriodTerms spt : shortPeriodTerms) {
  894.                     final double[] shortPeriodic = spt.value(meanOrbit);
  895.                     for (int i = 0; i < shortPeriodic.length; i++) {
  896.                         elements[i] += shortPeriodic[i];
  897.                     }
  898.                     if (selectedCoefficients != null) {
  899.                         coefficients.putAll(spt.getCoefficients(date, selectedCoefficients));
  900.                     }
  901.                 }
  902.             }

  903.             final double mass = elements[6];
  904.             if (mass <= 0.0) {
  905.                 throw new OrekitException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, mass);
  906.             }

  907.             final Orbit orbit       = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngle.MEAN, date, getMu(), getFrame());
  908.             final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());

  909.             if (coefficients == null) {
  910.                 return new SpacecraftState(orbit, attitude, mass);
  911.             } else {
  912.                 return new SpacecraftState(orbit, attitude, mass, coefficients);
  913.             }

  914.         }

  915.         /** {@inheritDoc} */
  916.         @Override
  917.         public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot) {

  918.             OrbitType.EQUINOCTIAL.mapOrbitToArray(state.getOrbit(), PositionAngle.MEAN, y, yDot);
  919.             y[6] = state.getMass();

  920.         }

  921.         /** Set the number of satellite revolutions to use for converting osculating to mean elements.
  922.          *  <p>
  923.          *  By default, if the initial orbit is defined as osculating,
  924.          *  it will be averaged over 2 satellite revolutions.
  925.          *  This can be changed by using this method.
  926.          *  </p>
  927.          *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
  928.          *                             elements
  929.          */
  930.         public void setSatelliteRevolution(final int satelliteRevolution) {
  931.             this.satelliteRevolution = satelliteRevolution;
  932.         }

  933.         /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  934.          *  @return number of satellite revolutions to use for converting osculating to mean elements
  935.          */
  936.         public int getSatelliteRevolution() {
  937.             return satelliteRevolution;
  938.         }

  939.         /** Set the selected short periodic coefficients that must be stored as additional states.
  940.          * @param selectedCoefficients short periodic coefficients that must be stored as additional states
  941.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  942.          */
  943.         public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
  944.             this.selectedCoefficients = selectedCoefficients;
  945.         }

  946.         /** Get the selected short periodic coefficients that must be stored as additional states.
  947.          * @return short periodic coefficients that must be stored as additional states
  948.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  949.          */
  950.         public Set<String> getSelectedCoefficients() {
  951.             return selectedCoefficients;
  952.         }

  953.         /** Set the short period terms.
  954.          * @param shortPeriodTerms short period terms
  955.          * @since 7.1
  956.          */
  957.         public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
  958.             this.shortPeriodTerms = shortPeriodTerms;
  959.         }

  960.         /** Get the short period terms.
  961.          * @return shortPeriodTerms short period terms
  962.          * @since 7.1
  963.          */
  964.         public List<ShortPeriodTerms> getShortPeriodTerms() {
  965.             return shortPeriodTerms;
  966.         }

  967.     }

  968.     /** {@inheritDoc} */
  969.     @Override
  970.     protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
  971.         return new Main(integrator);
  972.     }

  973.     /** Internal class for mean parameters integration. */
  974.     private class Main implements MainStateEquations {

  975.         /** Derivatives array. */
  976.         private final double[] yDot;

  977.         /** Simple constructor.
  978.          * @param integrator numerical integrator to use for propagation.
  979.          */
  980.         Main(final ODEIntegrator integrator) {
  981.             yDot = new double[7];

  982.             for (final DSSTForceModel forceModel : forceModels) {
  983.                 final EventDetector[] modelDetectors = forceModel.getEventsDetectors();
  984.                 if (modelDetectors != null) {
  985.                     for (final EventDetector detector : modelDetectors) {
  986.                         setUpEventDetector(integrator, detector);
  987.                     }
  988.                 }
  989.             }

  990.         }

  991.         /** {@inheritDoc} */
  992.         @Override
  993.         public void init(final SpacecraftState initialState, final AbsoluteDate target) {
  994.             forceModels.forEach(fm -> fm.init(initialState, target));
  995.         }

  996.         /** {@inheritDoc} */
  997.         @Override
  998.         public double[] computeDerivatives(final SpacecraftState state) {

  999.             Arrays.fill(yDot, 0.0);

  1000.             // compute common auxiliary elements
  1001.             final AuxiliaryElements auxiliaryElements = new AuxiliaryElements(state.getOrbit(), I);

  1002.             // compute the contributions of all perturbing forces
  1003.             for (final DSSTForceModel forceModel : forceModels) {
  1004.                 final double[] daidt = elementRates(forceModel, state, auxiliaryElements, forceModel.getParameters());
  1005.                 for (int i = 0; i < daidt.length; i++) {
  1006.                     yDot[i] += daidt[i];
  1007.                 }
  1008.             }

  1009.             return yDot.clone();
  1010.         }

  1011.         /** This method allows to compute the mean equinoctial elements rates da<sub>i</sub> / dt
  1012.          *  for a specific force model.
  1013.          *  @param forceModel force to take into account
  1014.          *  @param state current state
  1015.          *  @param auxiliaryElements auxiliary elements related to the current orbit
  1016.          *  @param parameters force model parameters
  1017.          *  @return the mean equinoctial elements rates da<sub>i</sub> / dt
  1018.          */
  1019.         private double[] elementRates(final DSSTForceModel forceModel,
  1020.                                       final SpacecraftState state,
  1021.                                       final AuxiliaryElements auxiliaryElements,
  1022.                                       final double[] parameters) {
  1023.             return forceModel.getMeanElementRate(state, auxiliaryElements, parameters);
  1024.         }

  1025.     }

  1026.     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
  1027.      *  <p>
  1028.      *  The errors are estimated from partial derivatives properties of orbits,
  1029.      *  starting from a scalar position error specified by the user.
  1030.      *  Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
  1031.      *  we get at constant energy (i.e. on a Keplerian trajectory):
  1032.      *
  1033.      *  <pre>
  1034.      *  V² r |dV| = mu |dr|
  1035.      *  </pre>
  1036.      *
  1037.      *  <p> So we deduce a scalar velocity error consistent with the position error. From here, we apply
  1038.      *  orbits Jacobians matrices to get consistent errors on orbital parameters.
  1039.      *
  1040.      *  <p>
  1041.      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
  1042.      *  local estimates, not global ones. So some care must be taken when using these tolerances.
  1043.      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
  1044.      *  position after several orbits integration.
  1045.      *  </p>
  1046.      *
  1047.      * @param dP user specified position error (m)
  1048.      * @param orbit reference orbit
  1049.      * @return a two rows array, row 0 being the absolute tolerance error
  1050.      *                       and row 1 being the relative tolerance error
  1051.      */
  1052.     public static double[][] tolerances(final double dP, final Orbit orbit) {

  1053.         return NumericalPropagator.tolerances(dP, orbit, OrbitType.EQUINOCTIAL);

  1054.     }

  1055.     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
  1056.      *  <p>
  1057.      *  The errors are estimated from partial derivatives properties of orbits,
  1058.      *  starting from scalar position and velocity errors specified by the user.
  1059.      *  <p>
  1060.      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
  1061.      *  local estimates, not global ones. So some care must be taken when using these tolerances.
  1062.      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
  1063.      *  position after several orbits integration.
  1064.      *  </p>
  1065.      *
  1066.      * @param dP user specified position error (m)
  1067.      * @param dV user specified velocity error (m/s)
  1068.      * @param orbit reference orbit
  1069.      * @return a two rows array, row 0 being the absolute tolerance error
  1070.      *                       and row 1 being the relative tolerance error
  1071.      * @since 10.3
  1072.      */
  1073.     public static double[][] tolerances(final double dP, final double dV, final Orbit orbit) {

  1074.         return NumericalPropagator.tolerances(dP, dV, orbit, OrbitType.EQUINOCTIAL);

  1075.     }

  1076.     /** Step handler used to compute the parameters for the short periodic contributions.
  1077.      * @author Lucian Barbulescu
  1078.      */
  1079.     private class ShortPeriodicsHandler implements ODEStepHandler {

  1080.         /** Force models used to compute short periodic terms. */
  1081.         private final List<DSSTForceModel> forceModels;

  1082.         /** Constructor.
  1083.          * @param forceModels force models
  1084.          */
  1085.         ShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
  1086.             this.forceModels = forceModels;
  1087.         }

  1088.         /** {@inheritDoc} */
  1089.         @Override
  1090.         public void handleStep(final ODEStateInterpolator interpolator) {

  1091.             // Get the grid points to compute
  1092.             final double[] interpolationPoints =
  1093.                     interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
  1094.                                                     interpolator.getCurrentState().getTime());

  1095.             final SpacecraftState[] meanStates = new SpacecraftState[interpolationPoints.length];
  1096.             for (int i = 0; i < interpolationPoints.length; ++i) {

  1097.                 // Build the mean state interpolated at grid point
  1098.                 final double time = interpolationPoints[i];
  1099.                 final ODEStateAndDerivative sd = interpolator.getInterpolatedState(time);
  1100.                 meanStates[i] = mapper.mapArrayToState(time,
  1101.                                                        sd.getPrimaryState(),
  1102.                                                        sd.getPrimaryDerivative(),
  1103.                                                        PropagationType.MEAN);
  1104.             }

  1105.             // Computate short periodic coefficients for this step
  1106.             for (DSSTForceModel forceModel : forceModels) {
  1107.                 forceModel.updateShortPeriodTerms(forceModel.getParameters(), meanStates);
  1108.             }

  1109.         }
  1110.     }
  1111. }