DSSTPropagator.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.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.OrekitMessages;
  38. import org.orekit.frames.Frame;
  39. import org.orekit.orbits.EquinoctialOrbit;
  40. import org.orekit.orbits.Orbit;
  41. import org.orekit.orbits.OrbitType;
  42. import org.orekit.orbits.PositionAngleType;
  43. import org.orekit.propagation.MatricesHarvester;
  44. import org.orekit.propagation.PropagationType;
  45. import org.orekit.propagation.Propagator;
  46. import org.orekit.propagation.SpacecraftState;
  47. import org.orekit.propagation.integration.AbstractIntegratedPropagator;
  48. import org.orekit.propagation.integration.AdditionalDerivativesProvider;
  49. import org.orekit.propagation.integration.StateMapper;
  50. import org.orekit.propagation.numerical.NumericalPropagator;
  51. import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
  52. import org.orekit.propagation.semianalytical.dsst.forces.DSSTNewtonianAttraction;
  53. import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
  54. import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
  55. import org.orekit.propagation.semianalytical.dsst.utilities.FixedNumberInterpolationGrid;
  56. import org.orekit.propagation.semianalytical.dsst.utilities.InterpolationGrid;
  57. import org.orekit.propagation.semianalytical.dsst.utilities.MaxGapInterpolationGrid;
  58. import org.orekit.time.AbsoluteDate;
  59. import org.orekit.utils.DoubleArrayDictionary;
  60. import org.orekit.utils.ParameterDriver;
  61. import org.orekit.utils.ParameterDriversList;
  62. import org.orekit.utils.ParameterDriversList.DelegatingDriver;
  63. import org.orekit.utils.ParameterObserver;
  64. import org.orekit.utils.TimeSpanMap;
  65. import org.orekit.utils.TimeSpanMap.Span;

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

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

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

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

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

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

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

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

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

  152.     /**
  153.      * Same as {@link org.orekit.propagation.AbstractPropagator#harvester} but with the
  154.      * more specific type. Saved to avoid a cast.
  155.      */
  156.     private DSSTHarvester harvester;

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

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


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

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

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

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

  247.     /** Set the initial state with osculating orbital elements.
  248.      *  @param initialState initial state (defined with osculating elements)
  249.      */
  250.     public void setInitialState(final SpacecraftState initialState) {
  251.         setInitialState(initialState, PropagationType.OSCULATING);
  252.     }

  253.     /** Set the initial state.
  254.      *  @param initialState initial state
  255.      *  @param stateType defined if the orbital state is defined with osculating or mean elements
  256.      */
  257.     public void setInitialState(final SpacecraftState initialState,
  258.                                 final PropagationType stateType) {
  259.         resetInitialState(initialState, stateType);
  260.     }

  261.     /** Reset the initial state.
  262.      *
  263.      *  @param state new initial state
  264.      */
  265.     @Override
  266.     public void resetInitialState(final SpacecraftState state) {
  267.         super.resetInitialState(state);
  268.         if (!hasNewtonianAttraction()) {
  269.             // use the state to define central attraction
  270.             setMu(state.getMu());
  271.         }
  272.         super.setStartDate(state.getDate());
  273.     }

  274.     /** {@inheritDoc}.
  275.      *
  276.      * <p>Change parameter {@link #initialIsOsculating()} accordingly
  277.      * @since 12.1.3
  278.      */
  279.     @Override
  280.     public void resetInitialState(final SpacecraftState state, final PropagationType stateType) {
  281.         // Reset initial state
  282.         resetInitialState(state);

  283.         // Change state of initial osculating, if needed
  284.         initialIsOsculating = stateType == PropagationType.OSCULATING;
  285.     }

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

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

  301.     /** Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
  302.      * @return names of the parameters (i.e. columns) of the Jacobian matrix
  303.      */
  304.     protected List<String> getJacobiansColumnsNames() {
  305.         final List<String> columnsNames = new ArrayList<>();
  306.         for (final DSSTForceModel forceModel : getAllForceModels()) {
  307.             for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
  308.                 if (driver.isSelected() && !columnsNames.contains(driver.getNamesSpanMap().getFirstSpan().getData())) {
  309.                     // As driver with same name should have same NamesSpanMap we only check if the first span is present,
  310.                     // if not we add all span names to columnsNames
  311.                     for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  312.                         columnsNames.add(span.getData());
  313.                     }
  314.                 }
  315.             }
  316.         }
  317.         Collections.sort(columnsNames);
  318.         return columnsNames;
  319.     }

  320.     /** {@inheritDoc} */
  321.     @Override
  322.     public DSSTHarvester setupMatricesComputation(
  323.             final String stmName,
  324.             final RealMatrix initialStm,
  325.             final DoubleArrayDictionary initialJacobianColumns) {

  326.         if (stmName == null) {
  327.             throw new OrekitException(OrekitMessages.NULL_ARGUMENT, "stmName");
  328.         }
  329.         final DSSTHarvester dsstHarvester =
  330.                 createHarvester(stmName, initialStm, initialJacobianColumns);
  331.         return this.harvester = dsstHarvester;
  332.     }

  333.     /** {@inheritDoc} */
  334.     @Override
  335.     protected DSSTHarvester createHarvester(final String stmName, final RealMatrix initialStm,
  336.                                             final DoubleArrayDictionary initialJacobianColumns) {
  337.         final DSSTHarvester dsstHarvester =
  338.                 new DSSTHarvester(this, stmName, initialStm, initialJacobianColumns);
  339.         this.harvester = dsstHarvester;
  340.         return dsstHarvester;
  341.     }

  342.     /** {@inheritDoc} */
  343.     @Override
  344.     protected DSSTHarvester getHarvester() {
  345.         return harvester;
  346.     }

  347.     /** {@inheritDoc} */
  348.     @Override
  349.     protected void setUpStmAndJacobianGenerators() {

  350.         final DSSTHarvester dsstHarvester = getHarvester();
  351.         if (dsstHarvester != null) {

  352.             // set up the additional equations and additional state providers
  353.             final DSSTStateTransitionMatrixGenerator stmGenerator = setUpStmGenerator();
  354.             setUpRegularParametersJacobiansColumns(stmGenerator);

  355.             // as we are now starting the propagation, everything is configured
  356.             // we can freeze the names in the harvester
  357.             dsstHarvester.freezeColumnsNames();

  358.         }

  359.     }

  360.     /** Set up the State Transition Matrix Generator.
  361.      * @return State Transition Matrix Generator
  362.      * @since 11.1
  363.      */
  364.     private DSSTStateTransitionMatrixGenerator setUpStmGenerator() {

  365.         final DSSTHarvester dsstHarvester = getHarvester();

  366.         // add the STM generator corresponding to the current settings, and setup state accordingly
  367.         DSSTStateTransitionMatrixGenerator stmGenerator = null;
  368.         for (final AdditionalDerivativesProvider equations : getAdditionalDerivativesProviders()) {
  369.             if (equations instanceof DSSTStateTransitionMatrixGenerator &&
  370.                 equations.getName().equals(dsstHarvester.getStmName())) {
  371.                 // the STM generator has already been set up in a previous propagation
  372.                 stmGenerator = (DSSTStateTransitionMatrixGenerator) equations;
  373.                 break;
  374.             }
  375.         }
  376.         if (stmGenerator == null) {
  377.             // this is the first time we need the STM generate, create it
  378.             stmGenerator = new DSSTStateTransitionMatrixGenerator(dsstHarvester.getStmName(),
  379.                                                                   getAllForceModels(),
  380.                                                                   getAttitudeProvider(),
  381.                                                                   getPropagationType());
  382.             addAdditionalDerivativesProvider(stmGenerator);
  383.         }

  384.         if (!getInitialIntegrationState().hasAdditionalState(dsstHarvester.getStmName())) {
  385.             // add the initial State Transition Matrix if it is not already there
  386.             // (perhaps due to a previous propagation)
  387.             setInitialState(stmGenerator.setInitialStateTransitionMatrix(getInitialState(),
  388.                                                                          dsstHarvester.getInitialStateTransitionMatrix()),
  389.                             getPropagationType());
  390.         }

  391.         return stmGenerator;

  392.     }

  393.     /** Set up the Jacobians columns generator for regular parameters.
  394.      * @param stmGenerator generator for the State Transition Matrix
  395.      * @since 11.1
  396.      */
  397.     private void setUpRegularParametersJacobiansColumns(final DSSTStateTransitionMatrixGenerator stmGenerator) {

  398.         // first pass: gather all parameters (excluding trigger dates), binding similar names together
  399.         final ParameterDriversList selected = new ParameterDriversList();
  400.         for (final DSSTForceModel forceModel : getAllForceModels()) {
  401.             for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
  402.                 selected.add(driver);
  403.             }
  404.         }

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

  408.         // third pass: sort parameters lexicographically
  409.         selected.sort();

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

  412.             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  413.                 DSSTIntegrableJacobianColumnGenerator generator = null;

  414.                 // check if we already have set up the providers
  415.                 for (final AdditionalDerivativesProvider provider : getAdditionalDerivativesProviders()) {
  416.                     if (provider instanceof DSSTIntegrableJacobianColumnGenerator &&
  417.                         provider.getName().equals(span.getData())) {
  418.                         // the Jacobian column generator has already been set up in a previous propagation
  419.                         generator = (DSSTIntegrableJacobianColumnGenerator) provider;
  420.                         break;
  421.                     }
  422.                 }

  423.                 if (generator == null) {
  424.                     // this is the first time we need the Jacobian column generator, create it
  425.                     generator = new DSSTIntegrableJacobianColumnGenerator(stmGenerator, span.getData());
  426.                     addAdditionalDerivativesProvider(generator);
  427.                 }

  428.                 if (!getInitialIntegrationState().hasAdditionalState(span.getData())) {
  429.                     // add the initial Jacobian column if it is not already there
  430.                     // (perhaps due to a previous propagation)
  431.                     setInitialState(getInitialState().addAdditionalState(span.getData(),
  432.                                                                          getHarvester().getInitialJacobianColumn(span.getData())),
  433.                                     getPropagationType());
  434.                 }
  435.             }

  436.         }

  437.     }

  438.     /** Check if the initial state is provided in osculating elements.
  439.      * @return true if initial state is provided in osculating elements
  440.      */
  441.     public boolean initialIsOsculating() {
  442.         return initialIsOsculating;
  443.     }

  444.     /** Set the interpolation grid generator.
  445.      * <p>
  446.      * The generator will create an interpolation grid with a fixed
  447.      * number of points for each mean element integration step.
  448.      * </p>
  449.      * <p>
  450.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  451.      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
  452.      * by default the propagator is set as to 3 interpolations points per step.
  453.      * </p>
  454.      * @param interpolationPoints number of interpolation points at
  455.      * each integration step
  456.      * @see #setInterpolationGridToMaxTimeGap(double)
  457.      * @since 7.1
  458.      */
  459.     public void setInterpolationGridToFixedNumberOfPoints(final int interpolationPoints) {
  460.         interpolationgrid = new FixedNumberInterpolationGrid(interpolationPoints);
  461.     }

  462.     /** Set the interpolation grid generator.
  463.      * <p>
  464.      * The generator will create an interpolation grid with a maximum
  465.      * time gap between interpolation points.
  466.      * </p>
  467.      * <p>
  468.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  469.      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
  470.      * by default the propagator is set as to 3 interpolations points per step.
  471.      * </p>
  472.      * @param maxGap maximum time gap between interpolation points (seconds)
  473.      * @see #setInterpolationGridToFixedNumberOfPoints(int)
  474.      * @since 7.1
  475.      */
  476.     public void setInterpolationGridToMaxTimeGap(final double maxGap) {
  477.         interpolationgrid = new MaxGapInterpolationGrid(maxGap);
  478.     }

  479.     /** Add a force model to the global perturbation model.
  480.      *  <p>
  481.      *  If this method is not called at all,
  482.      *  the integrated orbit will follow a Keplerian evolution only.
  483.      *  </p>
  484.      *  @param force perturbing {@link DSSTForceModel force} to add
  485.      *  @see #removeForceModels()
  486.      *  @see #setMu(double)
  487.      */
  488.     public void addForceModel(final DSSTForceModel force) {

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

  491.             // ensure we are notified of any mu change
  492.             force.getParametersDrivers().get(0).addObserver(new ParameterObserver() {
  493.                 /** {@inheritDoc} */
  494.                 @Override
  495.                 public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
  496.                     // mu PDriver should have only 1 span
  497.                     superSetMu(driver.getValue());
  498.                 }
  499.                 /** {@inheritDoc} */
  500.                 @Override
  501.                 public void valueSpanMapChanged(final TimeSpanMap<Double> previousValue, final ParameterDriver driver) {
  502.                     // mu PDriver should have only 1 span
  503.                     superSetMu(driver.getValue());
  504.                 }
  505.             });

  506.             if (hasNewtonianAttraction()) {
  507.                 // there is already a central attraction model, replace it
  508.                 forceModels.set(forceModels.size() - 1, force);
  509.             } else {
  510.                 // there are no central attraction model yet, add it at the end of the list
  511.                 forceModels.add(force);
  512.             }
  513.         } else {
  514.             // we want to add a perturbing force model
  515.             if (hasNewtonianAttraction()) {
  516.                 // insert the new force model before Newtonian attraction,
  517.                 // which should always be the last one in the list
  518.                 forceModels.add(forceModels.size() - 1, force);
  519.             } else {
  520.                 // we only have perturbing force models up to now, just append at the end of the list
  521.                 forceModels.add(force);
  522.             }
  523.         }

  524.         force.registerAttitudeProvider(getAttitudeProvider());

  525.     }

  526.     /** Remove all perturbing force models from the global perturbation model
  527.      *  (except central attraction).
  528.      *  <p>
  529.      *  Once all perturbing forces have been removed (and as long as no new force model is added),
  530.      *  the integrated orbit will follow a Keplerian evolution only.
  531.      *  </p>
  532.      *  @see #addForceModel(DSSTForceModel)
  533.      */
  534.     public void removeForceModels() {
  535.         final int last = forceModels.size() - 1;
  536.         if (hasNewtonianAttraction()) {
  537.             // preserve the Newtonian attraction model at the end
  538.             final DSSTForceModel newton = forceModels.get(last);
  539.             forceModels.clear();
  540.             forceModels.add(newton);
  541.         } else {
  542.             forceModels.clear();
  543.         }
  544.     }

  545.     /** Get all the force models, perturbing forces and Newtonian attraction included.
  546.      * @return list of perturbing force models, with Newtonian attraction being the
  547.      * last one
  548.      * @see #addForceModel(DSSTForceModel)
  549.      * @see #setMu(double)
  550.      */
  551.     public List<DSSTForceModel> getAllForceModels() {
  552.         return Collections.unmodifiableList(forceModels);
  553.     }

  554.     /** Get propagation parameter type.
  555.      * @return orbit type used for propagation
  556.      */
  557.     public OrbitType getOrbitType() {
  558.         return super.getOrbitType();
  559.     }

  560.     /** Get propagation parameter type.
  561.      * @return angle type to use for propagation
  562.      */
  563.     public PositionAngleType getPositionAngleType() {
  564.         return super.getPositionAngleType();
  565.     }

  566.     /** Conversion from mean to osculating orbit.
  567.      * <p>
  568.      * Compute osculating state <b>in a DSST sense</b>, corresponding to the
  569.      * mean SpacecraftState in input, and according to the Force models taken
  570.      * into account.
  571.      * </p><p>
  572.      * Since the osculating state is obtained by adding short-periodic variation
  573.      * of each force model, the resulting output will depend on the
  574.      * force models parameterized in input.
  575.      * </p>
  576.      * @param mean Mean state to convert
  577.      * @param forces Forces to take into account
  578.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  579.      * like atmospheric drag, radiation pressure or specific user-defined models)
  580.      * @return osculating state in a DSST sense
  581.      */
  582.     public static SpacecraftState computeOsculatingState(final SpacecraftState mean,
  583.                                                          final AttitudeProvider attitudeProvider,
  584.                                                          final Collection<DSSTForceModel> forces) {

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

  587.         // Set the force models
  588.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<>();
  589.         for (final DSSTForceModel force : forces) {
  590.             force.registerAttitudeProvider(attitudeProvider);
  591.             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters(mean.getDate())));
  592.             force.updateShortPeriodTerms(force.getParametersAllValues(), mean);
  593.         }

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

  595.         return new SpacecraftState(osculatingOrbit, mean.getAttitude(), mean.getMass(),
  596.                                    mean.getAdditionalStatesValues(), mean.getAdditionalStatesDerivatives());

  597.     }

  598.     /** Conversion from osculating to mean orbit.
  599.      * <p>
  600.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  601.      * osculating SpacecraftState in input, and according to the Force models
  602.      * taken into account.
  603.      * </p><p>
  604.      * Since the osculating state is obtained with the computation of
  605.      * short-periodic variation of each force model, the resulting output will
  606.      * depend on the force models parameterized in input.
  607.      * </p><p>
  608.      * The computation is done through a fixed-point iteration process.
  609.      * </p>
  610.      * @param osculating Osculating state to convert
  611.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  612.      * like atmospheric drag, radiation pressure or specific user-defined models)
  613.      * @param forceModels Forces to take into account
  614.      * @return mean state in a DSST sense
  615.      */
  616.     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
  617.                                                    final AttitudeProvider attitudeProvider,
  618.                                                    final Collection<DSSTForceModel> forceModels) {
  619.         return computeMeanState(osculating, attitudeProvider, forceModels, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  620.     }

  621.     /** Conversion from osculating to mean orbit.
  622.      * <p>
  623.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  624.      * osculating SpacecraftState in input, and according to the Force models
  625.      * taken into account.
  626.      * </p><p>
  627.      * Since the osculating state is obtained with the computation of
  628.      * short-periodic variation of each force model, the resulting output will
  629.      * depend on the force models parameterized in input.
  630.      * </p><p>
  631.      * The computation is done through a fixed-point iteration process.
  632.      * </p>
  633.      * @param osculating Osculating state to convert
  634.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  635.      * like atmospheric drag, radiation pressure or specific user-defined models)
  636.      * @param forceModels Forces to take into account
  637.      * @param epsilon convergence threshold for mean parameters conversion
  638.      * @param maxIterations maximum iterations for mean parameters conversion
  639.      * @return mean state in a DSST sense
  640.      * @since 10.1
  641.      */
  642.     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
  643.                                                    final AttitudeProvider attitudeProvider,
  644.                                                    final Collection<DSSTForceModel> forceModels,
  645.                                                    final double epsilon,
  646.                                                    final int maxIterations) {
  647.         final Orbit meanOrbit = computeMeanOrbit(osculating, attitudeProvider, forceModels, epsilon, maxIterations);
  648.         return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(),
  649.                                    osculating.getAdditionalStatesValues(), osculating.getAdditionalStatesDerivatives());
  650.     }

  651.      /** Override the default value of the parameter.
  652.      *  <p>
  653.      *  By default, if the initial orbit is defined as osculating,
  654.      *  it will be averaged over 2 satellite revolutions.
  655.      *  This can be changed by using this method.
  656.      *  </p>
  657.      *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
  658.      *                             elements
  659.      */
  660.     public void setSatelliteRevolution(final int satelliteRevolution) {
  661.         mapper.setSatelliteRevolution(satelliteRevolution);
  662.     }

  663.     /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  664.      *  @return number of satellite revolutions to use for converting osculating to mean elements
  665.      */
  666.     public int getSatelliteRevolution() {
  667.         return mapper.getSatelliteRevolution();
  668.     }

  669.     /** Override the default value short periodic terms.
  670.     *  <p>
  671.     *  By default, short periodic terms are initialized before
  672.     *  the numerical integration of the mean orbital elements.
  673.     *  </p>
  674.     *  @param shortPeriodTerms short periodic terms
  675.     */
  676.     public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
  677.         mapper.setShortPeriodTerms(shortPeriodTerms);
  678.     }

  679.    /** Get the short periodic terms.
  680.     *  @return the short periodic terms
  681.     */
  682.     public List<ShortPeriodTerms> getShortPeriodTerms() {
  683.         return mapper.getShortPeriodTerms();
  684.     }

  685.     /** {@inheritDoc} */
  686.     @Override
  687.     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
  688.         super.setAttitudeProvider(attitudeProvider);

  689.         //Register the attitude provider for each force model
  690.         for (final DSSTForceModel force : forceModels) {
  691.             force.registerAttitudeProvider(attitudeProvider);
  692.         }
  693.     }

  694.     /** Method called just before integration.
  695.      * <p>
  696.      * The default implementation does nothing, it may be specialized in subclasses.
  697.      * </p>
  698.      * @param initialState initial state
  699.      * @param tEnd target date at which state should be propagated
  700.      */
  701.     @Override
  702.     protected void beforeIntegration(final SpacecraftState initialState,
  703.                                      final AbsoluteDate tEnd) {
  704.         // If this method is updated also update DSSTStateTransitionMatrixGenerator.init(...)

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

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

  709.         // initialize all perturbing forces
  710.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<>();
  711.         for (final DSSTForceModel force : forceModels) {
  712.             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, type, force.getParameters(initialState.getDate())));
  713.         }
  714.         mapper.setShortPeriodTerms(shortPeriodTerms);

  715.         // if required, insert the special short periodics step handler
  716.         if (type == PropagationType.OSCULATING) {
  717.             final ShortPeriodicsHandler spHandler = new ShortPeriodicsHandler(forceModels);
  718.             // Compute short periodic coefficients for this point
  719.             for (DSSTForceModel forceModel : forceModels) {
  720.                 forceModel.updateShortPeriodTerms(forceModel.getParametersAllValues(), initialState);
  721.             }
  722.             final Collection<ODEStepHandler> stepHandlers = new ArrayList<>();
  723.             stepHandlers.add(spHandler);
  724.             final ODEIntegrator integrator = getIntegrator();
  725.             final Collection<ODEStepHandler> existing = integrator.getStepHandlers();
  726.             stepHandlers.addAll(existing);

  727.             integrator.clearStepHandlers();

  728.             // add back the existing handlers after the short periodics one
  729.             for (final ODEStepHandler sp : stepHandlers) {
  730.                 integrator.addStepHandler(sp);
  731.             }
  732.         }
  733.     }

  734.     /** {@inheritDoc} */
  735.     @Override
  736.     protected void afterIntegration() {
  737.         // remove the special short periodics step handler if added before
  738.         if (getPropagationType() == PropagationType.OSCULATING) {
  739.             final List<ODEStepHandler> preserved = new ArrayList<>();
  740.             final ODEIntegrator integrator = getIntegrator();
  741.             for (final ODEStepHandler sp : integrator.getStepHandlers()) {
  742.                 if (!(sp instanceof ShortPeriodicsHandler)) {
  743.                     preserved.add(sp);
  744.                 }
  745.             }

  746.             // clear the list
  747.             integrator.clearStepHandlers();

  748.             // add back the step handlers that were important for the user
  749.             for (final ODEStepHandler sp : preserved) {
  750.                 integrator.addStepHandler(sp);
  751.             }
  752.         }
  753.     }

  754.     /** Compute mean state from osculating state.
  755.      * <p>
  756.      * Compute in a DSST sense the mean state corresponding to the input osculating state.
  757.      * </p><p>
  758.      * The computing is done through a fixed-point iteration process.
  759.      * </p>
  760.      * @param osculating initial osculating state
  761.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  762.      * like atmospheric drag, radiation pressure or specific user-defined models)
  763.      * @param forceModels force models
  764.      * @param epsilon convergence threshold for mean parameters conversion
  765.      * @param maxIterations maximum iterations for mean parameters conversion
  766.      * @return mean state
  767.      */
  768.     private static Orbit computeMeanOrbit(final SpacecraftState osculating,
  769.                                           final AttitudeProvider attitudeProvider,
  770.                                           final Collection<DSSTForceModel> forceModels, final double epsilon, final int maxIterations) {

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

  773.         // threshold for each parameter
  774.         final double thresholdA = epsilon * (1 + FastMath.abs(meanOrbit.getA()));
  775.         final double thresholdE = epsilon * (1 + meanOrbit.getE());
  776.         final double thresholdI = epsilon * (1 + meanOrbit.getI());
  777.         final double thresholdL = epsilon * FastMath.PI;

  778.         // ensure all Gaussian force models can rely on attitude
  779.         for (final DSSTForceModel force : forceModels) {
  780.             force.registerAttitudeProvider(attitudeProvider);
  781.         }

  782.         int i = 0;
  783.         while (i++ < maxIterations) {

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

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

  787.             // Set the force models
  788.             final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<>();
  789.             for (final DSSTForceModel force : forceModels) {
  790.                 shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters(meanState.getDate())));
  791.                 force.updateShortPeriodTerms(force.getParametersAllValues(), meanState);
  792.             }

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

  795.             // adapted parameters residuals
  796.             final double deltaA  = osculating.getA() - rebuilt.getA();
  797.             final double deltaEx = osculating.getEquinoctialEx() - rebuilt.getEquinoctialEx();
  798.             final double deltaEy = osculating.getEquinoctialEy() - rebuilt.getEquinoctialEy();
  799.             final double deltaHx = osculating.getHx() - rebuilt.getHx();
  800.             final double deltaHy = osculating.getHy() - rebuilt.getHy();
  801.             final double deltaLM = MathUtils.normalizeAngle(osculating.getLM() - rebuilt.getLM(), 0.0);

  802.             // check convergence
  803.             if (FastMath.abs(deltaA)  < thresholdA &&
  804.                 FastMath.abs(deltaEx) < thresholdE &&
  805.                 FastMath.abs(deltaEy) < thresholdE &&
  806.                 FastMath.abs(deltaHx) < thresholdI &&
  807.                 FastMath.abs(deltaHy) < thresholdI &&
  808.                 FastMath.abs(deltaLM) < thresholdL) {
  809.                 return meanOrbit;
  810.             }

  811.             // update mean parameters
  812.             meanOrbit = new EquinoctialOrbit(meanOrbit.getA() + deltaA,
  813.                                              meanOrbit.getEquinoctialEx() + deltaEx,
  814.                                              meanOrbit.getEquinoctialEy() + deltaEy,
  815.                                              meanOrbit.getHx() + deltaHx,
  816.                                              meanOrbit.getHy() + deltaHy,
  817.                                              meanOrbit.getLM() + deltaLM,
  818.                                              PositionAngleType.MEAN, meanOrbit.getFrame(),
  819.                                              meanOrbit.getDate(), meanOrbit.getMu());
  820.         }

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

  822.     }

  823.     /** Compute osculating state from mean state.
  824.      * <p>
  825.      * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
  826.      * </p>
  827.      * @param meanState initial mean state
  828.      * @param shortPeriodTerms short period terms
  829.      * @return osculating state
  830.      */
  831.     private static EquinoctialOrbit computeOsculatingOrbit(final SpacecraftState meanState,
  832.                                                            final List<ShortPeriodTerms> shortPeriodTerms) {

  833.         final double[] mean = new double[6];
  834.         final double[] meanDot = new double[6];
  835.         OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngleType.MEAN, mean, meanDot);
  836.         final double[] y = mean.clone();
  837.         for (final ShortPeriodTerms spt : shortPeriodTerms) {
  838.             final double[] shortPeriodic = spt.value(meanState.getOrbit());
  839.             for (int i = 0; i < shortPeriodic.length; i++) {
  840.                 y[i] += shortPeriodic[i];
  841.             }
  842.         }
  843.         return (EquinoctialOrbit) OrbitType.EQUINOCTIAL.mapArrayToOrbit(y, meanDot,
  844.                                                                         PositionAngleType.MEAN, meanState.getDate(),
  845.                                                                         meanState.getMu(), meanState.getFrame());
  846.     }

  847.     /** {@inheritDoc} */
  848.     @Override
  849.     protected SpacecraftState getInitialIntegrationState() {
  850.         if (initialIsOsculating) {
  851.             // the initial state is an osculating state,
  852.             // it must be converted to mean state
  853.             return computeMeanState(getInitialState(), getAttitudeProvider(), forceModels);
  854.         } else {
  855.             // the initial state is already a mean state
  856.             return getInitialState();
  857.         }
  858.     }

  859.     /** {@inheritDoc}
  860.      * <p>
  861.      * Note that for DSST, orbit type is hardcoded to {@link OrbitType#EQUINOCTIAL}
  862.      * and position angle type is hardcoded to {@link PositionAngleType#MEAN}, so
  863.      * the corresponding parameters are ignored.
  864.      * </p>
  865.      */
  866.     @Override
  867.     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
  868.                                        final OrbitType ignoredOrbitType, final PositionAngleType ignoredPositionAngleType,
  869.                                        final AttitudeProvider attitudeProvider, final Frame frame) {

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

  873.         // copy the specific settings from the existing mapper
  874.         if (mapper != null) {
  875.             newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
  876.             newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
  877.             newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
  878.         }

  879.         mapper = newMapper;
  880.         return mapper;

  881.     }


  882.     /** Get the short period terms value.
  883.      * @param meanState the mean state
  884.      * @return shortPeriodTerms short period terms
  885.      * @since 7.1
  886.      */
  887.     public double[] getShortPeriodTermsValue(final SpacecraftState meanState) {
  888.         final double[] sptValue = new double[6];

  889.         for (ShortPeriodTerms spt : mapper.getShortPeriodTerms()) {
  890.             final double[] shortPeriodic = spt.value(meanState.getOrbit());
  891.             for (int i = 0; i < shortPeriodic.length; i++) {
  892.                 sptValue[i] += shortPeriodic[i];
  893.             }
  894.         }
  895.         return sptValue;
  896.     }


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

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

  901.         /** Number of satellite revolutions in the averaging interval. */
  902.         private int                        satelliteRevolution;

  903.         /** Short period terms. */
  904.         private List<ShortPeriodTerms>     shortPeriodTerms;

  905.         /** Simple constructor.
  906.          * @param referenceDate reference date
  907.          * @param mu central attraction coefficient (m³/s²)
  908.          * @param attitudeProvider attitude provider
  909.          * @param frame inertial frame
  910.          */
  911.         MeanPlusShortPeriodicMapper(final AbsoluteDate referenceDate, final double mu,
  912.                                     final AttitudeProvider attitudeProvider, final Frame frame) {

  913.             super(referenceDate, mu, OrbitType.EQUINOCTIAL, PositionAngleType.MEAN, attitudeProvider, frame);

  914.             this.selectedCoefficients = null;

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

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

  918.         }

  919.         /** {@inheritDoc} */
  920.         @Override
  921.         public SpacecraftState mapArrayToState(final AbsoluteDate date,
  922.                                                final double[] y, final double[] yDot,
  923.                                                final PropagationType type) {

  924.             // add short periodic variations to mean elements to get osculating elements
  925.             // (the loop may not be performed if there are no force models and in the
  926.             //  case we want to remain in mean parameters only)
  927.             final double[] elements = y.clone();
  928.             final DoubleArrayDictionary coefficients;
  929.             if (type == PropagationType.MEAN) {
  930.                 coefficients = null;
  931.             } else {
  932.                 final Orbit meanOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngleType.MEAN, date, getMu(), getFrame());
  933.                 coefficients = selectedCoefficients == null ? null : new DoubleArrayDictionary();
  934.                 for (final ShortPeriodTerms spt : shortPeriodTerms) {
  935.                     final double[] shortPeriodic = spt.value(meanOrbit);
  936.                     for (int i = 0; i < shortPeriodic.length; i++) {
  937.                         elements[i] += shortPeriodic[i];
  938.                     }
  939.                     if (selectedCoefficients != null) {
  940.                         coefficients.putAll(spt.getCoefficients(date, selectedCoefficients));
  941.                     }
  942.                 }
  943.             }

  944.             final double mass = elements[6];
  945.             if (mass <= 0.0) {
  946.                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS, mass);
  947.             }

  948.             final Orbit orbit       = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngleType.MEAN, date, getMu(), getFrame());
  949.             final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());

  950.             if (coefficients == null) {
  951.                 return new SpacecraftState(orbit, attitude, mass);
  952.             } else {
  953.                 return new SpacecraftState(orbit, attitude, mass, coefficients);
  954.             }

  955.         }

  956.         /** {@inheritDoc} */
  957.         @Override
  958.         public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot) {

  959.             OrbitType.EQUINOCTIAL.mapOrbitToArray(state.getOrbit(), PositionAngleType.MEAN, y, yDot);
  960.             y[6] = state.getMass();

  961.         }

  962.         /** Set the number of satellite revolutions to use for converting osculating to mean elements.
  963.          *  <p>
  964.          *  By default, if the initial orbit is defined as osculating,
  965.          *  it will be averaged over 2 satellite revolutions.
  966.          *  This can be changed by using this method.
  967.          *  </p>
  968.          *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
  969.          *                             elements
  970.          */
  971.         public void setSatelliteRevolution(final int satelliteRevolution) {
  972.             this.satelliteRevolution = satelliteRevolution;
  973.         }

  974.         /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  975.          *  @return number of satellite revolutions to use for converting osculating to mean elements
  976.          */
  977.         public int getSatelliteRevolution() {
  978.             return satelliteRevolution;
  979.         }

  980.         /** Set the selected short periodic coefficients that must be stored as additional states.
  981.          * @param selectedCoefficients short periodic coefficients that must be stored as additional states
  982.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  983.          */
  984.         public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
  985.             this.selectedCoefficients = selectedCoefficients;
  986.         }

  987.         /** Get the selected short periodic coefficients that must be stored as additional states.
  988.          * @return short periodic coefficients that must be stored as additional states
  989.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  990.          */
  991.         public Set<String> getSelectedCoefficients() {
  992.             return selectedCoefficients;
  993.         }

  994.         /** Set the short period terms.
  995.          * @param shortPeriodTerms short period terms
  996.          * @since 7.1
  997.          */
  998.         public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
  999.             this.shortPeriodTerms = shortPeriodTerms;
  1000.         }

  1001.         /** Get the short period terms.
  1002.          * @return shortPeriodTerms short period terms
  1003.          * @since 7.1
  1004.          */
  1005.         public List<ShortPeriodTerms> getShortPeriodTerms() {
  1006.             return shortPeriodTerms;
  1007.         }

  1008.     }

  1009.     /** {@inheritDoc} */
  1010.     @Override
  1011.     protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
  1012.         return new Main(integrator);
  1013.     }

  1014.     /** Internal class for mean parameters integration. */
  1015.     private class Main implements MainStateEquations {

  1016.         /** Derivatives array. */
  1017.         private final double[] yDot;

  1018.         /** Simple constructor.
  1019.          * @param integrator numerical integrator to use for propagation.
  1020.          */
  1021.         Main(final ODEIntegrator integrator) {
  1022.             yDot = new double[7];

  1023.             // Setup event detectors for each force model
  1024.             forceModels.forEach(dsstForceModel -> dsstForceModel.getEventDetectors().
  1025.                                 forEach(eventDetector -> setUpEventDetector(integrator, eventDetector)));
  1026.         }

  1027.         /** {@inheritDoc} */
  1028.         @Override
  1029.         public void init(final SpacecraftState initialState, final AbsoluteDate target) {
  1030.             forceModels.forEach(fm -> fm.init(initialState, target));
  1031.         }

  1032.         /** {@inheritDoc} */
  1033.         @Override
  1034.         public double[] computeDerivatives(final SpacecraftState state) {

  1035.             Arrays.fill(yDot, 0.0);

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

  1038.             // compute the contributions of all perturbing forces
  1039.             for (final DSSTForceModel forceModel : forceModels) {
  1040.                 final double[] daidt = elementRates(forceModel, state, auxiliaryElements, forceModel.getParameters(state.getDate()));
  1041.                 for (int i = 0; i < daidt.length; i++) {
  1042.                     yDot[i] += daidt[i];
  1043.                 }
  1044.             }

  1045.             return yDot.clone();
  1046.         }

  1047.         /** This method allows to compute the mean equinoctial elements rates da<sub>i</sub> / dt
  1048.          *  for a specific force model.
  1049.          *  @param forceModel force to take into account
  1050.          *  @param state current state
  1051.          *  @param auxiliaryElements auxiliary elements related to the current orbit
  1052.          *  @param parameters force model parameters at state date (only 1 value for
  1053.          *  each parameter
  1054.          *  @return the mean equinoctial elements rates da<sub>i</sub> / dt
  1055.          */
  1056.         private double[] elementRates(final DSSTForceModel forceModel,
  1057.                                       final SpacecraftState state,
  1058.                                       final AuxiliaryElements auxiliaryElements,
  1059.                                       final double[] parameters) {
  1060.             return forceModel.getMeanElementRate(state, auxiliaryElements, parameters);
  1061.         }

  1062.     }

  1063.     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
  1064.      *  <p>
  1065.      *  The errors are estimated from partial derivatives properties of orbits,
  1066.      *  starting from a scalar position error specified by the user.
  1067.      *  Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
  1068.      *  we get at constant energy (i.e. on a Keplerian trajectory):
  1069.      *
  1070.      *  <pre>
  1071.      *  V r² |dV| = mu |dr|
  1072.      *  </pre>
  1073.      *
  1074.      *  <p> So we deduce a scalar velocity error consistent with the position error. From here, we apply
  1075.      *  orbits Jacobians matrices to get consistent errors on orbital parameters.
  1076.      *
  1077.      *  <p>
  1078.      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
  1079.      *  local estimates, not global ones. So some care must be taken when using these tolerances.
  1080.      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
  1081.      *  position after several orbits integration.
  1082.      *  </p>
  1083.      *
  1084.      * @param dP user specified position error (m)
  1085.      * @param orbit reference orbit
  1086.      * @return a two rows array, row 0 being the absolute tolerance error
  1087.      *                       and row 1 being the relative tolerance error
  1088.      */
  1089.     public static double[][] tolerances(final double dP, final Orbit orbit) {

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

  1091.     }

  1092.     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
  1093.      *  <p>
  1094.      *  The errors are estimated from partial derivatives properties of orbits,
  1095.      *  starting from scalar position and velocity errors specified by the user.
  1096.      *  <p>
  1097.      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
  1098.      *  local estimates, not global ones. So some care must be taken when using these tolerances.
  1099.      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
  1100.      *  position after several orbits integration.
  1101.      *  </p>
  1102.      *
  1103.      * @param dP user specified position error (m)
  1104.      * @param dV user specified velocity error (m/s)
  1105.      * @param orbit reference orbit
  1106.      * @return a two rows array, row 0 being the absolute tolerance error
  1107.      *                       and row 1 being the relative tolerance error
  1108.      * @since 10.3
  1109.      */
  1110.     public static double[][] tolerances(final double dP, final double dV, final Orbit orbit) {

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

  1112.     }

  1113.     /** Step handler used to compute the parameters for the short periodic contributions.
  1114.      * @author Lucian Barbulescu
  1115.      */
  1116.     private class ShortPeriodicsHandler implements ODEStepHandler {

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

  1119.         /** Constructor.
  1120.          * @param forceModels force models
  1121.          */
  1122.         ShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
  1123.             this.forceModels = forceModels;
  1124.         }

  1125.         /** {@inheritDoc} */
  1126.         @Override
  1127.         public void handleStep(final ODEStateInterpolator interpolator) {

  1128.             // Get the grid points to compute
  1129.             final double[] interpolationPoints =
  1130.                     interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
  1131.                                                     interpolator.getCurrentState().getTime());

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

  1134.                 // Build the mean state interpolated at grid point
  1135.                 final double time = interpolationPoints[i];
  1136.                 final ODEStateAndDerivative sd = interpolator.getInterpolatedState(time);
  1137.                 meanStates[i] = mapper.mapArrayToState(time,
  1138.                                                        sd.getPrimaryState(),
  1139.                                                        sd.getPrimaryDerivative(),
  1140.                                                        PropagationType.MEAN);
  1141.             }

  1142.             // Computate short periodic coefficients for this step
  1143.             for (DSSTForceModel forceModel : forceModels) {
  1144.                 forceModel.updateShortPeriodTerms(forceModel.getParametersAllValues(), meanStates);
  1145.             }
  1146.         }
  1147.     }
  1148. }