DSSTPropagator.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.io.NotSerializableException;
  19. import java.io.Serializable;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.Collection;
  23. import java.util.Collections;
  24. import java.util.HashMap;
  25. import java.util.HashSet;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Set;

  29. import org.hipparchus.ode.ODEIntegrator;
  30. import org.hipparchus.ode.ODEStateAndDerivative;
  31. import org.hipparchus.ode.sampling.ODEStateInterpolator;
  32. import org.hipparchus.ode.sampling.ODEStepHandler;
  33. import org.hipparchus.util.FastMath;
  34. import org.hipparchus.util.MathUtils;
  35. import org.orekit.attitudes.Attitude;
  36. import org.orekit.attitudes.AttitudeProvider;
  37. import org.orekit.errors.OrekitException;
  38. import org.orekit.errors.OrekitExceptionWrapper;
  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.SpacecraftState;
  46. import org.orekit.propagation.events.EventDetector;
  47. import org.orekit.propagation.integration.AbstractIntegratedPropagator;
  48. import org.orekit.propagation.integration.StateMapper;
  49. import org.orekit.propagation.numerical.NumericalPropagator;
  50. import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
  51. import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
  52. import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
  53. import org.orekit.propagation.semianalytical.dsst.utilities.FixedNumberInterpolationGrid;
  54. import org.orekit.propagation.semianalytical.dsst.utilities.InterpolationGrid;
  55. import org.orekit.propagation.semianalytical.dsst.utilities.MaxGapInterpolationGrid;
  56. import org.orekit.time.AbsoluteDate;

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

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

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

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

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

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

  140.     /** Generator for the interpolation grid. */
  141.     private InterpolationGrid interpolationgrid;

  142.     /** Create a new instance of DSSTPropagator.
  143.      *  <p>
  144.      *  After creation, there are no perturbing forces at all.
  145.      *  This means that if {@link #addForceModel addForceModel}
  146.      *  is not called after creation, the integrated orbit will
  147.      *  follow a Keplerian evolution only.
  148.      *  </p>
  149.      *  @param integrator numerical integrator to use for propagation.
  150.      *  @param meanOnly output only the mean orbits.
  151.      */
  152.     public DSSTPropagator(final ODEIntegrator integrator, final boolean meanOnly) {
  153.         super(integrator, meanOnly);
  154.         forceModels = new ArrayList<DSSTForceModel>();
  155.         initMapper();
  156.         // DSST uses only equinoctial orbits and mean longitude argument
  157.         setOrbitType(OrbitType.EQUINOCTIAL);
  158.         setPositionAngleType(PositionAngle.MEAN);
  159.         setAttitudeProvider(DEFAULT_LAW);
  160.         setInterpolationGridToFixedNumberOfPoints(INTERPOLATION_POINTS_PER_STEP);
  161.     }


  162.     /** Create a new instance of DSSTPropagator.
  163.      *  <p>
  164.      *  After creation, there are no perturbing forces at all.
  165.      *  This means that if {@link #addForceModel addForceModel}
  166.      *  is not called after creation, the integrated orbit will
  167.      *  follow a Keplerian evolution only. Only the mean orbits
  168.      *  will be generated.
  169.      *  </p>
  170.      *  @param integrator numerical integrator to use for propagation.
  171.      */
  172.     public DSSTPropagator(final ODEIntegrator integrator) {
  173.         super(integrator, true);
  174.         forceModels = new ArrayList<DSSTForceModel>();
  175.         initMapper();
  176.         // DSST uses only equinoctial orbits and mean longitude argument
  177.         setOrbitType(OrbitType.EQUINOCTIAL);
  178.         setPositionAngleType(PositionAngle.MEAN);
  179.         setAttitudeProvider(DEFAULT_LAW);
  180.         setInterpolationGridToFixedNumberOfPoints(INTERPOLATION_POINTS_PER_STEP);
  181.     }

  182.     /** Set the initial state with osculating orbital elements.
  183.      *  @param initialState initial state (defined with osculating elements)
  184.      *  @throws OrekitException if the initial state cannot be set
  185.      */
  186.     public void setInitialState(final SpacecraftState initialState)
  187.         throws OrekitException {
  188.         setInitialState(initialState, true);
  189.     }

  190.     /** Set the initial state.
  191.      *  @param initialState initial state
  192.      *  @param isOsculating true if the orbital state is defined with osculating elements
  193.      *  @throws OrekitException if the initial state cannot be set
  194.      */
  195.     public void setInitialState(final SpacecraftState initialState,
  196.                                 final boolean isOsculating)
  197.         throws OrekitException {
  198.         initialIsOsculating = isOsculating;
  199.         resetInitialState(initialState);
  200.     }

  201.     /** Reset the initial state.
  202.      *
  203.      *  @param state new initial state
  204.      *  @throws OrekitException if initial state cannot be reset
  205.      */
  206.     @Override
  207.     public void resetInitialState(final SpacecraftState state) throws OrekitException {
  208.         super.setStartDate(state.getDate());
  209.         super.resetInitialState(state);
  210.     }

  211.     /** Set the selected short periodic coefficients that must be stored as additional states.
  212.      * @param selectedCoefficients short periodic coefficients that must be stored as additional states
  213.      * (null means no coefficients are selected, empty set means all coefficients are selected)
  214.      */
  215.     public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
  216.         mapper.setSelectedCoefficients(selectedCoefficients == null ?
  217.                                        null : new HashSet<String>(selectedCoefficients));
  218.     }

  219.     /** Get the selected short periodic coefficients that must be stored as additional states.
  220.      * @return short periodic coefficients that must be stored as additional states
  221.      * (null means no coefficients are selected, empty set means all coefficients are selected)
  222.      */
  223.     public Set<String> getSelectedCoefficients() {
  224.         final Set<String> set = mapper.getSelectedCoefficients();
  225.         return set == null ? null : Collections.unmodifiableSet(set);
  226.     }

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

  233.     /** Set the interpolation grid generator.
  234.      * <p>
  235.      * The generator will create an interpolation grid with a fixed
  236.      * number of points for each mean element integration step.
  237.      * </p>
  238.      * <p>
  239.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  240.      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
  241.      * by default the propagator is set as to 3 interpolations points per step.
  242.      * </p>
  243.      * @param interpolationPoints number of interpolation points at
  244.      * each integration step
  245.      * @see #setInterpolationGridToMaxTimeGap(double)
  246.      * @since 7.1
  247.      */
  248.     public void setInterpolationGridToFixedNumberOfPoints(final int interpolationPoints) {
  249.         interpolationgrid = new FixedNumberInterpolationGrid(interpolationPoints);
  250.     }

  251.     /** Set the interpolation grid generator.
  252.      * <p>
  253.      * The generator will create an interpolation grid with a maximum
  254.      * time gap between interpolation points.
  255.      * </p>
  256.      * <p>
  257.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  258.      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
  259.      * by default the propagator is set as to 3 interpolations points per step.
  260.      * </p>
  261.      * @param maxGap maximum time gap between interpolation points (seconds)
  262.      * @see #setInterpolationGridToFixedNumberOfPoints(int)
  263.      * @since 7.1
  264.      */
  265.     public void setInterpolationGridToMaxTimeGap(final double maxGap) {
  266.         interpolationgrid = new MaxGapInterpolationGrid(maxGap);
  267.     }

  268.     /** Add a force model to the global perturbation model.
  269.      *  <p>
  270.      *  If this method is not called at all,
  271.      *  the integrated orbit will follow a Keplerian evolution only.
  272.      *  </p>
  273.      *  @param force perturbing {@link DSSTForceModel force} to add
  274.      *  @see #removeForceModels()
  275.      */
  276.     public void addForceModel(final DSSTForceModel force) {
  277.         forceModels.add(force);
  278.         force.registerAttitudeProvider(getAttitudeProvider());
  279.     }

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

  290.     /** Conversion from mean to osculating orbit.
  291.      * <p>
  292.      * Compute osculating state <b>in a DSST sense</b>, corresponding to the
  293.      * mean SpacecraftState in input, and according to the Force models taken
  294.      * into account.
  295.      * </p><p>
  296.      * Since the osculating state is obtained by adding short-periodic variation
  297.      * of each force model, the resulting output will depend on the
  298.      * force models parameterized in input.
  299.      * </p>
  300.      * @param mean Mean state to convert
  301.      * @param forces Forces to take into account
  302.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  303.      * like atmospheric drag, radiation pressure or specific user-defined models)
  304.      * @return osculating state in a DSST sense
  305.      * @throws OrekitException if computation of short periodics fails
  306.      */
  307.     public static SpacecraftState computeOsculatingState(final SpacecraftState mean,
  308.                                                          final AttitudeProvider attitudeProvider,
  309.                                                          final Collection<DSSTForceModel> forces)
  310.         throws OrekitException {

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

  313.         // Set the force models
  314.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
  315.         for (final DSSTForceModel force : forces) {
  316.             force.registerAttitudeProvider(attitudeProvider);
  317.             shortPeriodTerms.addAll(force.initialize(aux, false));
  318.             force.updateShortPeriodTerms(mean);
  319.         }

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

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

  323.     }

  324.     /** Conversion from osculating to mean orbit.
  325.      * <p>
  326.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  327.      * osculating SpacecraftState in input, and according to the Force models
  328.      * taken into account.
  329.      * </p><p>
  330.      * Since the osculating state is obtained with the computation of
  331.      * short-periodic variation of each force model, the resulting output will
  332.      * depend on the force models parameterized in input.
  333.      * </p><p>
  334.      * The computation is done through a fixed-point iteration process.
  335.      * </p>
  336.      * @param osculating Osculating state to convert
  337.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  338.      * like atmospheric drag, radiation pressure or specific user-defined models)
  339.      * @param forceModels Forces to take into account
  340.      * @return mean state in a DSST sense
  341.      * @throws OrekitException if computation of short periodics fails or iteration algorithm does not converge
  342.      */
  343.     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
  344.                                                    final AttitudeProvider attitudeProvider,
  345.                                                    final Collection<DSSTForceModel> forceModels)
  346.         throws OrekitException {
  347.         final Orbit meanOrbit = computeMeanOrbit(osculating, attitudeProvider, forceModels);
  348.         return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(), osculating.getAdditionalStates());
  349.     }

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

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

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

  372.         //Register the attitude provider for each force model
  373.         for (final DSSTForceModel force : forceModels) {
  374.             force.registerAttitudeProvider(attitudeProvider);
  375.         }
  376.     }

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

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

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

  393.         // initialize all perturbing forces
  394.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
  395.         for (final DSSTForceModel force : forceModels) {
  396.             shortPeriodTerms.addAll(force.initialize(aux, meanOnly));
  397.         }
  398.         mapper.setShortPeriodTerms(shortPeriodTerms);

  399.         // if required, insert the special short periodics step handler
  400.         if (!meanOnly) {
  401.             final ShortPeriodicsHandler spHandler = new ShortPeriodicsHandler(forceModels);
  402.             final Collection<ODEStepHandler> stepHandlers = new ArrayList<ODEStepHandler>();
  403.             stepHandlers.add(spHandler);
  404.             final ODEIntegrator integrator = getIntegrator();
  405.             final Collection<ODEStepHandler> existing = integrator.getStepHandlers();
  406.             stepHandlers.addAll(existing);

  407.             integrator.clearStepHandlers();

  408.             // add back the existing handlers after the short periodics one
  409.             for (final ODEStepHandler sp : stepHandlers) {
  410.                 integrator.addStepHandler(sp);
  411.             }
  412.         }
  413.     }

  414.     /** {@inheritDoc} */
  415.     @Override
  416.     protected void afterIntegration() throws OrekitException {
  417.         // remove the special short periodics step handler if added before
  418.         if (!isMeanOrbit()) {
  419.             final List<ODEStepHandler> preserved = new ArrayList<ODEStepHandler>();
  420.             final ODEIntegrator integrator = getIntegrator();
  421.             for (final ODEStepHandler sp : integrator.getStepHandlers()) {
  422.                 if (!(sp instanceof ShortPeriodicsHandler)) {
  423.                     preserved.add(sp);
  424.                 }
  425.             }

  426.             // clear the list
  427.             integrator.clearStepHandlers();

  428.             // add back the step handlers that were important for the user
  429.             for (final ODEStepHandler sp : preserved) {
  430.                 integrator.addStepHandler(sp);
  431.             }
  432.         }
  433.     }

  434.     /** Compute mean state from osculating state.
  435.      * <p>
  436.      * Compute in a DSST sense the mean state corresponding to the input osculating state.
  437.      * </p><p>
  438.      * The computing is done through a fixed-point iteration process.
  439.      * </p>
  440.      * @param osculating initial osculating state
  441.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  442.      * like atmospheric drag, radiation pressure or specific user-defined models)
  443.      * @param forceModels force models
  444.      * @return mean state
  445.      * @throws OrekitException if the underlying computation of short periodic variation fails
  446.      */
  447.     private static Orbit computeMeanOrbit(final SpacecraftState osculating,
  448.                                           final AttitudeProvider attitudeProvider,
  449.                                           final Collection<DSSTForceModel> forceModels)
  450.         throws OrekitException {

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

  453.         // threshold for each parameter
  454.         final double epsilon    = 1.0e-13;
  455.         final double thresholdA = epsilon * (1 + FastMath.abs(meanOrbit.getA()));
  456.         final double thresholdE = epsilon * (1 + meanOrbit.getE());
  457.         final double thresholdI = epsilon * (1 + meanOrbit.getI());
  458.         final double thresholdL = epsilon * FastMath.PI;

  459.         // ensure all Gaussian force models can rely on attitude
  460.         for (final DSSTForceModel force : forceModels) {
  461.             force.registerAttitudeProvider(attitudeProvider);
  462.         }

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

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

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

  468.             // Set the force models
  469.             final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
  470.             for (final DSSTForceModel force : forceModels) {
  471.                 shortPeriodTerms.addAll(force.initialize(aux, false));
  472.                 force.updateShortPeriodTerms(meanState);
  473.             }

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

  476.             // adapted parameters residuals
  477.             final double deltaA  = osculating.getA() - rebuilt.getA();
  478.             final double deltaEx = osculating.getEquinoctialEx() - rebuilt.getEquinoctialEx();
  479.             final double deltaEy = osculating.getEquinoctialEy() - rebuilt.getEquinoctialEy();
  480.             final double deltaHx = osculating.getHx() - rebuilt.getHx();
  481.             final double deltaHy = osculating.getHy() - rebuilt.getHy();
  482.             final double deltaLv = MathUtils.normalizeAngle(osculating.getLv() - rebuilt.getLv(), 0.0);

  483.             // check convergence
  484.             if (FastMath.abs(deltaA)  < thresholdA &&
  485.                 FastMath.abs(deltaEx) < thresholdE &&
  486.                 FastMath.abs(deltaEy) < thresholdE &&
  487.                 FastMath.abs(deltaHx) < thresholdI &&
  488.                 FastMath.abs(deltaHy) < thresholdI &&
  489.                 FastMath.abs(deltaLv) < thresholdL) {
  490.                 return meanOrbit;
  491.             }

  492.             // update mean parameters
  493.             meanOrbit = new EquinoctialOrbit(meanOrbit.getA() + deltaA,
  494.                                              meanOrbit.getEquinoctialEx() + deltaEx,
  495.                                              meanOrbit.getEquinoctialEy() + deltaEy,
  496.                                              meanOrbit.getHx() + deltaHx,
  497.                                              meanOrbit.getHy() + deltaHy,
  498.                                              meanOrbit.getLv() + deltaLv,
  499.                                              PositionAngle.TRUE, meanOrbit.getFrame(),
  500.                                              meanOrbit.getDate(), meanOrbit.getMu());
  501.         }

  502.         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_DSST_MEAN_PARAMETERS, i);
  503.     }

  504.     /** Compute osculating state from mean state.
  505.      * <p>
  506.      * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
  507.      * </p>
  508.      * @param meanState initial mean state
  509.      * @param shortPeriodTerms short period terms
  510.      * @return osculating state
  511.      * @throws OrekitException if the computation of the short-periodic variation fails
  512.      */
  513.     private static EquinoctialOrbit computeOsculatingOrbit(final SpacecraftState meanState,
  514.                                                            final List<ShortPeriodTerms> shortPeriodTerms)
  515.         throws OrekitException {

  516.         final double[] mean = new double[6];
  517.         final double[] meanDot = new double[6];
  518.         OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngle.MEAN, mean, meanDot);
  519.         final double[] y = mean.clone();
  520.         for (final ShortPeriodTerms spt : shortPeriodTerms) {
  521.             final double[] shortPeriodic = spt.value(meanState.getOrbit());
  522.             for (int i = 0; i < shortPeriodic.length; i++) {
  523.                 y[i] += shortPeriodic[i];
  524.             }
  525.         }
  526.         return (EquinoctialOrbit) OrbitType.EQUINOCTIAL.mapArrayToOrbit(y, meanDot,
  527.                                                                         PositionAngle.MEAN, meanState.getDate(),
  528.                                                                         meanState.getMu(), meanState.getFrame());
  529.     }

  530.     /** {@inheritDoc} */
  531.     @Override
  532.     protected SpacecraftState getInitialIntegrationState() throws OrekitException {
  533.         if (initialIsOsculating) {
  534.             // the initial state is an osculating state,
  535.             // it must be converted to mean state
  536.             return computeMeanState(getInitialState(), getAttitudeProvider(), forceModels);
  537.         } else {
  538.             // the initial state is already a mean state
  539.             return getInitialState();
  540.         }
  541.     }

  542.     /** {@inheritDoc}
  543.      * <p>
  544.      * Note that for DSST, orbit type is hardcoded to {@link OrbitType#EQUINOCTIAL}
  545.      * and position angle type is hardcoded to {@link PositionAngle#MEAN}, so
  546.      * the corresponding parameters are ignored.
  547.      * </p>
  548.      */
  549.     @Override
  550.     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
  551.                                        final OrbitType ignoredOrbitType, final PositionAngle ignoredPositionAngleType,
  552.                                        final AttitudeProvider attitudeProvider, final Frame frame) {

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

  556.         // copy the specific settings from the existing mapper
  557.         if (mapper != null) {
  558.             newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
  559.             newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
  560.             newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
  561.         }

  562.         mapper = newMapper;
  563.         return mapper;

  564.     }

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

  567.         /** Serializable UID. */
  568.         private static final long serialVersionUID = 20151104L;

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

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

  573.         /** Short period terms. */
  574.         private List<ShortPeriodTerms>     shortPeriodTerms;

  575.         /** Simple constructor.
  576.          * @param referenceDate reference date
  577.          * @param mu central attraction coefficient (m³/s²)
  578.          * @param attitudeProvider attitude provider
  579.          * @param frame inertial frame
  580.          */
  581.         MeanPlusShortPeriodicMapper(final AbsoluteDate referenceDate, final double mu,
  582.                                     final AttitudeProvider attitudeProvider, final Frame frame) {

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

  584.             this.selectedCoefficients = null;

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

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

  588.         }

  589.         /** {@inheritDoc} */
  590.         @Override
  591.         public SpacecraftState mapArrayToState(final AbsoluteDate date,
  592.                                                final double[] y, final double[] yDot,
  593.                                                final boolean meanOnly)
  594.             throws OrekitException {

  595.             // add short periodic variations to mean elements to get osculating elements
  596.             // (the loop may not be performed if there are no force models and in the
  597.             //  case we want to remain in mean parameters only)
  598.             final double[] elements = y.clone();
  599.             final Map<String, double[]> coefficients;
  600.             if (meanOnly) {
  601.                 coefficients = null;
  602.             } else {
  603.                 final Orbit meanOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngle.MEAN, date, getMu(), getFrame());
  604.                 coefficients = selectedCoefficients == null ? null : new HashMap<String, double[]>();
  605.                 for (final ShortPeriodTerms spt : shortPeriodTerms) {
  606.                     final double[] shortPeriodic = spt.value(meanOrbit);
  607.                     for (int i = 0; i < shortPeriodic.length; i++) {
  608.                         elements[i] += shortPeriodic[i];
  609.                     }
  610.                     if (selectedCoefficients != null) {
  611.                         coefficients.putAll(spt.getCoefficients(date, selectedCoefficients));
  612.                     }
  613.                 }
  614.             }

  615.             final double mass = elements[6];
  616.             if (mass <= 0.0) {
  617.                 throw new OrekitException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, mass);
  618.             }

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

  621.             if (coefficients == null) {
  622.                 return new SpacecraftState(orbit, attitude, mass);
  623.             } else {
  624.                 return new SpacecraftState(orbit, attitude, mass, coefficients);
  625.             }

  626.         }

  627.         /** {@inheritDoc} */
  628.         @Override
  629.         public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot)
  630.             throws OrekitException {

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

  633.         }

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

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

  652.         /** Set the selected short periodic coefficients that must be stored as additional states.
  653.          * @param selectedCoefficients short periodic coefficients that must be stored as additional states
  654.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  655.          */
  656.         public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
  657.             this.selectedCoefficients = selectedCoefficients;
  658.         }

  659.         /** Get the selected short periodic coefficients that must be stored as additional states.
  660.          * @return short periodic coefficients that must be stored as additional states
  661.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  662.          */
  663.         public Set<String> getSelectedCoefficients() {
  664.             return selectedCoefficients;
  665.         }

  666.         /** Set the short period terms.
  667.          * @param shortPeriodTerms short period terms
  668.          * @since 7.1
  669.          */
  670.         public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
  671.             this.shortPeriodTerms = shortPeriodTerms;
  672.         }

  673.         /** Get the short period terms.
  674.          * @return shortPeriodTerms short period terms
  675.          * @since 7.1
  676.          */
  677.         public List<ShortPeriodTerms> getShortPeriodTerms() {
  678.             return shortPeriodTerms;
  679.         }

  680.         /** Replace the instance with a data transfer object for serialization.
  681.          * @return data transfer object that will be serialized
  682.          * @exception NotSerializableException if one of the force models cannot be serialized
  683.          */
  684.         private Object writeReplace() throws NotSerializableException {
  685.             return new DataTransferObject(getReferenceDate(), getMu(), getAttitudeProvider(), getFrame(),
  686.                                           satelliteRevolution, selectedCoefficients, shortPeriodTerms);
  687.         }

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

  690.             /** Serializable UID. */
  691.             private static final long serialVersionUID = 20151106L;

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

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

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

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

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

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

  704.             /** Short period terms. */
  705.             private final List<ShortPeriodTerms> shortPeriodTerms;

  706.             /** Simple constructor.
  707.              * @param referenceDate reference date
  708.              * @param mu central attraction coefficient (m³/s²)
  709.              * @param attitudeProvider attitude provider
  710.              * @param frame inertial frame
  711.              * @param satelliteRevolution number of satellite revolutions in the averaging interval
  712.              * @param selectedCoefficients short periodic coefficients that must be stored as additional states
  713.              * @param shortPeriodTerms short period terms
  714.              */
  715.             DataTransferObject(final AbsoluteDate referenceDate, final double mu,
  716.                                       final AttitudeProvider attitudeProvider, final Frame frame,
  717.                                       final int satelliteRevolution,
  718.                                       final Set<String> selectedCoefficients,
  719.                                       final List<ShortPeriodTerms> shortPeriodTerms) {
  720.                 this.referenceDate        = referenceDate;
  721.                 this.mu                   = mu;
  722.                 this.attitudeProvider     = attitudeProvider;
  723.                 this.frame                = frame;
  724.                 this.satelliteRevolution  = satelliteRevolution;
  725.                 this.selectedCoefficients = selectedCoefficients;
  726.                 this.shortPeriodTerms     = shortPeriodTerms;
  727.             }

  728.             /** Replace the deserialized data transfer object with a {@link MeanPlusShortPeriodicMapper}.
  729.              * @return replacement {@link MeanPlusShortPeriodicMapper}
  730.              */
  731.             private Object readResolve() {
  732.                 final MeanPlusShortPeriodicMapper mapper =
  733.                         new MeanPlusShortPeriodicMapper(referenceDate, mu, attitudeProvider, frame);
  734.                 mapper.setSatelliteRevolution(satelliteRevolution);
  735.                 mapper.setSelectedCoefficients(selectedCoefficients);
  736.                 mapper.setShortPeriodTerms(shortPeriodTerms);
  737.                 return mapper;
  738.             }

  739.         }

  740.     }

  741.     /** {@inheritDoc} */
  742.     @Override
  743.     protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
  744.         return new Main(integrator);
  745.     }

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

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

  750.         /** Simple constructor.
  751.          * @param integrator numerical integrator to use for propagation.
  752.          */
  753.         Main(final ODEIntegrator integrator) {
  754.             yDot = new double[7];

  755.             for (final DSSTForceModel forceModel : forceModels) {
  756.                 final EventDetector[] modelDetectors = forceModel.getEventsDetectors();
  757.                 if (modelDetectors != null) {
  758.                     for (final EventDetector detector : modelDetectors) {
  759.                         setUpEventDetector(integrator, detector);
  760.                     }
  761.                 }
  762.             }

  763.         }

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

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

  769.             // initialize all perturbing forces
  770.             for (final DSSTForceModel force : forceModels) {
  771.                 force.initializeStep(aux);
  772.             }

  773.             Arrays.fill(yDot, 0.0);

  774.             // compute the contributions of all perturbing forces
  775.             for (final DSSTForceModel forceModel : forceModels) {
  776.                 final double[] daidt = forceModel.getMeanElementRate(state);
  777.                 for (int i = 0; i < daidt.length; i++) {
  778.                     yDot[i] += daidt[i];
  779.                 }
  780.             }

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

  784.             return yDot.clone();
  785.         }

  786.     }

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

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

  817.     }

  818.     /** Step handler used to compute the parameters for the short periodic contributions.
  819.      * @author Lucian Barbulescu
  820.      */
  821.     private class ShortPeriodicsHandler implements ODEStepHandler {

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

  824.         /** Constructor.
  825.          * @param forceModels force models
  826.          */
  827.         ShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
  828.             this.forceModels = forceModels;
  829.         }

  830.         /** {@inheritDoc} */
  831.         @Override
  832.         public void init(final ODEStateAndDerivative initialState, final double finalTime)
  833.             throws OrekitExceptionWrapper {
  834.             try {
  835.                 // Build the mean state interpolated at initial point
  836.                 final SpacecraftState meanStates = mapper.mapArrayToState(0.0,
  837.                                                                           initialState.getPrimaryState(),
  838.                                                                           initialState.getPrimaryDerivative(),
  839.                                                                           true);

  840.                 // Compute short periodic coefficients for this point
  841.                 for (DSSTForceModel forceModel : forceModels) {
  842.                     forceModel.updateShortPeriodTerms(meanStates);

  843.                 }
  844.             } catch (OrekitException oe) {
  845.                 throw new OrekitExceptionWrapper(oe);
  846.             }

  847.         }

  848.         /** {@inheritDoc} */
  849.         @Override
  850.         public void handleStep(final ODEStateInterpolator interpolator, final boolean isLast)
  851.             throws OrekitExceptionWrapper {

  852.             try {
  853.                 // Get the grid points to compute
  854.                 final double[] interpolationPoints =
  855.                         interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
  856.                                                         interpolator.getCurrentState().getTime());

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

  859.                     // Build the mean state interpolated at grid point
  860.                     final double time = interpolationPoints[i];
  861.                     final ODEStateAndDerivative sd = interpolator.getInterpolatedState(time);
  862.                     meanStates[i] = mapper.mapArrayToState(time,
  863.                                                            sd.getPrimaryState(),
  864.                                                            sd.getPrimaryDerivative(),
  865.                                                            true);

  866.                 }

  867.                 // Computate short periodic coefficients for this step
  868.                 for (DSSTForceModel forceModel : forceModels) {
  869.                     forceModel.updateShortPeriodTerms(meanStates);
  870.                 }

  871.             } catch (OrekitException oe) {
  872.                 throw new OrekitExceptionWrapper(oe);
  873.             }

  874.         }
  875.     }
  876. }