DSSTPropagator.java

  1. /* Copyright 2002-2020 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.HashMap;
  23. import java.util.HashSet;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

  247.     /** Set the initial state.
  248.      *  @param initialState initial state
  249.      *  @param stateType defined if the orbital state is defined with osculating or mean elements
  250.      */
  251.     public void setInitialState(final SpacecraftState initialState,
  252.                                 final PropagationType stateType) {
  253.         switch (stateType) {
  254.             case MEAN:
  255.                 initialIsOsculating = false;
  256.                 break;
  257.             case OSCULATING:
  258.                 initialIsOsculating = true;
  259.                 break;
  260.             default:
  261.                 throw new OrekitInternalError(null);
  262.         }
  263.         resetInitialState(initialState);
  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.getMu());
  275.         }
  276.         super.setStartDate(state.getDate());
  277.     }

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

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

  294.     /** Check if the initial state is provided in osculating elements.
  295.      * @return true if initial state is provided in osculating elements
  296.      */
  297.     public boolean initialIsOsculating() {
  298.         return initialIsOsculating;
  299.     }

  300.     /** Set the interpolation grid generator.
  301.      * <p>
  302.      * The generator will create an interpolation grid with a fixed
  303.      * number of points for each mean element integration step.
  304.      * </p>
  305.      * <p>
  306.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  307.      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
  308.      * by default the propagator is set as to 3 interpolations points per step.
  309.      * </p>
  310.      * @param interpolationPoints number of interpolation points at
  311.      * each integration step
  312.      * @see #setInterpolationGridToMaxTimeGap(double)
  313.      * @since 7.1
  314.      */
  315.     public void setInterpolationGridToFixedNumberOfPoints(final int interpolationPoints) {
  316.         interpolationgrid = new FixedNumberInterpolationGrid(interpolationPoints);
  317.     }

  318.     /** Set the interpolation grid generator.
  319.      * <p>
  320.      * The generator will create an interpolation grid with a maximum
  321.      * time gap between interpolation points.
  322.      * </p>
  323.      * <p>
  324.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  325.      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
  326.      * by default the propagator is set as to 3 interpolations points per step.
  327.      * </p>
  328.      * @param maxGap maximum time gap between interpolation points (seconds)
  329.      * @see #setInterpolationGridToFixedNumberOfPoints(int)
  330.      * @since 7.1
  331.      */
  332.     public void setInterpolationGridToMaxTimeGap(final double maxGap) {
  333.         interpolationgrid = new MaxGapInterpolationGrid(maxGap);
  334.     }

  335.     /** Add a force model to the global perturbation model.
  336.      *  <p>
  337.      *  If this method is not called at all,
  338.      *  the integrated orbit will follow a Keplerian evolution only.
  339.      *  </p>
  340.      *  @param force perturbing {@link DSSTForceModel force} to add
  341.      *  @see #removeForceModels()
  342.      *  @see #setMu(double)
  343.      */
  344.     public void addForceModel(final DSSTForceModel force) {

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

  347.             // ensure we are notified of any mu change
  348.             force.getParametersDrivers()[0].addObserver(new ParameterObserver() {
  349.                 /** {@inheritDoc} */
  350.                 @Override
  351.                 public void valueChanged(final double previousValue, final ParameterDriver driver) {
  352.                     superSetMu(driver.getValue());
  353.                 }
  354.             });

  355.             if (hasNewtonianAttraction()) {
  356.                 // there is already a central attraction model, replace it
  357.                 forceModels.set(forceModels.size() - 1, force);
  358.             } else {
  359.                 // there are no central attraction model yet, add it at the end of the list
  360.                 forceModels.add(force);
  361.             }
  362.         } else {
  363.             // we want to add a perturbing force model
  364.             if (hasNewtonianAttraction()) {
  365.                 // insert the new force model before Newtonian attraction,
  366.                 // which should always be the last one in the list
  367.                 forceModels.add(forceModels.size() - 1, force);
  368.             } else {
  369.                 // we only have perturbing force models up to now, just append at the end of the list
  370.                 forceModels.add(force);
  371.             }
  372.         }

  373.         force.registerAttitudeProvider(getAttitudeProvider());

  374.     }

  375.     /** Remove all perturbing force models from the global perturbation model
  376.      *  (except central attraction).
  377.      *  <p>
  378.      *  Once all perturbing forces have been removed (and as long as no new force model is added),
  379.      *  the integrated orbit will follow a Keplerian evolution only.
  380.      *  </p>
  381.      *  @see #addForceModel(DSSTForceModel)
  382.      */
  383.     public void removeForceModels() {
  384.         final int last = forceModels.size() - 1;
  385.         if (hasNewtonianAttraction()) {
  386.             // preserve the Newtonian attraction model at the end
  387.             final DSSTForceModel newton = forceModels.get(last);
  388.             forceModels.clear();
  389.             forceModels.add(newton);
  390.         } else {
  391.             forceModels.clear();
  392.         }
  393.     }

  394.     /** Get all the force models, perturbing forces and Newtonian attraction included.
  395.      * @return list of perturbing force models, with Newtonian attraction being the
  396.      * last one
  397.      * @see #addForceModel(DSSTForceModel)
  398.      * @see #setMu(double)
  399.      */
  400.     public List<DSSTForceModel> getAllForceModels() {
  401.         return Collections.unmodifiableList(forceModels);
  402.     }

  403.     /** Get propagation parameter type.
  404.      * @return orbit type used for propagation
  405.      */
  406.     public OrbitType getOrbitType() {
  407.         return super.getOrbitType();
  408.     }

  409.     /** Get propagation parameter type.
  410.      * @return angle type to use for propagation
  411.      */
  412.     public PositionAngle getPositionAngleType() {
  413.         return super.getPositionAngleType();
  414.     }

  415.     /** Conversion from mean to osculating orbit.
  416.      * <p>
  417.      * Compute osculating state <b>in a DSST sense</b>, corresponding to the
  418.      * mean SpacecraftState in input, and according to the Force models taken
  419.      * into account.
  420.      * </p><p>
  421.      * Since the osculating state is obtained by adding short-periodic variation
  422.      * of each force model, the resulting output will depend on the
  423.      * force models parameterized in input.
  424.      * </p>
  425.      * @param mean Mean state to convert
  426.      * @param forces Forces to take into account
  427.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  428.      * like atmospheric drag, radiation pressure or specific user-defined models)
  429.      * @return osculating state in a DSST sense
  430.      */
  431.     public static SpacecraftState computeOsculatingState(final SpacecraftState mean,
  432.                                                          final AttitudeProvider attitudeProvider,
  433.                                                          final Collection<DSSTForceModel> forces) {

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

  436.         // Set the force models
  437.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
  438.         for (final DSSTForceModel force : forces) {
  439.             force.registerAttitudeProvider(attitudeProvider);
  440.             shortPeriodTerms.addAll(force.initialize(aux, PropagationType.OSCULATING, force.getParameters()));
  441.             force.updateShortPeriodTerms(force.getParameters(), mean);
  442.         }

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

  444.         return new SpacecraftState(osculatingOrbit, mean.getAttitude(), mean.getMass(),
  445.                                    mean.getAdditionalStates());

  446.     }

  447.     /** Conversion from osculating to mean orbit.
  448.      * <p>
  449.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  450.      * osculating SpacecraftState in input, and according to the Force models
  451.      * taken into account.
  452.      * </p><p>
  453.      * Since the osculating state is obtained with the computation of
  454.      * short-periodic variation of each force model, the resulting output will
  455.      * depend on the force models parameterized in input.
  456.      * </p><p>
  457.      * The computation is done through a fixed-point iteration process.
  458.      * </p>
  459.      * @param osculating Osculating state to convert
  460.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  461.      * like atmospheric drag, radiation pressure or specific user-defined models)
  462.      * @param forceModels Forces to take into account
  463.      * @return mean state in a DSST sense
  464.      */
  465.     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
  466.                                                    final AttitudeProvider attitudeProvider,
  467.                                                    final Collection<DSSTForceModel> forceModels) {
  468.         return computeMeanState(osculating, attitudeProvider, forceModels, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  469.     }

  470.     /** Conversion from osculating to mean orbit.
  471.      * <p>
  472.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  473.      * osculating SpacecraftState in input, and according to the Force models
  474.      * taken into account.
  475.      * </p><p>
  476.      * Since the osculating state is obtained with the computation of
  477.      * short-periodic variation of each force model, the resulting output will
  478.      * depend on the force models parameterized in input.
  479.      * </p><p>
  480.      * The computation is done through a fixed-point iteration process.
  481.      * </p>
  482.      * @param osculating Osculating state to convert
  483.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  484.      * like atmospheric drag, radiation pressure or specific user-defined models)
  485.      * @param forceModels Forces to take into account
  486.      * @param epsilon convergence threshold for mean parameters conversion
  487.      * @param maxIterations maximum iterations for mean parameters conversion
  488.      * @return mean state in a DSST sense
  489.      * @since 10.1
  490.      */
  491.     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
  492.                                                    final AttitudeProvider attitudeProvider,
  493.                                                    final Collection<DSSTForceModel> forceModels,
  494.                                                    final double epsilon,
  495.                                                    final int maxIterations) {
  496.         final Orbit meanOrbit = computeMeanOrbit(osculating, attitudeProvider, forceModels, epsilon, maxIterations);
  497.         return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(), osculating.getAdditionalStates());
  498.     }

  499.      /** Override the default value of the parameter.
  500.      *  <p>
  501.      *  By default, if the initial orbit is defined as osculating,
  502.      *  it will be averaged over 2 satellite revolutions.
  503.      *  This can be changed by using this method.
  504.      *  </p>
  505.      *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
  506.      *                             elements
  507.      */
  508.     public void setSatelliteRevolution(final int satelliteRevolution) {
  509.         mapper.setSatelliteRevolution(satelliteRevolution);
  510.     }

  511.     /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  512.      *  @return number of satellite revolutions to use for converting osculating to mean elements
  513.      */
  514.     public int getSatelliteRevolution() {
  515.         return mapper.getSatelliteRevolution();
  516.     }

  517.     /** {@inheritDoc} */
  518.     @Override
  519.     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
  520.         super.setAttitudeProvider(attitudeProvider);

  521.         //Register the attitude provider for each force model
  522.         for (final DSSTForceModel force : forceModels) {
  523.             force.registerAttitudeProvider(attitudeProvider);
  524.         }
  525.     }

  526.     /** Method called just before integration.
  527.      * <p>
  528.      * The default implementation does nothing, it may be specialized in subclasses.
  529.      * </p>
  530.      * @param initialState initial state
  531.      * @param tEnd target date at which state should be propagated
  532.      */
  533.     @Override
  534.     protected void beforeIntegration(final SpacecraftState initialState,
  535.                                      final AbsoluteDate tEnd) {

  536.         // check if only mean elements must be used
  537.         final PropagationType type = isMeanOrbit();

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

  540.         // initialize all perturbing forces
  541.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
  542.         for (final DSSTForceModel force : forceModels) {
  543.             shortPeriodTerms.addAll(force.initialize(aux, type, force.getParameters()));
  544.         }
  545.         mapper.setShortPeriodTerms(shortPeriodTerms);

  546.         // if required, insert the special short periodics step handler
  547.         if (type == PropagationType.OSCULATING) {
  548.             final ShortPeriodicsHandler spHandler = new ShortPeriodicsHandler(forceModels);
  549.             // Compute short periodic coefficients for this point
  550.             for (DSSTForceModel forceModel : forceModels) {
  551.                 forceModel.updateShortPeriodTerms(forceModel.getParameters(), initialState);
  552.             }
  553.             final Collection<ODEStepHandler> stepHandlers = new ArrayList<ODEStepHandler>();
  554.             stepHandlers.add(spHandler);
  555.             final ODEIntegrator integrator = getIntegrator();
  556.             final Collection<ODEStepHandler> existing = integrator.getStepHandlers();
  557.             stepHandlers.addAll(existing);

  558.             integrator.clearStepHandlers();

  559.             // add back the existing handlers after the short periodics one
  560.             for (final ODEStepHandler sp : stepHandlers) {
  561.                 integrator.addStepHandler(sp);
  562.             }
  563.         }
  564.     }

  565.     /** {@inheritDoc} */
  566.     @Override
  567.     protected void afterIntegration() {
  568.         // remove the special short periodics step handler if added before
  569.         if (isMeanOrbit() == PropagationType.OSCULATING) {
  570.             final List<ODEStepHandler> preserved = new ArrayList<ODEStepHandler>();
  571.             final ODEIntegrator integrator = getIntegrator();
  572.             for (final ODEStepHandler sp : integrator.getStepHandlers()) {
  573.                 if (!(sp instanceof ShortPeriodicsHandler)) {
  574.                     preserved.add(sp);
  575.                 }
  576.             }

  577.             // clear the list
  578.             integrator.clearStepHandlers();

  579.             // add back the step handlers that were important for the user
  580.             for (final ODEStepHandler sp : preserved) {
  581.                 integrator.addStepHandler(sp);
  582.             }
  583.         }
  584.     }

  585.     /** Compute mean state from osculating state.
  586.      * <p>
  587.      * Compute in a DSST sense the mean state corresponding to the input osculating state.
  588.      * </p><p>
  589.      * The computing is done through a fixed-point iteration process.
  590.      * </p>
  591.      * @param osculating initial osculating state
  592.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  593.      * like atmospheric drag, radiation pressure or specific user-defined models)
  594.      * @param forceModels force models
  595.      * @param epsilon convergence threshold for mean parameters conversion
  596.      * @param maxIterations maximum iterations for mean parameters conversion
  597.      * @return mean state
  598.      */
  599.     private static Orbit computeMeanOrbit(final SpacecraftState osculating,
  600.                                           final AttitudeProvider attitudeProvider,
  601.                                           final Collection<DSSTForceModel> forceModels, final double epsilon, final int maxIterations) {

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

  604.         // threshold for each parameter
  605.         final double thresholdA = epsilon * (1 + FastMath.abs(meanOrbit.getA()));
  606.         final double thresholdE = epsilon * (1 + meanOrbit.getE());
  607.         final double thresholdI = epsilon * (1 + meanOrbit.getI());
  608.         final double thresholdL = epsilon * FastMath.PI;

  609.         // ensure all Gaussian force models can rely on attitude
  610.         for (final DSSTForceModel force : forceModels) {
  611.             force.registerAttitudeProvider(attitudeProvider);
  612.         }

  613.         int i = 0;
  614.         while (i++ < maxIterations) {

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

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

  618.             // Set the force models
  619.             final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
  620.             for (final DSSTForceModel force : forceModels) {
  621.                 shortPeriodTerms.addAll(force.initialize(aux, PropagationType.OSCULATING, force.getParameters()));
  622.                 force.updateShortPeriodTerms(force.getParameters(), meanState);
  623.             }

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

  626.             // adapted parameters residuals
  627.             final double deltaA  = osculating.getA() - rebuilt.getA();
  628.             final double deltaEx = osculating.getEquinoctialEx() - rebuilt.getEquinoctialEx();
  629.             final double deltaEy = osculating.getEquinoctialEy() - rebuilt.getEquinoctialEy();
  630.             final double deltaHx = osculating.getHx() - rebuilt.getHx();
  631.             final double deltaHy = osculating.getHy() - rebuilt.getHy();
  632.             final double deltaLv = MathUtils.normalizeAngle(osculating.getLv() - rebuilt.getLv(), 0.0);

  633.             // check convergence
  634.             if (FastMath.abs(deltaA)  < thresholdA &&
  635.                 FastMath.abs(deltaEx) < thresholdE &&
  636.                 FastMath.abs(deltaEy) < thresholdE &&
  637.                 FastMath.abs(deltaHx) < thresholdI &&
  638.                 FastMath.abs(deltaHy) < thresholdI &&
  639.                 FastMath.abs(deltaLv) < thresholdL) {
  640.                 return meanOrbit;
  641.             }

  642.             // update mean parameters
  643.             meanOrbit = new EquinoctialOrbit(meanOrbit.getA() + deltaA,
  644.                                              meanOrbit.getEquinoctialEx() + deltaEx,
  645.                                              meanOrbit.getEquinoctialEy() + deltaEy,
  646.                                              meanOrbit.getHx() + deltaHx,
  647.                                              meanOrbit.getHy() + deltaHy,
  648.                                              meanOrbit.getLv() + deltaLv,
  649.                                              PositionAngle.TRUE, meanOrbit.getFrame(),
  650.                                              meanOrbit.getDate(), meanOrbit.getMu());
  651.         }

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

  653.     }

  654.     /** Compute osculating state from mean state.
  655.      * <p>
  656.      * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
  657.      * </p>
  658.      * @param meanState initial mean state
  659.      * @param shortPeriodTerms short period terms
  660.      * @return osculating state
  661.      */
  662.     private static EquinoctialOrbit computeOsculatingOrbit(final SpacecraftState meanState,
  663.                                                            final List<ShortPeriodTerms> shortPeriodTerms) {

  664.         final double[] mean = new double[6];
  665.         final double[] meanDot = new double[6];
  666.         OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngle.MEAN, mean, meanDot);
  667.         final double[] y = mean.clone();
  668.         for (final ShortPeriodTerms spt : shortPeriodTerms) {
  669.             final double[] shortPeriodic = spt.value(meanState.getOrbit());
  670.             for (int i = 0; i < shortPeriodic.length; i++) {
  671.                 y[i] += shortPeriodic[i];
  672.             }
  673.         }
  674.         return (EquinoctialOrbit) OrbitType.EQUINOCTIAL.mapArrayToOrbit(y, meanDot,
  675.                                                                         PositionAngle.MEAN, meanState.getDate(),
  676.                                                                         meanState.getMu(), meanState.getFrame());
  677.     }

  678.     /** {@inheritDoc} */
  679.     @Override
  680.     protected SpacecraftState getInitialIntegrationState() {
  681.         if (initialIsOsculating) {
  682.             // the initial state is an osculating state,
  683.             // it must be converted to mean state
  684.             return computeMeanState(getInitialState(), getAttitudeProvider(), forceModels);
  685.         } else {
  686.             // the initial state is already a mean state
  687.             return getInitialState();
  688.         }
  689.     }

  690.     /** {@inheritDoc}
  691.      * <p>
  692.      * Note that for DSST, orbit type is hardcoded to {@link OrbitType#EQUINOCTIAL}
  693.      * and position angle type is hardcoded to {@link PositionAngle#MEAN}, so
  694.      * the corresponding parameters are ignored.
  695.      * </p>
  696.      */
  697.     @Override
  698.     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
  699.                                        final OrbitType ignoredOrbitType, final PositionAngle ignoredPositionAngleType,
  700.                                        final AttitudeProvider attitudeProvider, final Frame frame) {

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

  704.         // copy the specific settings from the existing mapper
  705.         if (mapper != null) {
  706.             newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
  707.             newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
  708.             newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
  709.         }

  710.         mapper = newMapper;
  711.         return mapper;

  712.     }

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

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

  717.         /** Number of satellite revolutions in the averaging interval. */
  718.         private int                        satelliteRevolution;

  719.         /** Short period terms. */
  720.         private List<ShortPeriodTerms>     shortPeriodTerms;

  721.         /** Simple constructor.
  722.          * @param referenceDate reference date
  723.          * @param mu central attraction coefficient (m³/s²)
  724.          * @param attitudeProvider attitude provider
  725.          * @param frame inertial frame
  726.          */
  727.         MeanPlusShortPeriodicMapper(final AbsoluteDate referenceDate, final double mu,
  728.                                     final AttitudeProvider attitudeProvider, final Frame frame) {

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

  730.             this.selectedCoefficients = null;

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

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

  734.         }

  735.         /** {@inheritDoc} */
  736.         @Override
  737.         public SpacecraftState mapArrayToState(final AbsoluteDate date,
  738.                                                final double[] y, final double[] yDot,
  739.                                                final PropagationType type) {

  740.             // add short periodic variations to mean elements to get osculating elements
  741.             // (the loop may not be performed if there are no force models and in the
  742.             //  case we want to remain in mean parameters only)
  743.             final double[] elements = y.clone();
  744.             final Map<String, double[]> coefficients;
  745.             switch (type) {
  746.                 case MEAN:
  747.                     coefficients = null;
  748.                     break;
  749.                 case OSCULATING:
  750.                     final Orbit meanOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngle.MEAN, date, getMu(), getFrame());
  751.                     coefficients = selectedCoefficients == null ? null : new HashMap<String, double[]>();
  752.                     for (final ShortPeriodTerms spt : shortPeriodTerms) {
  753.                         final double[] shortPeriodic = spt.value(meanOrbit);
  754.                         for (int i = 0; i < shortPeriodic.length; i++) {
  755.                             elements[i] += shortPeriodic[i];
  756.                         }
  757.                         if (selectedCoefficients != null) {
  758.                             coefficients.putAll(spt.getCoefficients(date, selectedCoefficients));
  759.                         }
  760.                     }
  761.                     break;
  762.                 default:
  763.                     throw new OrekitInternalError(null);
  764.             }

  765.             final double mass = elements[6];
  766.             if (mass <= 0.0) {
  767.                 throw new OrekitException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, mass);
  768.             }

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

  771.             if (coefficients == null) {
  772.                 return new SpacecraftState(orbit, attitude, mass);
  773.             } else {
  774.                 return new SpacecraftState(orbit, attitude, mass, coefficients);
  775.             }

  776.         }

  777.         /** {@inheritDoc} */
  778.         @Override
  779.         public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot) {

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

  782.         }

  783.         /** Set the number of satellite revolutions to use for converting osculating to mean elements.
  784.          *  <p>
  785.          *  By default, if the initial orbit is defined as osculating,
  786.          *  it will be averaged over 2 satellite revolutions.
  787.          *  This can be changed by using this method.
  788.          *  </p>
  789.          *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
  790.          *                             elements
  791.          */
  792.         public void setSatelliteRevolution(final int satelliteRevolution) {
  793.             this.satelliteRevolution = satelliteRevolution;
  794.         }

  795.         /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  796.          *  @return number of satellite revolutions to use for converting osculating to mean elements
  797.          */
  798.         public int getSatelliteRevolution() {
  799.             return satelliteRevolution;
  800.         }

  801.         /** Set the selected short periodic coefficients that must be stored as additional states.
  802.          * @param selectedCoefficients short periodic coefficients that must be stored as additional states
  803.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  804.          */
  805.         public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
  806.             this.selectedCoefficients = selectedCoefficients;
  807.         }

  808.         /** Get the selected short periodic coefficients that must be stored as additional states.
  809.          * @return short periodic coefficients that must be stored as additional states
  810.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  811.          */
  812.         public Set<String> getSelectedCoefficients() {
  813.             return selectedCoefficients;
  814.         }

  815.         /** Set the short period terms.
  816.          * @param shortPeriodTerms short period terms
  817.          * @since 7.1
  818.          */
  819.         public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
  820.             this.shortPeriodTerms = shortPeriodTerms;
  821.         }

  822.         /** Get the short period terms.
  823.          * @return shortPeriodTerms short period terms
  824.          * @since 7.1
  825.          */
  826.         public List<ShortPeriodTerms> getShortPeriodTerms() {
  827.             return shortPeriodTerms;
  828.         }

  829.     }

  830.     /** {@inheritDoc} */
  831.     @Override
  832.     protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
  833.         return new Main(integrator);
  834.     }

  835.     /** Internal class for mean parameters integration. */
  836.     private class Main implements MainStateEquations {

  837.         /** Derivatives array. */
  838.         private final double[] yDot;

  839.         /** Simple constructor.
  840.          * @param integrator numerical integrator to use for propagation.
  841.          */
  842.         Main(final ODEIntegrator integrator) {
  843.             yDot = new double[7];

  844.             for (final DSSTForceModel forceModel : forceModels) {
  845.                 final EventDetector[] modelDetectors = forceModel.getEventsDetectors();
  846.                 if (modelDetectors != null) {
  847.                     for (final EventDetector detector : modelDetectors) {
  848.                         setUpEventDetector(integrator, detector);
  849.                     }
  850.                 }
  851.             }

  852.         }

  853.         /** {@inheritDoc} */
  854.         @Override
  855.         public double[] computeDerivatives(final SpacecraftState state) {

  856.             Arrays.fill(yDot, 0.0);

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

  859.             // compute the contributions of all perturbing forces
  860.             for (final DSSTForceModel forceModel : forceModels) {
  861.                 final double[] daidt = elementRates(forceModel, state, auxiliaryElements, forceModel.getParameters());
  862.                 for (int i = 0; i < daidt.length; i++) {
  863.                     yDot[i] += daidt[i];
  864.                 }
  865.             }

  866.             return yDot.clone();
  867.         }

  868.         /** This method allows to compute the mean equinoctial elements rates da<sub>i</sub> / dt
  869.          *  for a specific force model.
  870.          *  @param forceModel force to take into account
  871.          *  @param state current state
  872.          *  @param auxiliaryElements auxiliary elements related to the current orbit
  873.          *  @param parameters force model parameters
  874.          *  @return the mean equinoctial elements rates da<sub>i</sub> / dt
  875.          */
  876.         private double[] elementRates(final DSSTForceModel forceModel,
  877.                                       final SpacecraftState state,
  878.                                       final AuxiliaryElements auxiliaryElements,
  879.                                       final double[] parameters) {
  880.             return forceModel.getMeanElementRate(state, auxiliaryElements, parameters);
  881.         }

  882.     }

  883.     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
  884.      *  <p>
  885.      *  The errors are estimated from partial derivatives properties of orbits,
  886.      *  starting from a scalar position error specified by the user.
  887.      *  Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
  888.      *  we get at constant energy (i.e. on a Keplerian trajectory):
  889.      *
  890.      *  <pre>
  891.      *  V² r |dV| = mu |dr|
  892.      *  </pre>
  893.      *
  894.      *  <p> So we deduce a scalar velocity error consistent with the position error. From here, we apply
  895.      *  orbits Jacobians matrices to get consistent errors on orbital parameters.
  896.      *
  897.      *  <p>
  898.      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
  899.      *  local estimates, not global ones. So some care must be taken when using these tolerances.
  900.      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
  901.      *  position after several orbits integration.
  902.      *  </p>
  903.      *
  904.      * @param dP user specified position error (m)
  905.      * @param orbit reference orbit
  906.      * @return a two rows array, row 0 being the absolute tolerance error
  907.      *                       and row 1 being the relative tolerance error
  908.      */
  909.     public static double[][] tolerances(final double dP, final Orbit orbit) {

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

  911.     }

  912.     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
  913.      *  <p>
  914.      *  The errors are estimated from partial derivatives properties of orbits,
  915.      *  starting from scalar position and velocity errors specified by the user.
  916.      *  <p>
  917.      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
  918.      *  local estimates, not global ones. So some care must be taken when using these tolerances.
  919.      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
  920.      *  position after several orbits integration.
  921.      *  </p>
  922.      *
  923.      * @param dP user specified position error (m)
  924.      * @param dV user specified velocity error (m/s)
  925.      * @param orbit reference orbit
  926.      * @return a two rows array, row 0 being the absolute tolerance error
  927.      *                       and row 1 being the relative tolerance error
  928.      * @since 10.3
  929.      */
  930.     public static double[][] tolerances(final double dP, final double dV, final Orbit orbit) {

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

  932.     }

  933.     /** Step handler used to compute the parameters for the short periodic contributions.
  934.      * @author Lucian Barbulescu
  935.      */
  936.     private class ShortPeriodicsHandler implements ODEStepHandler {

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

  939.         /** Constructor.
  940.          * @param forceModels force models
  941.          */
  942.         ShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
  943.             this.forceModels = forceModels;
  944.         }

  945.         /** {@inheritDoc} */
  946.         @Override
  947.         public void handleStep(final ODEStateInterpolator interpolator, final boolean isLast) {

  948.             // Get the grid points to compute
  949.             final double[] interpolationPoints =
  950.                     interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
  951.                                                     interpolator.getCurrentState().getTime());

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

  954.                 // Build the mean state interpolated at grid point
  955.                 final double time = interpolationPoints[i];
  956.                 final ODEStateAndDerivative sd = interpolator.getInterpolatedState(time);
  957.                 meanStates[i] = mapper.mapArrayToState(time,
  958.                                                        sd.getPrimaryState(),
  959.                                                        sd.getPrimaryDerivative(),
  960.                                                        PropagationType.MEAN);
  961.             }

  962.             // Computate short periodic coefficients for this step
  963.             for (DSSTForceModel forceModel : forceModels) {
  964.                 forceModel.updateShortPeriodTerms(forceModel.getParameters(), meanStates);
  965.             }

  966.         }
  967.     }
  968. }