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.CartesianToleranceProvider;
  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.ToleranceProvider;
  47. import org.orekit.propagation.conversion.osc2mean.DSSTTheory;
  48. import org.orekit.propagation.conversion.osc2mean.FixedPointConverter;
  49. import org.orekit.propagation.conversion.osc2mean.MeanTheory;
  50. import org.orekit.propagation.conversion.osc2mean.OsculatingToMeanConverter;
  51. import org.orekit.propagation.integration.AbstractIntegratedPropagator;
  52. import org.orekit.propagation.integration.AdditionalDerivativesProvider;
  53. import org.orekit.propagation.integration.StateMapper;
  54. import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
  55. import org.orekit.propagation.semianalytical.dsst.forces.DSSTNewtonianAttraction;
  56. import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
  57. import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
  58. import org.orekit.propagation.semianalytical.dsst.utilities.FixedNumberInterpolationGrid;
  59. import org.orekit.propagation.semianalytical.dsst.utilities.InterpolationGrid;
  60. import org.orekit.propagation.semianalytical.dsst.utilities.MaxGapInterpolationGrid;
  61. import org.orekit.time.AbsoluteDate;
  62. import org.orekit.utils.DataDictionary;
  63. import org.orekit.utils.DoubleArrayDictionary;
  64. import org.orekit.utils.PVCoordinates;
  65. import org.orekit.utils.ParameterDriver;
  66. import org.orekit.utils.ParameterDriversList;
  67. import org.orekit.utils.ParameterDriversList.DelegatingDriver;
  68. import org.orekit.utils.ParameterObserver;
  69. import org.orekit.utils.TimeSpanMap;
  70. import org.orekit.utils.TimeSpanMap.Span;

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

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

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

  145.     /** Default value for maxIterations. */
  146.     private static final int MAX_ITERATIONS_DEFAULT = 200;

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

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

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

  153.     /** State mapper holding the force models. */
  154.     private MeanPlusShortPeriodicMapper mapper;

  155.     /** Generator for the interpolation grid. */
  156.     private InterpolationGrid interpolationgrid;

  157.     /**
  158.      * Same as {@link org.orekit.propagation.AbstractPropagator#harvester} but with the
  159.      * more specific type. Saved to avoid a cast.
  160.      */
  161.     private DSSTHarvester harvester;

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  353.     /** {@inheritDoc} */
  354.     @Override
  355.     protected void setUpStmAndJacobianGenerators() {

  356.         final DSSTHarvester dsstHarvester = getHarvester();
  357.         if (dsstHarvester != null) {

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

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

  364.         }

  365.     }

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

  371.         final DSSTHarvester dsstHarvester = getHarvester();

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

  390.         if (!getInitialIntegrationState().hasAdditionalData(dsstHarvester.getStmName())) {
  391.             // add the initial State Transition Matrix if it is not already there
  392.             // (perhaps due to a previous propagation)
  393.             setInitialState(stmGenerator.setInitialStateTransitionMatrix(getInitialState(),
  394.                                                                          dsstHarvester.getInitialStateTransitionMatrix()),
  395.                             getPropagationType());
  396.         }

  397.         return stmGenerator;

  398.     }

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

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

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

  414.         // third pass: sort parameters lexicographically
  415.         selected.sort();

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

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

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

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

  434.                 if (!getInitialIntegrationState().hasAdditionalData(span.getData())) {
  435.                     // add the initial Jacobian column if it is not already there
  436.                     // (perhaps due to a previous propagation)
  437.                     setInitialState(getInitialState().addAdditionalData(span.getData(),
  438.                                                                          getHarvester().getInitialJacobianColumn(span.getData())),
  439.                                     getPropagationType());
  440.                 }
  441.             }

  442.         }

  443.     }

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

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

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

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

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

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

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

  530.         force.registerAttitudeProvider(getAttitudeProvider());

  531.     }

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

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

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

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

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

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

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

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

  603.         return new SpacecraftState(osculatingOrbit, mean.getAttitude(), mean.getMass(),
  604.                                    mean.getAdditionalDataValues(), mean.getAdditionalStatesDerivatives());

  605.     }

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

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

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

  685.         final MeanTheory theory = new DSSTTheory(forceModels, attitudeProvider, osculating.getMass());
  686.         converter.setMeanTheory(theory);
  687.         final Orbit meanOrbit = converter.convertToMean(osculating.getOrbit());
  688.         return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(),
  689.                                    osculating.getAdditionalDataValues(), osculating.getAdditionalStatesDerivatives());
  690.     }

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

  703.     /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  704.      *  @return number of satellite revolutions to use for converting osculating to mean elements
  705.      */
  706.     public int getSatelliteRevolution() {
  707.         return mapper.getSatelliteRevolution();
  708.     }

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

  719.    /** Get the short periodic terms.
  720.     *  @return the short periodic terms
  721.     */
  722.     public List<ShortPeriodTerms> getShortPeriodTerms() {
  723.         return mapper.getShortPeriodTerms();
  724.     }

  725.     /** {@inheritDoc} */
  726.     @Override
  727.     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
  728.         super.setAttitudeProvider(attitudeProvider);

  729.         //Register the attitude provider for each force model
  730.         for (final DSSTForceModel force : forceModels) {
  731.             force.registerAttitudeProvider(attitudeProvider);
  732.         }
  733.     }

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

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

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

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

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

  767.             integrator.clearStepHandlers();

  768.             // add back the existing handlers after the short periodics one
  769.             for (final ODEStepHandler sp : stepHandlers) {
  770.                 integrator.addStepHandler(sp);
  771.             }
  772.         }
  773.     }

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

  786.             // clear the list
  787.             integrator.clearStepHandlers();

  788.             // add back the step handlers that were important for the user
  789.             for (final ODEStepHandler sp : preserved) {
  790.                 integrator.addStepHandler(sp);
  791.             }
  792.         }
  793.     }

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

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

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

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

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

  844.         // copy the specific settings from the existing mapper
  845.         if (mapper != null) {
  846.             newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
  847.             newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
  848.             newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
  849.         }

  850.         mapper = newMapper;
  851.         return mapper;

  852.     }


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

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


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

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

  872.         /** Number of satellite revolutions in the averaging interval. */
  873.         private int                        satelliteRevolution;

  874.         /** Short period terms. */
  875.         private List<ShortPeriodTerms>     shortPeriodTerms;

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

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

  885.             this.selectedCoefficients = null;

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

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

  889.         }

  890.         /** {@inheritDoc} */
  891.         @Override
  892.         public SpacecraftState mapArrayToState(final AbsoluteDate date,
  893.                                                final double[] y, final double[] yDot,
  894.                                                final PropagationType type) {

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

  915.             final double mass = elements[6];
  916.             if (mass <= 0.0) {
  917.                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS, mass);
  918.             }

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

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

  922.         }

  923.         /** {@inheritDoc} */
  924.         @Override
  925.         public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot) {

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

  928.         }

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

  941.         /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  942.          *  @return number of satellite revolutions to use for converting osculating to mean elements
  943.          */
  944.         public int getSatelliteRevolution() {
  945.             return satelliteRevolution;
  946.         }

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

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

  961.         /** Set the short period terms.
  962.          * @param shortPeriodTerms short period terms
  963.          * @since 7.1
  964.          */
  965.         public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
  966.             this.shortPeriodTerms = shortPeriodTerms;
  967.         }

  968.         /** Get the short period terms.
  969.          * @return shortPeriodTerms short period terms
  970.          * @since 7.1
  971.          */
  972.         public List<ShortPeriodTerms> getShortPeriodTerms() {
  973.             return shortPeriodTerms;
  974.         }

  975.     }

  976.     /** {@inheritDoc} */
  977.     @Override
  978.     protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
  979.         return new Main(integrator);
  980.     }

  981.     /** Internal class for mean parameters integration. */
  982.     private class Main implements MainStateEquations {

  983.         /** Derivatives array. */
  984.         private final double[] yDot;

  985.         /** Simple constructor.
  986.          * @param integrator numerical integrator to use for propagation.
  987.          */
  988.         Main(final ODEIntegrator integrator) {
  989.             yDot = new double[7];

  990.             // Setup event detectors for each force model
  991.             forceModels.forEach(dsstForceModel -> dsstForceModel.getEventDetectors().
  992.                                 forEach(eventDetector -> setUpEventDetector(integrator, eventDetector)));
  993.         }

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

  999.         /** {@inheritDoc} */
  1000.         @Override
  1001.         public double[] computeDerivatives(final SpacecraftState state) {

  1002.             Arrays.fill(yDot, 0.0);

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

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

  1012.             return yDot.clone();
  1013.         }

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

  1029.     }

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

  1064.         return DSSTPropagator.tolerances(dP, dV, orbit);

  1065.     }

  1066.     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
  1067.      *  <p>
  1068.      *  The errors are estimated from partial derivatives properties of orbits,
  1069.      *  starting from scalar position and velocity errors specified by the user.
  1070.      *  <p>
  1071.      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
  1072.      *  local estimates, not global ones. So some care must be taken when using these tolerances.
  1073.      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
  1074.      *  position after several orbits integration.
  1075.      *  </p>
  1076.      *
  1077.      * @param dP user specified position error (m)
  1078.      * @param dV user specified velocity error (m/s)
  1079.      * @param orbit reference orbit
  1080.      * @return a two rows array, row 0 being the absolute tolerance error
  1081.      *                       and row 1 being the relative tolerance error
  1082.      * @since 10.3
  1083.      * @deprecated since 13.0. Use {@link ToleranceProvider} for default and custom tolerances.
  1084.      */
  1085.     @Deprecated
  1086.     public static double[][] tolerances(final double dP, final double dV, final Orbit orbit) {

  1087.         return ToleranceProvider.of(CartesianToleranceProvider.of(dP, dV, CartesianToleranceProvider.DEFAULT_ABSOLUTE_MASS_TOLERANCE))
  1088.             .getTolerances(orbit, OrbitType.EQUINOCTIAL, PositionAngleType.MEAN);
  1089.     }

  1090.     /** Step handler used to compute the parameters for the short periodic contributions.
  1091.      * @author Lucian Barbulescu
  1092.      */
  1093.     private class ShortPeriodicsHandler implements ODEStepHandler {

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

  1096.         /** Constructor.
  1097.          * @param forceModels force models
  1098.          */
  1099.         ShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
  1100.             this.forceModels = forceModels;
  1101.         }

  1102.         /** {@inheritDoc} */
  1103.         @Override
  1104.         public void handleStep(final ODEStateInterpolator interpolator) {

  1105.             // Get the grid points to compute
  1106.             final double[] interpolationPoints =
  1107.                     interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
  1108.                                                     interpolator.getCurrentState().getTime());

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

  1111.                 // Build the mean state interpolated at grid point
  1112.                 final double time = interpolationPoints[i];
  1113.                 final ODEStateAndDerivative sd = interpolator.getInterpolatedState(time);
  1114.                 meanStates[i] = mapper.mapArrayToState(time,
  1115.                                                        sd.getPrimaryState(),
  1116.                                                        sd.getPrimaryDerivative(),
  1117.                                                        PropagationType.MEAN);
  1118.             }

  1119.             // Compute short periodic coefficients for this step
  1120.             for (DSSTForceModel forceModel : forceModels) {
  1121.                 forceModel.updateShortPeriodTerms(forceModel.getParametersAllValues(), meanStates);
  1122.             }
  1123.         }
  1124.     }
  1125. }