DSSTPropagator.java

/* Copyright 2002-2015 CS Systèmes d'Information
 * Licensed to CS Systèmes d'Information (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.propagation.semianalytical.dsst;

import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.apache.commons.math3.exception.MaxCountExceededException;
import org.apache.commons.math3.ode.AbstractIntegrator;
import org.apache.commons.math3.ode.nonstiff.ClassicalRungeKuttaIntegrator;
import org.apache.commons.math3.ode.sampling.StepHandler;
import org.apache.commons.math3.ode.sampling.StepInterpolator;
import org.apache.commons.math3.util.FastMath;
import org.apache.commons.math3.util.MathUtils;
import org.orekit.attitudes.Attitude;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.errors.PropagationException;
import org.orekit.frames.Frame;
import org.orekit.orbits.EquinoctialOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.OrbitType;
import org.orekit.orbits.PositionAngle;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.integration.AbstractIntegratedPropagator;
import org.orekit.propagation.integration.StateMapper;
import org.orekit.propagation.numerical.NumericalPropagator;
import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
import org.orekit.propagation.semianalytical.dsst.utilities.InterpolationGrid;
import org.orekit.propagation.semianalytical.dsst.utilities.VariableStepInterpolationGrid;
import org.orekit.time.AbsoluteDate;

/**
 * This class propagates {@link org.orekit.orbits.Orbit orbits} using the DSST theory.
 * <p>
 * Whereas analytical propagators are configured only thanks to their various
 * constructors and can be used immediately after construction, such a semianalytical
 * propagator configuration involves setting several parameters between construction
 * time and propagation time, just as numerical propagators.
 * </p>
 * <p>
 * The configuration parameters that can be set are:
 * </p>
 * <ul>
 * <li>the initial spacecraft state ({@link #setInitialState(SpacecraftState)})</li>
 * <li>the various force models ({@link #addForceModel(DSSTForceModel)},
 * {@link #removeForceModels()})</li>
 * <li>the discrete events that should be triggered during propagation (
 * {@link #addEventDetector(org.orekit.propagation.events.EventDetector)},
 * {@link #clearEventsDetectors()})</li>
 * <li>the binding logic with the rest of the application ({@link #setSlaveMode()},
 * {@link #setMasterMode(double, org.orekit.propagation.sampling.OrekitFixedStepHandler)},
 * {@link #setMasterMode(org.orekit.propagation.sampling.OrekitStepHandler)},
 * {@link #setEphemerisMode()}, {@link #getGeneratedEphemeris()})</li>
 * </ul>
 * <p>
 * From these configuration parameters, only the initial state is mandatory.
 * The default propagation settings are in {@link OrbitType#EQUINOCTIAL equinoctial}
 * parameters with {@link PositionAngle#TRUE true} longitude argument.
 * The central attraction coefficient used to define the initial orbit will be used.
 * However, specifying only the initial state would mean the propagator would use
 * only keplerian forces. In this case, the simpler
 * {@link org.orekit.propagation.analytical.KeplerianPropagator KeplerianPropagator}
 * class would be more effective.
 * </p>
 * <p>
 * The underlying numerical integrator set up in the constructor may also have
 * its own configuration parameters. Typical configuration parameters for adaptive
 * stepsize integrators are the min, max and perhaps start step size as well as
 * the absolute and/or relative errors thresholds.
 * </p>
 * <p>
 * The state that is seen by the integrator is a simple six elements double array.
 * These six elements are:
 * <ul>
 * <li>the {@link org.orekit.orbits.EquinoctialOrbit equinoctial orbit parameters}
 * (a, e<sub>x</sub>, e<sub>y</sub>, h<sub>x</sub>, h<sub>y</sub>, λ<sub>m</sub>)
 * in meters and radians,</li>
 * </ul>
 * </p>
 * <p>
 * The same propagator can be reused for several orbit extrapolations,
 * by resetting the initial state without modifying the other configuration
 * parameters. However, the same instance cannot be used simultaneously by
 * different threads, the class is <em>not</em> thread-safe.
 * </p>
 *
 * @see SpacecraftState
 * @see DSSTForceModel
 * @author Romain Di Costanzo
 * @author Pascal Parraud
 */
public class DSSTPropagator extends AbstractIntegratedPropagator {

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

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

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

    /** Create a new instance of DSSTPropagator.
     *  <p>
     *  After creation, there are no perturbing forces at all.
     *  This means that if {@link #addForceModel addForceModel}
     *  is not called after creation, the integrated orbit will
     *  follow a keplerian evolution only.
     *  </p>
     *  @param integrator numerical integrator to use for propagation.
     *  @param meanOnly output only the mean orbits.
     */
    public DSSTPropagator(final AbstractIntegrator integrator, final boolean meanOnly) {
        super(integrator, meanOnly);
        initMapper();
        // DSST uses only equinoctial orbits and mean longitude argument
        setOrbitType(OrbitType.EQUINOCTIAL);
        setPositionAngleType(PositionAngle.TRUE);
        setAttitudeProvider(DEFAULT_LAW);
    }


    /** Create a new instance of DSSTPropagator.
     *  <p>
     *  After creation, there are no perturbing forces at all.
     *  This means that if {@link #addForceModel addForceModel}
     *  is not called after creation, the integrated orbit will
     *  follow a keplerian evolution only. Only the mean orbits
     *  will be generated.
     *  </p>
     *  @param integrator numerical integrator to use for propagation.
     */
    public DSSTPropagator(final AbstractIntegrator integrator) {
        super(integrator, true);
        initMapper();
        // DSST uses only equinoctial orbits and mean longitude argument
        setOrbitType(OrbitType.EQUINOCTIAL);
        setPositionAngleType(PositionAngle.TRUE);
        setAttitudeProvider(DEFAULT_LAW);
    }

    /** Set the initial state with osculating orbital elements.
     *  @param initialState initial state (defined with osculating elements)
     *  @throws PropagationException if the initial state cannot be set
     */
    public void setInitialState(final SpacecraftState initialState)
        throws PropagationException {
        setInitialState(initialState, true);
    }

    /** Set the initial state.
     *  @param initialState initial state
     *  @param isOsculating true if the orbital state is defined with osculating elements
     *  @throws PropagationException if the initial state cannot be set
     */
    public void setInitialState(final SpacecraftState initialState,
                                final boolean isOsculating)
        throws PropagationException {
        mapper.setInitialIsOsculating(isOsculating);
        resetInitialState(initialState);
    }

    /** Reset the initial state.
     *
     *  @param state new initial state
     *  @throws PropagationException if initial state cannot be reset
     */
    @Override
    public void resetInitialState(final SpacecraftState state) throws PropagationException {
        super.setStartDate(state.getDate());
        super.resetInitialState(state);
    }

    /** Check if the initial state is provided in osculating elements.
     * @return true if initial state is provided in osculating elements
     */
    public boolean initialIsOsculating() {
        return mapper.initialIsOsculating();
    }

    /** Add a force model to the global perturbation model.
     *  <p>
     *  If this method is not called at all,
     *  the integrated orbit will follow a keplerian evolution only.
     *  </p>
     *  @param force perturbing {@link DSSTForceModel force} to add
     *  @see #removeForceModels()
     */
    public void addForceModel(final DSSTForceModel force) {
        mapper.addForceModel(force);
        force.registerAttitudeProvider(getAttitudeProvider());
    }

    /** Remove all perturbing force models from the global perturbation model.
     *  <p>
     *  Once all perturbing forces have been removed (and as long as no new force model is added),
     *  the integrated orbit will follow a keplerian evolution only.
     *  </p>
     *  @see #addForceModel(DSSTForceModel)
     */
    public void removeForceModels() {
        mapper.removeForceModels();
    }

    /** Conversion from mean to osculating orbit.
     * <p>
     * Compute osculating state <b>in a DSST sense</b>, corresponding to the
     * mean SpacecraftState in input, and according to the Force models taken
     * into account.
     * </p><p>
     * Since the osculating state is obtained by adding short-periodic variation
     * of each force model, the resulting output will depend on the
     * force models parameterized in input.
     * </p>
     * @param mean Mean state to convert
     * @param forces Forces to take into account
     * @return osculating state in a DSST sense
     * @throws OrekitException if computation of short periodics fails
     */
    public static SpacecraftState computeOsculatingState(final SpacecraftState mean, final Collection<DSSTForceModel> forces)
        throws OrekitException {

        // Creation of a DSSTPropagator instance
        final AbstractIntegrator integrator = new ClassicalRungeKuttaIntegrator(43200.);
        final DSSTPropagator dsst = new DSSTPropagator(integrator, false);
        //Create the auxiliary object
        final AuxiliaryElements aux = new AuxiliaryElements(mean.getOrbit(), I);

        // Set the force models
        for (final DSSTForceModel force : forces) {
            force.initialize(aux, false);
            dsst.addForceModel(force);
        }

        final Orbit osculatingOrbit = dsst.mapper.computeOsculatingOrbit(mean);

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

    /** Conversion from osculating to mean, orbit.
     * <p>
     * Compute osculating state <b>in a DSST sense</b>, corresponding to the
     * mean SpacecraftState in input, and according to the Force models taken
     * into account.
     * </p><p>
     * Since the osculating state is obtained with the computation of
     * short-periodic variation of each force model, the resulting output will
     * depend on the force models parametrized in input.
     * </p><p>
     * The computing is done through a fixed-point iteration process.
     * </p>
     * @param osculating Osculating state to convert
     * @param forces Forces to take into account
     * @return mean state in a DSST sense
     * @throws OrekitException if computation of short periodics fails or iteration algorithm does not converge
     */
    public static SpacecraftState computeMeanState(final SpacecraftState osculating, final Collection<DSSTForceModel> forces)
        throws OrekitException {

        // Creation of a DSSTPropagator instance
        final AbstractIntegrator integrator = new ClassicalRungeKuttaIntegrator(43200.);
        final DSSTPropagator dsst = new DSSTPropagator(integrator, false);
        //Create the auxiliary object
        final AuxiliaryElements aux = new AuxiliaryElements(osculating.getOrbit(), I);

        // Set the force models
        for (final DSSTForceModel force : forces) {
            force.initialize(aux, false);
            dsst.addForceModel(force);
        }

        dsst.setInitialState(osculating, true);

        final Orbit meanOrbit = dsst.mapper.computeMeanOrbit(osculating);

        return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(), osculating.getAdditionalStates());
    }

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

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

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

        //Register the attitude provider for each force model
        for (final DSSTForceModel force : mapper.getForceModels()) {
            force.registerAttitudeProvider(attitudeProvider);
        }
    }

    /** Method called just before integration.
     * <p>
     * The default implementation does nothing, it may be specialized in subclasses.
     * </p>
     * @param initialState initial state
     * @param tEnd target date at which state should be propagated
     * @exception OrekitException if hook cannot be run
     */
    @Override
    protected void beforeIntegration(final SpacecraftState initialState,
                                     final AbsoluteDate tEnd)
        throws OrekitException {

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

        // check if only mean elements must be used
        final boolean meanOnly = isMeanOrbit();

        // initialize all perturbing forces
        for (final DSSTForceModel force : mapper.getForceModels()) {
            force.initialize(aux, meanOnly);
        }

        // if required, insert the special short periodics step handler
        if (!meanOnly) {
            final InterpolationGrid grid = new VariableStepInterpolationGrid(INTERPOLATION_POINTS_PER_STEP);
            final ShortPeriodicsHandler spHandler = new ShortPeriodicsHandler(grid);
            final Collection<StepHandler> stepHandlers = new ArrayList<StepHandler>();
            stepHandlers.add(spHandler);
            final AbstractIntegrator integrator = getIntegrator();
            final Collection<StepHandler> existing = integrator.getStepHandlers();
            stepHandlers.addAll(existing);

            integrator.clearStepHandlers();

            // add back the existing handlers after the short periodics one
            for (final StepHandler sp : stepHandlers) {
                integrator.addStepHandler(sp);
            }
        }
    }

    /** {@inheritDoc} */
    @Override
    protected void afterIntegration() throws OrekitException {
        // remove the special short periodics step handler if added before
        if (!isMeanOrbit()) {
            final List<StepHandler> preserved = new ArrayList<StepHandler>();
            final AbstractIntegrator integrator = getIntegrator();
            for (final StepHandler sp : integrator.getStepHandlers()) {
                if (!(sp instanceof ShortPeriodicsHandler)) {
                    preserved.add(sp);
                }
            }

            // clear the list
            integrator.clearStepHandlers();

            // add back the step handlers that were important for the user
            for (final StepHandler sp : preserved) {
                integrator.addStepHandler(sp);
            }
        }
    }

    /** {@inheritDoc} */
    @Override
    protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
                                       final OrbitType orbitType, final PositionAngle positionAngleType,
                                       final AttitudeProvider attitudeProvider, final Frame frame) {

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

        // copy the specific settings from the existing mapper
        if (mapper != null) {
            for (final DSSTForceModel forceModel : mapper.getForceModels()) {
                newMapper.addForceModel(forceModel);
            }
            newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
            newMapper.setInitialIsOsculating(mapper.initialIsOsculating());
        }

        mapper = newMapper;
        return mapper;

    }


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

        /** Serializable UID. */
        private static final long serialVersionUID = 20130621L;

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

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

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

        /** Simple constructor.
         * <p>
         * The position parameter type is meaningful only if {@link
         * #getOrbitType() propagation orbit type}
         * support it. As an example, it is not meaningful for propagation
         * in {@link OrbitType#CARTESIAN Cartesian} parameters.
         * </p>
         * @param referenceDate reference date
         * @param mu central attraction coefficient (m³/s²)
         * @param attitudeProvider attitude provider
         * @param frame inertial frame
         */
        public MeanPlusShortPeriodicMapper(final AbsoluteDate referenceDate, final double mu,
                                           final AttitudeProvider attitudeProvider, final Frame frame) {

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

            this.forceModels = new ArrayList<DSSTForceModel>();

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

            this.initialIsOsculating = true;

        }

        /** {@inheritDoc} */
        @Override
        public SpacecraftState mapArrayToState(final double t, final double[] y, final boolean meanOnly)
            throws OrekitException {

            final AbsoluteDate date = mapDoubleToDate(t);

            // add short periodic variations to mean elements to get osculating elements
            // (the loop may not be performed if there are no force models and in the
            //  case we want to remain in mean parameters only)
            final double[] elements = y.clone();
            if (!meanOnly) {
                for (final DSSTForceModel forceModel : forceModels) {
                    final double[] shortPeriodic = forceModel.getShortPeriodicVariations(date, y);
                    for (int i = 0; i < shortPeriodic.length; i++) {
                        elements[i] += shortPeriodic[i];
                    }
                }
            }

            final double mass = elements[6];
            if (mass <= 0.0) {
                throw new PropagationException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, mass);
            }

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

            return new SpacecraftState(orbit, attitude, mass);

        }

        /** {@inheritDoc} */
        @Override
        public void mapStateToArray(final SpacecraftState state, final double[] y)
            throws OrekitException {

            final Orbit meanOrbit;
            if (!initialIsOsculating) {
                // the state is considered to be already a mean state
                meanOrbit = state.getOrbit();
            } else {
                // the state is considered to be an osculating state
                meanOrbit = computeMeanState(state, forceModels).getOrbit();
            }

            OrbitType.EQUINOCTIAL.mapOrbitToArray(meanOrbit, PositionAngle.MEAN, y);
            y[6] = state.getMass();

        }

        /** Add a force model to the global perturbation model.
         *  <p>
         *  If this method is not called at all,
         *  the integrated orbit will follow a keplerian evolution only.
         *  </p>
         *  @param force perturbing {@link DSSTForceModel force} to add
         *  @see #removeForceModels()
         */
        public void addForceModel(final DSSTForceModel force) {
            forceModels.add(force);
        }

        /** Remove all perturbing force models from the global perturbation model.
         *  <p>
         *  Once all perturbing forces have been removed (and as long as no new force model is added),
         *  the integrated orbit will follow a keplerian evolution only.
         *  </p>
         *  @see #addForceModel(DSSTForceModel)
         */
        public void removeForceModels() {
            forceModels.clear();
        }

        /** Get the force models.
         * @return force models
         */
        public List<DSSTForceModel> getForceModels() {
            return forceModels;
        }

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

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

        /** Set the osculating parameters flag.
         * @param initialIsOsculating if true, the initial state is provided in osculating elements
         */
        public void setInitialIsOsculating(final boolean initialIsOsculating) {
            this.initialIsOsculating = initialIsOsculating;
        }

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

        /** Reset the short periodics coefficient for each {@link DSSTForceModel}.
         * @see DSSTForceModel#resetShortPeriodicsCoefficients()
         */
        private void resetShortPeriodicsCoefficients() {
            for (final DSSTForceModel forceModel : forceModels) {
                forceModel.resetShortPeriodicsCoefficients();
            }
        }

        /** Launch the computation of short periodics coefficients.
         * @param state input state
         * @throws OrekitException if the computation of a short periodic coefficient fails
         * @see {@link DSSTForceModel#computeShortPeriodicsCoefficients(AuxiliaryElements)}
         */
        private void computeShortPeriodicsCoefficients(final SpacecraftState state)
            throws OrekitException {
            final AuxiliaryElements aux = new AuxiliaryElements(state.getOrbit(), I);
            for (final DSSTForceModel forceModel : forceModels) {
                forceModel.initializeStep(aux);
                forceModel.computeShortPeriodicsCoefficients(state);
            }
        }

        /** Compute mean state from osculating state.
         * <p>
         * Compute in a DSST sense the mean state corresponding to the input osculating state.
         * </p><p>
         * The computing is done through a fixed-point iteration process.
         * </p>
         * @param osculating initial osculating state
         * @return mean state
         * @throws OrekitException if the underlying computation of short periodic variation fails
         */
        private Orbit computeMeanOrbit(final SpacecraftState osculating)
            throws OrekitException {

            // rough initialization of the mean parameters
            EquinoctialOrbit meanOrbit = new EquinoctialOrbit(
                    osculating.getOrbit());

            // threshold for each parameter
            final double epsilon         = 1.0e-13;
            final double thresholdA      = epsilon * (1 + FastMath.abs(meanOrbit.getA()));
            final double thresholdE      = epsilon * (1 + meanOrbit.getE());
            final double thresholdAngles = epsilon * FastMath.PI;

            int i = 0;
            while (i++ < 200) {

                final SpacecraftState meanState = new SpacecraftState(
                        meanOrbit, osculating.getAttitude(), osculating.getMass());
                // recompute the osculating parameters from the current mean parameters
                final EquinoctialOrbit rebuilt = (EquinoctialOrbit) computeOsculatingOrbit(meanState);

                // adapted parameters residuals
                final double deltaA = osculating.getA() - rebuilt.getA();
                final double deltaEx = osculating.getEquinoctialEx() - rebuilt.getEquinoctialEx();
                final double deltaEy = osculating.getEquinoctialEy() - rebuilt.getEquinoctialEy();
                final double deltaHx = osculating.getHx() - rebuilt.getHx();
                final double deltaHy = osculating.getHy() - rebuilt.getHy();
                final double deltaLm = MathUtils.normalizeAngle(osculating.getLM() - rebuilt.getLM(), 0.0);

                // check convergence
                if ((FastMath.abs(deltaA) < thresholdA) &&
                        (FastMath.abs(deltaEx) < thresholdE) &&
                        (FastMath.abs(deltaEy) < thresholdE) &&
                        (FastMath.abs(deltaLm) < thresholdAngles)) {
                    return meanOrbit;
                }

                // update mean parameters
                meanOrbit = new EquinoctialOrbit(
                        meanOrbit.getA() + deltaA,
                        meanOrbit.getEquinoctialEx() + deltaEx,
                        meanOrbit.getEquinoctialEy() + deltaEy,
                        meanOrbit.getHx() + deltaHx,
                        meanOrbit.getHy() + deltaHy,
                        meanOrbit.getLM() + deltaLm,
                        PositionAngle.MEAN, meanOrbit.getFrame(),
                        meanOrbit.getDate(), meanOrbit.getMu());
            }

            throw new PropagationException(OrekitMessages.UNABLE_TO_COMPUTE_DSST_MEAN_PARAMETERS, i);
        }

        /** Compute osculating state from mean state.
         * <p>
         * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
         * </p>
         * @param meanState initial mean state
         * @return osculating state
         * @throws OrekitException if the computation of the short-periodic variation fails
         */
        private Orbit computeOsculatingOrbit(final SpacecraftState meanState) throws OrekitException {

            resetShortPeriodicsCoefficients();
            computeShortPeriodicsCoefficients(meanState);


            final double[] mean = new double[6];
            OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngle.MEAN, mean);
            final double[] y = mean.clone();
            for (final DSSTForceModel forceModel : this.forceModels) {

                final double[] shortPeriodic = forceModel
                        .getShortPeriodicVariations(meanState.getDate(), mean);

                for (int i = 0; i < shortPeriodic.length; i++) {
                    y[i] += shortPeriodic[i];
                }
            }
            return OrbitType.EQUINOCTIAL.mapArrayToOrbit(y,
                    PositionAngle.MEAN, meanState.getDate(),
                    meanState.getMu(), meanState.getFrame());
        }

        /** Replace the instance with a data transfer object for serialization.
         * @return data transfer object that will be serialized
         * @exception NotSerializableException if one of the force models cannot be serialized
         */
        private Object writeReplace() throws NotSerializableException {

            // Check the force models can be serialized
            final DSSTForceModel[] serializableorceModels = new DSSTForceModel[forceModels.size()];
            for (int i = 0; i < serializableorceModels.length; ++i) {
                final DSSTForceModel forceModel = forceModels.get(i);
                if (forceModel instanceof Serializable) {
                    serializableorceModels[i] = forceModel;
                } else {
                    throw new NotSerializableException(forceModel.getClass().getName());
                }
            }
            return new DataTransferObject(getReferenceDate(), getMu(), getAttitudeProvider(), getFrame(),
                                          initialIsOsculating, serializableorceModels, satelliteRevolution);
        }

        /** Internal class used only for serialization. */
        private static class DataTransferObject implements Serializable {

            /** Serializable UID. */
            private static final long serialVersionUID = 20130621L;

            /** Reference date. */
            private final AbsoluteDate referenceDate;

            /** Central attraction coefficient (m³/s²). */
            private final double mu;

            /** Attitude provider. */
            private final AttitudeProvider attitudeProvider;

            /** Inertial frame. */
            private final Frame frame;

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

            /** Force models to use for short periodic terms computation. */
            private final DSSTForceModel[] forceModels;

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

            /** Simple constructor.
             * @param referenceDate reference date
             * @param mu central attraction coefficient (m³/s²)
             * @param attitudeProvider attitude provider
             * @param frame inertial frame
             * @param initialIsOsculating if true, initial orbital state is given with osculating elements
             * @param forceModels force models to use for short periodic terms computation
             * @param satelliteRevolution number of satellite revolutions in the averaging interval
             */
            public DataTransferObject(final AbsoluteDate referenceDate, final double mu,
                                      final AttitudeProvider attitudeProvider, final Frame frame,
                                      final boolean initialIsOsculating,
                                      final DSSTForceModel[] forceModels, final int satelliteRevolution) {
                this.referenceDate       = referenceDate;
                this.mu                  = mu;
                this.attitudeProvider    = attitudeProvider;
                this.frame               = frame;
                this.initialIsOsculating = initialIsOsculating;
                this.forceModels         = forceModels;
                this.satelliteRevolution = satelliteRevolution;
            }

            /** Replace the deserialized data transfer object with a {@link MeanPlusShortPeriodicMapper}.
             * @return replacement {@link MeanPlusShortPeriodicMapper}
             */
            private Object readResolve() {
                final MeanPlusShortPeriodicMapper mapper =
                        new MeanPlusShortPeriodicMapper(referenceDate, mu, attitudeProvider, frame);
                for (final DSSTForceModel forceModel : forceModels) {
                    mapper.addForceModel(forceModel);
                }
                mapper.setSatelliteRevolution(satelliteRevolution);
                mapper.setInitialIsOsculating(initialIsOsculating);
                return mapper;
            }

        }

    }

    /** {@inheritDoc} */
    @Override
    protected MainStateEquations getMainStateEquations(final AbstractIntegrator integrator) {
        return new Main(integrator);
    }

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

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

        /** Simple constructor.
         * @param integrator numerical integrator to use for propagation.
         */
        public Main(final AbstractIntegrator integrator) {
            yDot = new double[7];

            for (final DSSTForceModel forceModel : mapper.getForceModels()) {
                final EventDetector[] modelDetectors = forceModel.getEventsDetectors();
                if (modelDetectors != null) {
                    for (final EventDetector detector : modelDetectors) {
                        setUpEventDetector(integrator, detector);
                    }
                }
            }

        }

        /** {@inheritDoc} */
        @Override
        public double[] computeDerivatives(final SpacecraftState state) throws OrekitException {

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

            // initialize all perturbing forces
            for (final DSSTForceModel force : mapper.getForceModels()) {
                force.initializeStep(aux);
            }

            Arrays.fill(yDot, 0.0);

            // compute the contributions of all perturbing forces
            for (final DSSTForceModel forceModel : mapper.getForceModels()) {
                final double[] daidt = forceModel.getMeanElementRate(state);
                for (int i = 0; i < daidt.length; i++) {
                    yDot[i] += daidt[i];
                }
            }

            // finalize derivatives by adding the Kepler contribution
            final EquinoctialOrbit orbit = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(state.getOrbit());
            orbit.addKeplerContribution(PositionAngle.MEAN, getMu(), yDot);

            return yDot.clone();
        }

    }

    /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
     *  <p>
     *  The errors are estimated from partial derivatives properties of orbits,
     *  starting from a scalar position error specified by the user.
     *  Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
     *  we get at constant energy (i.e. on a Keplerian trajectory):
     *
     *  <pre>
     *  V² r |dV| = mu |dr|
     *  </pre>
     *
     *  So we deduce a scalar velocity error consistent with the position error. From here, we apply
     *  orbits Jacobians matrices to get consistent errors on orbital parameters.
     *  </p>
     *  <p>
     *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
     *  local estimates, not global ones. So some care must be taken when using these tolerances.
     *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
     *  position after several orbits integration.
     *  </p>
     *
     * @param dP user specified position error (m)
     * @param orbit reference orbit
     * @return a two rows array, row 0 being the absolute tolerance error
     *                       and row 1 being the relative tolerance error
     * @exception PropagationException if Jacobian is singular
     */
    public static double[][] tolerances(final double dP, final Orbit orbit)
        throws PropagationException {

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

    }

    /** Step handler used to compute the parameters for the short periodic contributions.
     * @author Lucian Barbulescu
     */
    private class ShortPeriodicsHandler implements StepHandler {

        /** Grid for interpolation of the short periodics coefficients. */
        private final InterpolationGrid grid;

        /** Constructor.
         * @param grid for interpolation of the short periodics coefficients
         */
        public ShortPeriodicsHandler(final InterpolationGrid grid) {
            this.grid = grid;
        }

        @Override
        public void init(final double t0, final double[] y0, final double t) {
            //No particular initialization is necessary
        }

        /** {@inheritDoc} */
        @Override
        public void handleStep(final StepInterpolator interpolator, final boolean isLast) {

            // Reset the short periodics coefficients in order to ensure that interpolation
            // will be based on current step only
            mapper.resetShortPeriodicsCoefficients();

            // Get the grid points to compute
            final double[] interpolationPoints = grid.getGridPoints(
                    interpolator.getPreviousTime(), interpolator.getCurrentTime());

            for (final double time : interpolationPoints) {
                // Move the interpolator to the grid point
                interpolator.setInterpolatedTime(time);

                // Build the corresponding state
                SpacecraftState state;
                try {
                    state = mapper.mapArrayToState(time,
                            interpolator.getInterpolatedState(), true);
                    // Launch the computation of short periodic coefficients for this date
                    mapper.computeShortPeriodicsCoefficients(state);

                } catch (MaxCountExceededException e) {
                    e.printStackTrace();
                } catch (OrekitException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}