DSSTPropagator.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.propagation.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.orekit.annotation.DefaultDataContext;
  31. import org.orekit.attitudes.Attitude;
  32. import org.orekit.attitudes.AttitudeProvider;
  33. import org.orekit.data.DataContext;
  34. import org.orekit.errors.OrekitException;
  35. import org.orekit.errors.OrekitMessages;
  36. import org.orekit.frames.Frame;
  37. import org.orekit.orbits.EquinoctialOrbit;
  38. import org.orekit.orbits.Orbit;
  39. import org.orekit.orbits.OrbitType;
  40. import org.orekit.orbits.PositionAngleType;
  41. import org.orekit.propagation.AbstractPropagator;
  42. import org.orekit.propagation.MatricesHarvester;
  43. import org.orekit.propagation.PropagationType;
  44. import org.orekit.propagation.Propagator;
  45. import org.orekit.propagation.SpacecraftState;
  46. import org.orekit.propagation.conversion.osc2mean.DSSTTheory;
  47. import org.orekit.propagation.conversion.osc2mean.FixedPointConverter;
  48. import org.orekit.propagation.conversion.osc2mean.MeanTheory;
  49. import org.orekit.propagation.conversion.osc2mean.OsculatingToMeanConverter;
  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.semianalytical.dsst.forces.DSSTForceModel;
  54. import org.orekit.propagation.semianalytical.dsst.forces.DSSTNewtonianAttraction;
  55. import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
  56. import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
  57. import org.orekit.propagation.semianalytical.dsst.utilities.FixedNumberInterpolationGrid;
  58. import org.orekit.propagation.semianalytical.dsst.utilities.InterpolationGrid;
  59. import org.orekit.propagation.semianalytical.dsst.utilities.MaxGapInterpolationGrid;
  60. import org.orekit.time.AbsoluteDate;
  61. import org.orekit.utils.DataDictionary;
  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. import org.orekit.utils.TimeSpanMap;
  68. import org.orekit.utils.TimeSpanMap.Span;

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

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

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

  143.     /** Default value for maxIterations. */
  144.     private static final int MAX_ITERATIONS_DEFAULT = 200;

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

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

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

  151.     /** State mapper holding the force models. */
  152.     private MeanPlusShortPeriodicMapper mapper;

  153.     /** Generator for the interpolation grid. */
  154.     private InterpolationGrid interpolationgrid;

  155.     /**
  156.      * Same as {@link AbstractPropagator#getHarvester()} but with the
  157.      * more specific type. Saved to avoid a cast.
  158.      */
  159.     private DSSTHarvester harvester;

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

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


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

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

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

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

  251.     /** Set the initial state with osculating orbital elements.
  252.      *  @param initialState initial state (defined with osculating elements)
  253.      */
  254.     public void setInitialState(final SpacecraftState initialState) {
  255.         setInitialState(initialState, PropagationType.OSCULATING);
  256.     }

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

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

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

  287.         // Change state of initial osculating, if needed
  288.         initialIsOsculating = stateType == PropagationType.OSCULATING;
  289.     }

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

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

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

  324.     /** {@inheritDoc} */
  325.     @Override
  326.     public DSSTHarvester setupMatricesComputation(
  327.             final String stmName,
  328.             final RealMatrix initialStm,
  329.             final DoubleArrayDictionary initialJacobianColumns) {

  330.         if (stmName == null) {
  331.             throw new OrekitException(OrekitMessages.NULL_ARGUMENT, "stmName");
  332.         }
  333.         final DSSTHarvester dsstHarvester =
  334.                 createHarvester(stmName, initialStm, initialJacobianColumns);
  335.         return this.harvester = dsstHarvester;
  336.     }

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

  346.     /** {@inheritDoc} */
  347.     @Override
  348.     protected DSSTHarvester getHarvester() {
  349.         return harvester;
  350.     }

  351.     /** {@inheritDoc} */
  352.     @Override
  353.     protected void setUpStmAndJacobianGenerators() {

  354.         final DSSTHarvester dsstHarvester = getHarvester();
  355.         if (dsstHarvester != null) {

  356.             // set up the additional equations and additional state providers
  357.             final DSSTStateTransitionMatrixGenerator stmGenerator = setUpStmGenerator();
  358.             setUpRegularParametersJacobiansColumns(stmGenerator);

  359.             // as we are now starting the propagation, everything is configured
  360.             // we can freeze the names in the harvester
  361.             dsstHarvester.freezeColumnsNames();

  362.         }

  363.     }

  364.     /** Set up the State Transition Matrix Generator.
  365.      * @return State Transition Matrix Generator
  366.      * @since 11.1
  367.      */
  368.     private DSSTStateTransitionMatrixGenerator setUpStmGenerator() {

  369.         final DSSTHarvester dsstHarvester = getHarvester();

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

  388.         if (!getInitialIntegrationState().hasAdditionalData(dsstHarvester.getStmName())) {
  389.             // add the initial State Transition Matrix if it is not already there
  390.             // (perhaps due to a previous propagation)
  391.             setInitialState(stmGenerator.setInitialStateTransitionMatrix(getInitialState(),
  392.                                                                          dsstHarvester.getInitialStateTransitionMatrix()),
  393.                             initialIsOsculating() ? PropagationType.OSCULATING : PropagationType.MEAN);
  394.         }

  395.         return stmGenerator;

  396.     }

  397.     /** Set up the Jacobians columns generator for regular parameters.
  398.      * @param stmGenerator generator for the State Transition Matrix
  399.      * @since 11.1
  400.      */
  401.     private void setUpRegularParametersJacobiansColumns(final DSSTStateTransitionMatrixGenerator stmGenerator) {

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

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

  412.         // third pass: sort parameters lexicographically
  413.         selected.sort();

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

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

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

  427.                 if (generator == null) {
  428.                     // this is the first time we need the Jacobian column generator, create it
  429.                     generator = new DSSTIntegrableJacobianColumnGenerator(stmGenerator, span.getData());
  430.                     addAdditionalDerivativesProvider(generator);
  431.                 }

  432.                 if (!getInitialIntegrationState().hasAdditionalData(span.getData())) {
  433.                     // add the initial Jacobian column if it is not already there
  434.                     // (perhaps due to a previous propagation)
  435.                     setInitialState(getInitialState().addAdditionalData(span.getData(),
  436.                                                                          getHarvester().getInitialJacobianColumn(span.getData())),
  437.                                     initialIsOsculating() ? PropagationType.OSCULATING : PropagationType.MEAN);
  438.                 }
  439.             }

  440.         }

  441.     }

  442.     /** Check if the initial state is provided in osculating elements.
  443.      * @return true if initial state is provided in osculating elements
  444.      */
  445.     public boolean initialIsOsculating() {
  446.         return initialIsOsculating;
  447.     }

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

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

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

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

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

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

  528.         force.registerAttitudeProvider(getAttitudeProvider());

  529.     }

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

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

  558.     /** Get propagation parameter type.
  559.      * @return orbit type used for propagation
  560.      */
  561.     @Override
  562.     public OrbitType getOrbitType() {
  563.         return super.getOrbitType();
  564.     }

  565.     /** Get propagation parameter type.
  566.      * @return angle type to use for propagation
  567.      */
  568.     @Override
  569.     public PositionAngleType getPositionAngleType() {
  570.         return super.getPositionAngleType();
  571.     }

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

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

  593.         // Set the force models
  594.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<>();
  595.         for (final DSSTForceModel force : forces) {
  596.             force.registerAttitudeProvider(attitudeProvider);
  597.             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters(mean.getDate())));
  598.             force.updateShortPeriodTerms(force.getParametersAllValues(), mean);
  599.         }

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

  601.         return new SpacecraftState(osculatingOrbit, mean.getAttitude(), mean.getMass(),
  602.                                    mean.getAdditionalDataValues(), mean.getAdditionalStatesDerivatives());

  603.     }

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

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

  657.     /** Conversion from osculating to mean orbit.
  658.      * <p>
  659.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  660.      * osculating SpacecraftState in input, and according to the Force models
  661.      * taken into account.
  662.      * </p><p>
  663.      * Since the osculating state is obtained with the computation of
  664.      * short-periodic variation of each force model, the resulting output will
  665.      * depend on the force models parameterized in input.
  666.      * </p><p>
  667.      * The computation is done using the given osculating to mean orbit converter.
  668.      * </p>
  669.      * @param osculating       osculating state to convert
  670.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  671.      *                         like atmospheric drag, radiation pressure or specific user-defined models)
  672.      * @param forceModels      forces to take into account
  673.      * @param converter        osculating to mean orbit converter
  674.      * @return mean state in a DSST sense
  675.      * @since 13.0
  676.      */
  677.     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
  678.                                                    final AttitudeProvider attitudeProvider,
  679.                                                    final Collection<DSSTForceModel> forceModels,
  680.                                                    final OsculatingToMeanConverter converter) {

  681.         final MeanTheory theory = new DSSTTheory(forceModels, attitudeProvider, osculating.getMass());
  682.         converter.setMeanTheory(theory);
  683.         final Orbit meanOrbit = converter.convertToMean(osculating.getOrbit());
  684.         return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(),
  685.                                    osculating.getAdditionalDataValues(), osculating.getAdditionalStatesDerivatives());
  686.     }

  687.      /** Override the default value of the parameter.
  688.      *  <p>
  689.      *  By default, if the initial orbit is defined as osculating,
  690.      *  it will be averaged over 2 satellite revolutions.
  691.      *  This can be changed by using this method.
  692.      *  </p>
  693.      *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
  694.      *                             elements
  695.      */
  696.     public void setSatelliteRevolution(final int satelliteRevolution) {
  697.         mapper.setSatelliteRevolution(satelliteRevolution);
  698.     }

  699.     /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  700.      *  @return number of satellite revolutions to use for converting osculating to mean elements
  701.      */
  702.     public int getSatelliteRevolution() {
  703.         return mapper.getSatelliteRevolution();
  704.     }

  705.     /** Override the default value short periodic terms.
  706.     *  <p>
  707.     *  By default, short periodic terms are initialized before
  708.     *  the numerical integration of the mean orbital elements.
  709.     *  </p>
  710.     *  @param shortPeriodTerms short periodic terms
  711.     */
  712.     public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
  713.         mapper.setShortPeriodTerms(shortPeriodTerms);
  714.     }

  715.    /** Get the short periodic terms.
  716.     *  @return the short periodic terms
  717.     */
  718.     public List<ShortPeriodTerms> getShortPeriodTerms() {
  719.         return mapper.getShortPeriodTerms();
  720.     }

  721.     /** {@inheritDoc} */
  722.     @Override
  723.     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
  724.         super.setAttitudeProvider(attitudeProvider);

  725.         //Register the attitude provider for each force model
  726.         for (final DSSTForceModel force : forceModels) {
  727.             force.registerAttitudeProvider(attitudeProvider);
  728.         }
  729.     }

  730.     /** Method called just before integration.
  731.      * <p>
  732.      * The default implementation does nothing, it may be specialized in subclasses.
  733.      * </p>
  734.      * @param initialState initial state
  735.      * @param tEnd target date at which state should be propagated
  736.      */
  737.     @Override
  738.     protected void beforeIntegration(final SpacecraftState initialState,
  739.                                      final AbsoluteDate tEnd) {
  740.         // If this method is updated also update DSSTStateTransitionMatrixGenerator.init(...)

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

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

  745.         // initialize all perturbing forces
  746.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<>();
  747.         for (final DSSTForceModel force : forceModels) {
  748.             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, type, force.getParameters(initialState.getDate())));
  749.         }
  750.         mapper.setShortPeriodTerms(shortPeriodTerms);

  751.         // if required, insert the special short periodics step handler
  752.         if (type == PropagationType.OSCULATING) {
  753.             final ShortPeriodicsHandler spHandler = new ShortPeriodicsHandler(forceModels);
  754.             // Compute short periodic coefficients for this point
  755.             for (DSSTForceModel forceModel : forceModels) {
  756.                 forceModel.updateShortPeriodTerms(forceModel.getParametersAllValues(), initialState);
  757.             }
  758.             final Collection<ODEStepHandler> stepHandlers = new ArrayList<>();
  759.             stepHandlers.add(spHandler);
  760.             final ODEIntegrator integrator = getIntegrator();
  761.             final Collection<ODEStepHandler> existing = integrator.getStepHandlers();
  762.             stepHandlers.addAll(existing);

  763.             integrator.clearStepHandlers();

  764.             // add back the existing handlers after the short periodics one
  765.             for (final ODEStepHandler sp : stepHandlers) {
  766.                 integrator.addStepHandler(sp);
  767.             }
  768.         }
  769.     }

  770.     /** {@inheritDoc} */
  771.     @Override
  772.     protected void afterIntegration() {
  773.         // remove the special short periodics step handler if added before
  774.         if (getPropagationType() == PropagationType.OSCULATING) {
  775.             final List<ODEStepHandler> preserved = new ArrayList<>();
  776.             final ODEIntegrator integrator = getIntegrator();
  777.             for (final ODEStepHandler sp : integrator.getStepHandlers()) {
  778.                 if (!(sp instanceof ShortPeriodicsHandler)) {
  779.                     preserved.add(sp);
  780.                 }
  781.             }

  782.             // clear the list
  783.             integrator.clearStepHandlers();

  784.             // add back the step handlers that were important for the user
  785.             for (final ODEStepHandler sp : preserved) {
  786.                 integrator.addStepHandler(sp);
  787.             }
  788.         }
  789.     }

  790.     /** Compute osculating state from mean state.
  791.      * <p>
  792.      * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
  793.      * </p>
  794.      * @param meanState initial mean state
  795.      * @param shortPeriodTerms short period terms
  796.      * @return osculating state
  797.      */
  798.     private static EquinoctialOrbit computeOsculatingOrbit(final SpacecraftState meanState,
  799.                                                            final List<ShortPeriodTerms> shortPeriodTerms) {

  800.         final double[] mean = new double[6];
  801.         final double[] meanDot = new double[6];
  802.         OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngleType.MEAN, mean, meanDot);
  803.         final double[] y = mean.clone();
  804.         for (final ShortPeriodTerms spt : shortPeriodTerms) {
  805.             final double[] shortPeriodic = spt.value(meanState.getOrbit());
  806.             for (int i = 0; i < shortPeriodic.length; i++) {
  807.                 y[i] += shortPeriodic[i];
  808.             }
  809.         }
  810.         return (EquinoctialOrbit) OrbitType.EQUINOCTIAL.mapArrayToOrbit(y, meanDot,
  811.                                                                         PositionAngleType.MEAN, meanState.getDate(),
  812.                                                                         meanState.getOrbit().getMu(), meanState.getFrame());
  813.     }

  814.     /** {@inheritDoc} */
  815.     @Override
  816.     protected SpacecraftState getInitialIntegrationState() {
  817.         if (initialIsOsculating) {
  818.             // the initial state is an osculating state,
  819.             // it must be converted to mean state
  820.             return computeMeanState(getInitialState(), getAttitudeProvider(), forceModels);
  821.         } else {
  822.             // the initial state is already a mean state
  823.             return getInitialState();
  824.         }
  825.     }

  826.     /** {@inheritDoc}
  827.      * <p>
  828.      * Note that for DSST, orbit type is hardcoded to {@link OrbitType#EQUINOCTIAL}
  829.      * and position angle type is hardcoded to {@link PositionAngleType#MEAN}, so
  830.      * the corresponding parameters are ignored.
  831.      * </p>
  832.      */
  833.     @Override
  834.     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
  835.                                        final OrbitType ignoredOrbitType, final PositionAngleType ignoredPositionAngleType,
  836.                                        final AttitudeProvider attitudeProvider, final Frame frame) {

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

  840.         // copy the specific settings from the existing mapper
  841.         if (mapper != null) {
  842.             newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
  843.             newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
  844.             newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
  845.         }

  846.         mapper = newMapper;
  847.         return mapper;

  848.     }


  849.     /** Get the short period terms value.
  850.      * @param meanState the mean state
  851.      * @return shortPeriodTerms short period terms
  852.      * @since 7.1
  853.      */
  854.     public double[] getShortPeriodTermsValue(final SpacecraftState meanState) {
  855.         final double[] sptValue = new double[6];

  856.         for (ShortPeriodTerms spt : mapper.getShortPeriodTerms()) {
  857.             final double[] shortPeriodic = spt.value(meanState.getOrbit());
  858.             for (int i = 0; i < shortPeriodic.length; i++) {
  859.                 sptValue[i] += shortPeriodic[i];
  860.             }
  861.         }
  862.         return sptValue;
  863.     }


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

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

  868.         /** Number of satellite revolutions in the averaging interval. */
  869.         private int                        satelliteRevolution;

  870.         /** Short period terms. */
  871.         private List<ShortPeriodTerms>     shortPeriodTerms;

  872.         /** Simple constructor.
  873.          * @param referenceDate reference date
  874.          * @param mu central attraction coefficient (m³/s²)
  875.          * @param attitudeProvider attitude provider
  876.          * @param frame inertial frame
  877.          */
  878.         MeanPlusShortPeriodicMapper(final AbsoluteDate referenceDate, final double mu,
  879.                                     final AttitudeProvider attitudeProvider, final Frame frame) {

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

  881.             this.selectedCoefficients = null;

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

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

  885.         }

  886.         /** {@inheritDoc} */
  887.         @Override
  888.         public SpacecraftState mapArrayToState(final AbsoluteDate date,
  889.                                                final double[] y, final double[] yDot,
  890.                                                final PropagationType type) {

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

  911.             final double mass = elements[6];
  912.             if (mass <= 0.0) {
  913.                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS, mass);
  914.             }

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

  917.             return new SpacecraftState(orbit, attitude, mass, coefficients, null);

  918.         }

  919.         /** {@inheritDoc} */
  920.         @Override
  921.         public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot) {

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

  924.         }

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

  937.         /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  938.          *  @return number of satellite revolutions to use for converting osculating to mean elements
  939.          */
  940.         public int getSatelliteRevolution() {
  941.             return satelliteRevolution;
  942.         }

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

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

  957.         /** Set the short period terms.
  958.          * @param shortPeriodTerms short period terms
  959.          * @since 7.1
  960.          */
  961.         public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
  962.             this.shortPeriodTerms = shortPeriodTerms;
  963.         }

  964.         /** Get the short period terms.
  965.          * @return shortPeriodTerms short period terms
  966.          * @since 7.1
  967.          */
  968.         public List<ShortPeriodTerms> getShortPeriodTerms() {
  969.             return shortPeriodTerms;
  970.         }

  971.     }

  972.     /** {@inheritDoc} */
  973.     @Override
  974.     protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
  975.         return new Main(integrator);
  976.     }

  977.     /** Internal class for mean parameters integration. */
  978.     private class Main implements MainStateEquations {

  979.         /** Derivatives array. */
  980.         private final double[] yDot;

  981.         /** Simple constructor.
  982.          * @param integrator numerical integrator to use for propagation.
  983.          */
  984.         Main(final ODEIntegrator integrator) {
  985.             yDot = new double[7];

  986.             // Setup event detectors from attitude provider and each force model
  987.             getAttitudeProvider().getEventDetectors().forEach(eventDetector -> setUpEventDetector(integrator, eventDetector));
  988.             forceModels.forEach(dsstForceModel -> dsstForceModel.getEventDetectors().
  989.                                 forEach(eventDetector -> setUpEventDetector(integrator, eventDetector)));
  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(state.getDate()));
  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 at state date (only 1 value for
  1017.          *  each parameter
  1018.          *  @return the mean equinoctial elements rates da<sub>i</sub> / dt
  1019.          */
  1020.         private double[] elementRates(final DSSTForceModel forceModel,
  1021.                                       final SpacecraftState state,
  1022.                                       final AuxiliaryElements auxiliaryElements,
  1023.                                       final double[] parameters) {
  1024.             return forceModel.getMeanElementRate(state, auxiliaryElements, parameters);
  1025.         }

  1026.     }

  1027.     /** Step handler used to compute the parameters for the short periodic contributions.
  1028.      * @author Lucian Barbulescu
  1029.      */
  1030.     private class ShortPeriodicsHandler implements ODEStepHandler {

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

  1033.         /** Constructor.
  1034.          * @param forceModels force models
  1035.          */
  1036.         ShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
  1037.             this.forceModels = forceModels;
  1038.         }

  1039.         /** {@inheritDoc} */
  1040.         @Override
  1041.         public void handleStep(final ODEStateInterpolator interpolator) {

  1042.             // Get the grid points to compute
  1043.             final double[] interpolationPoints =
  1044.                     interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
  1045.                                                     interpolator.getCurrentState().getTime());

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

  1048.                 // Build the mean state interpolated at grid point
  1049.                 final double time = interpolationPoints[i];
  1050.                 final ODEStateAndDerivative sd = interpolator.getInterpolatedState(time);
  1051.                 meanStates[i] = mapper.mapArrayToState(time,
  1052.                                                        sd.getPrimaryState(),
  1053.                                                        sd.getPrimaryDerivative(),
  1054.                                                        PropagationType.MEAN);
  1055.             }

  1056.             // Compute short periodic coefficients for this step
  1057.             for (DSSTForceModel forceModel : forceModels) {
  1058.                 forceModel.updateShortPeriodTerms(forceModel.getParametersAllValues(), meanStates);
  1059.             }
  1060.         }
  1061.     }
  1062. }