1   /* Copyright 2002-2022 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  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Set;
26  
27  import org.hipparchus.linear.RealMatrix;
28  import org.hipparchus.ode.ODEIntegrator;
29  import org.hipparchus.ode.ODEStateAndDerivative;
30  import org.hipparchus.ode.sampling.ODEStateInterpolator;
31  import org.hipparchus.ode.sampling.ODEStepHandler;
32  import org.hipparchus.util.FastMath;
33  import org.hipparchus.util.MathUtils;
34  import org.orekit.annotation.DefaultDataContext;
35  import org.orekit.attitudes.Attitude;
36  import org.orekit.attitudes.AttitudeProvider;
37  import org.orekit.data.DataContext;
38  import org.orekit.errors.OrekitException;
39  import org.orekit.errors.OrekitInternalError;
40  import org.orekit.errors.OrekitMessages;
41  import org.orekit.frames.Frame;
42  import org.orekit.orbits.EquinoctialOrbit;
43  import org.orekit.orbits.Orbit;
44  import org.orekit.orbits.OrbitType;
45  import org.orekit.orbits.PositionAngle;
46  import org.orekit.propagation.AbstractMatricesHarvester;
47  import org.orekit.propagation.MatricesHarvester;
48  import org.orekit.propagation.PropagationType;
49  import org.orekit.propagation.Propagator;
50  import org.orekit.propagation.SpacecraftState;
51  import org.orekit.propagation.events.EventDetector;
52  import org.orekit.propagation.integration.AbstractIntegratedPropagator;
53  import org.orekit.propagation.integration.AdditionalDerivativesProvider;
54  import org.orekit.propagation.integration.StateMapper;
55  import org.orekit.propagation.numerical.NumericalPropagator;
56  import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
57  import org.orekit.propagation.semianalytical.dsst.forces.DSSTNewtonianAttraction;
58  import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
59  import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
60  import org.orekit.propagation.semianalytical.dsst.utilities.FixedNumberInterpolationGrid;
61  import org.orekit.propagation.semianalytical.dsst.utilities.InterpolationGrid;
62  import org.orekit.propagation.semianalytical.dsst.utilities.MaxGapInterpolationGrid;
63  import org.orekit.time.AbsoluteDate;
64  import org.orekit.utils.DoubleArrayDictionary;
65  import org.orekit.utils.ParameterDriver;
66  import org.orekit.utils.ParameterDriversList;
67  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
68  import org.orekit.utils.ParameterObserver;
69  
70  /**
71   * This class propagates {@link org.orekit.orbits.Orbit orbits} using the DSST theory.
72   * <p>
73   * Whereas analytical propagators are configured only thanks to their various
74   * constructors and can be used immediately after construction, such a semianalytical
75   * propagator configuration involves setting several parameters between construction
76   * time and propagation time, just as numerical propagators.
77   * </p>
78   * <p>
79   * The configuration parameters that can be set are:
80   * </p>
81   * <ul>
82   * <li>the initial spacecraft state ({@link #setInitialState(SpacecraftState)})</li>
83   * <li>the various force models ({@link #addForceModel(DSSTForceModel)},
84   * {@link #removeForceModels()})</li>
85   * <li>the discrete events that should be triggered during propagation (
86   * {@link #addEventDetector(org.orekit.propagation.events.EventDetector)},
87   * {@link #clearEventsDetectors()})</li>
88   * <li>the binding logic with the rest of the application ({@link #getMultiplexer()})</li>
89   * </ul>
90   * <p>
91   * From these configuration parameters, only the initial state is mandatory.
92   * The default propagation settings are in {@link OrbitType#EQUINOCTIAL equinoctial}
93   * parameters with {@link PositionAngle#TRUE true} longitude argument.
94   * The central attraction coefficient used to define the initial orbit will be used.
95   * However, specifying only the initial state would mean the propagator would use
96   * only Keplerian forces. In this case, the simpler
97   * {@link org.orekit.propagation.analytical.KeplerianPropagator KeplerianPropagator}
98   * class would be more effective.
99   * </p>
100  * <p>
101  * The underlying numerical integrator set up in the constructor may also have
102  * its own configuration parameters. Typical configuration parameters for adaptive
103  * stepsize integrators are the min, max and perhaps start step size as well as
104  * the absolute and/or relative errors thresholds.
105  * </p>
106  * <p>
107  * The state that is seen by the integrator is a simple six elements double array.
108  * These six elements are:
109  * <ul>
110  * <li>the {@link org.orekit.orbits.EquinoctialOrbit equinoctial orbit parameters}
111  * (a, e<sub>x</sub>, e<sub>y</sub>, h<sub>x</sub>, h<sub>y</sub>, λ<sub>m</sub>)
112  * in meters and radians,</li>
113  * </ul>
114  *
115  * <p>By default, at the end of the propagation, the propagator resets the initial state to the final state,
116  * thus allowing a new propagation to be started from there without recomputing the part already performed.
117  * This behaviour can be chenged by calling {@link #setResetAtEnd(boolean)}.
118  * </p>
119  * <p>Beware the same instance cannot be used simultaneously by different threads, the class is <em>not</em>
120  * thread-safe.</p>
121  *
122  * @see SpacecraftState
123  * @see DSSTForceModel
124  * @author Romain Di Costanzo
125  * @author Pascal Parraud
126  */
127 public class DSSTPropagator extends AbstractIntegratedPropagator {
128 
129     /** Retrograde factor I.
130      *  <p>
131      *  DSST model needs equinoctial orbit as internal representation.
132      *  Classical equinoctial elements have discontinuities when inclination
133      *  is close to zero. In this representation, I = +1. <br>
134      *  To avoid this discontinuity, another representation exists and equinoctial
135      *  elements can be expressed in a different way, called "retrograde" orbit.
136      *  This implies I = -1. <br>
137      *  As Orekit doesn't implement the retrograde orbit, I is always set to +1.
138      *  But for the sake of consistency with the theory, the retrograde factor
139      *  has been kept in the formulas.
140      *  </p>
141      */
142     private static final int I = 1;
143 
144     /** Default value for epsilon. */
145     private static final double EPSILON_DEFAULT = 1.0e-13;
146 
147     /** Default value for maxIterations. */
148     private static final int MAX_ITERATIONS_DEFAULT = 200;
149 
150     /** Number of grid points per integration step to be used in interpolation of short periodics coefficients.*/
151     private static final int INTERPOLATION_POINTS_PER_STEP = 3;
152 
153     /** Flag specifying whether the initial orbital state is given with osculating elements. */
154     private boolean initialIsOsculating;
155 
156     /** Force models used to compute short periodic terms. */
157     private final transient List<DSSTForceModel> forceModels;
158 
159     /** State mapper holding the force models. */
160     private MeanPlusShortPeriodicMapper mapper;
161 
162     /** Generator for the interpolation grid. */
163     private InterpolationGrid interpolationgrid;
164 
165     /** Create a new instance of DSSTPropagator.
166      *  <p>
167      *  After creation, there are no perturbing forces at all.
168      *  This means that if {@link #addForceModel addForceModel}
169      *  is not called after creation, the integrated orbit will
170      *  follow a Keplerian evolution only.
171      *  </p>
172      *
173      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
174      *
175      *  @param integrator numerical integrator to use for propagation.
176      *  @param propagationType type of orbit to output (mean or osculating).
177      * @see #DSSTPropagator(ODEIntegrator, PropagationType, AttitudeProvider)
178      */
179     @DefaultDataContext
180     public DSSTPropagator(final ODEIntegrator integrator, final PropagationType propagationType) {
181         this(integrator, propagationType,
182                 Propagator.getDefaultLaw(DataContext.getDefault().getFrames()));
183     }
184 
185     /** Create a new instance of DSSTPropagator.
186      *  <p>
187      *  After creation, there are no perturbing forces at all.
188      *  This means that if {@link #addForceModel addForceModel}
189      *  is not called after creation, the integrated orbit will
190      *  follow a Keplerian evolution only.
191      *  </p>
192      * @param integrator numerical integrator to use for propagation.
193      * @param propagationType type of orbit to output (mean or osculating).
194      * @param attitudeProvider the attitude law.
195      * @since 10.1
196      */
197     public DSSTPropagator(final ODEIntegrator integrator,
198                           final PropagationType propagationType,
199                           final AttitudeProvider attitudeProvider) {
200         super(integrator, propagationType);
201         forceModels = new ArrayList<DSSTForceModel>();
202         initMapper();
203         // DSST uses only equinoctial orbits and mean longitude argument
204         setOrbitType(OrbitType.EQUINOCTIAL);
205         setPositionAngleType(PositionAngle.MEAN);
206         setAttitudeProvider(attitudeProvider);
207         setInterpolationGridToFixedNumberOfPoints(INTERPOLATION_POINTS_PER_STEP);
208     }
209 
210 
211     /** Create a new instance of DSSTPropagator.
212      *  <p>
213      *  After creation, there are no perturbing forces at all.
214      *  This means that if {@link #addForceModel addForceModel}
215      *  is not called after creation, the integrated orbit will
216      *  follow a Keplerian evolution only. Only the mean orbits
217      *  will be generated.
218      *  </p>
219      *
220      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
221      *
222      *  @param integrator numerical integrator to use for propagation.
223      * @see #DSSTPropagator(ODEIntegrator, PropagationType, AttitudeProvider)
224      */
225     @DefaultDataContext
226     public DSSTPropagator(final ODEIntegrator integrator) {
227         this(integrator, PropagationType.MEAN);
228     }
229 
230     /** Set the central attraction coefficient μ.
231      * <p>
232      * Setting the central attraction coefficient is
233      * equivalent to {@link #addForceModel(DSSTForceModel) add}
234      * a {@link DSSTNewtonianAttraction} force model.
235      * </p>
236     * @param mu central attraction coefficient (m³/s²)
237     * @see #addForceModel(DSSTForceModel)
238     * @see #getAllForceModels()
239     */
240     public void setMu(final double mu) {
241         addForceModel(new DSSTNewtonianAttraction(mu));
242     }
243 
244     /** Set the central attraction coefficient μ only in upper class.
245      * @param mu central attraction coefficient (m³/s²)
246      */
247     private void superSetMu(final double mu) {
248         super.setMu(mu);
249     }
250 
251     /** Check if Newtonian attraction force model is available.
252      * <p>
253      * Newtonian attraction is always the last force model in the list.
254      * </p>
255      * @return true if Newtonian attraction force model is available
256      */
257     private boolean hasNewtonianAttraction() {
258         final int last = forceModels.size() - 1;
259         return last >= 0 && forceModels.get(last) instanceof DSSTNewtonianAttraction;
260     }
261 
262     /** Set the initial state with osculating orbital elements.
263      *  @param initialState initial state (defined with osculating elements)
264      */
265     public void setInitialState(final SpacecraftState initialState) {
266         setInitialState(initialState, PropagationType.OSCULATING);
267     }
268 
269     /** Set the initial state.
270      *  @param initialState initial state
271      *  @param stateType defined if the orbital state is defined with osculating or mean elements
272      */
273     public void setInitialState(final SpacecraftState initialState,
274                                 final PropagationType stateType) {
275         switch (stateType) {
276             case MEAN:
277                 initialIsOsculating = false;
278                 break;
279             case OSCULATING:
280                 initialIsOsculating = true;
281                 break;
282             default:
283                 throw new OrekitInternalError(null);
284         }
285         resetInitialState(initialState);
286     }
287 
288     /** Reset the initial state.
289      *
290      *  @param state new initial state
291      */
292     @Override
293     public void resetInitialState(final SpacecraftState state) {
294         super.resetInitialState(state);
295         if (!hasNewtonianAttraction()) {
296             // use the state to define central attraction
297             setMu(state.getMu());
298         }
299         super.setStartDate(state.getDate());
300     }
301 
302     /** Set the selected short periodic coefficients that must be stored as additional states.
303      * @param selectedCoefficients short periodic coefficients that must be stored as additional states
304      * (null means no coefficients are selected, empty set means all coefficients are selected)
305      */
306     public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
307         mapper.setSelectedCoefficients(selectedCoefficients == null ?
308                                        null : new HashSet<String>(selectedCoefficients));
309     }
310 
311     /** Get the selected short periodic coefficients that must be stored as additional states.
312      * @return short periodic coefficients that must be stored as additional states
313      * (null means no coefficients are selected, empty set means all coefficients are selected)
314      */
315     public Set<String> getSelectedCoefficients() {
316         final Set<String> set = mapper.getSelectedCoefficients();
317         return set == null ? null : Collections.unmodifiableSet(set);
318     }
319 
320     /** Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
321      * @return names of the parameters (i.e. columns) of the Jacobian matrix
322      */
323     protected List<String> getJacobiansColumnsNames() {
324         final List<String> columnsNames = new ArrayList<>();
325         for (final DSSTForceModel forceModel : getAllForceModels()) {
326             for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
327                 if (driver.isSelected() && !columnsNames.contains(driver.getName())) {
328                     columnsNames.add(driver.getName());
329                 }
330             }
331         }
332         Collections.sort(columnsNames);
333         return columnsNames;
334     }
335 
336     /** {@inheritDoc} */
337     @Override
338     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
339                                                         final DoubleArrayDictionary initialJacobianColumns) {
340         return new DSSTHarvester(this, stmName, initialStm, initialJacobianColumns);
341     }
342 
343     /** {@inheritDoc} */
344     @Override
345     protected void setUpStmAndJacobianGenerators() {
346 
347         final AbstractMatricesHarvester harvester = getHarvester();
348         if (harvester != null) {
349 
350             // set up the additional equations and additional state providers
351             final DSSTStateTransitionMatrixGenerator stmGenerator = setUpStmGenerator();
352             setUpRegularParametersJacobiansColumns(stmGenerator);
353 
354             // as we are now starting the propagation, everything is configured
355             // we can freeze the names in the harvester
356             harvester.freezeColumnsNames();
357 
358         }
359 
360     }
361 
362     /** Set up the State Transition Matrix Generator.
363      * @return State Transition Matrix Generator
364      * @since 11.1
365      */
366     private DSSTStateTransitionMatrixGenerator setUpStmGenerator() {
367 
368         final AbstractMatricesHarvester harvester = getHarvester();
369 
370         // add the STM generator corresponding to the current settings, and setup state accordingly
371         DSSTStateTransitionMatrixGenerator stmGenerator = null;
372         for (final AdditionalDerivativesProvider equations : getAdditionalDerivativesProviders()) {
373             if (equations instanceof DSSTStateTransitionMatrixGenerator &&
374                 equations.getName().equals(harvester.getStmName())) {
375                 // the STM generator has already been set up in a previous propagation
376                 stmGenerator = (DSSTStateTransitionMatrixGenerator) equations;
377                 break;
378             }
379         }
380         if (stmGenerator == null) {
381             // this is the first time we need the STM generate, create it
382             stmGenerator = new DSSTStateTransitionMatrixGenerator(harvester.getStmName(),
383                                                                   getAllForceModels(),
384                                                                   getAttitudeProvider());
385             addAdditionalDerivativesProvider(stmGenerator);
386         }
387 
388         if (!getInitialIntegrationState().hasAdditionalState(harvester.getStmName())) {
389             // add the initial State Transition Matrix if it is not already there
390             // (perhaps due to a previous propagation)
391             setInitialState(stmGenerator.setInitialStateTransitionMatrix(getInitialState(),
392                                                                          harvester.getInitialStateTransitionMatrix()),
393                             getPropagationType());
394         }
395 
396         return stmGenerator;
397 
398     }
399 
400     /** Set up the Jacobians columns generator for regular parameters.
401      * @param stmGenerator generator for the State Transition Matrix
402      * @since 11.1
403      */
404     private void setUpRegularParametersJacobiansColumns(final DSSTStateTransitionMatrixGenerator stmGenerator) {
405 
406         // first pass: gather all parameters (excluding trigger dates), binding similar names together
407         final ParameterDriversList selected = new ParameterDriversList();
408         for (final DSSTForceModel forceModel : getAllForceModels()) {
409             for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
410                 selected.add(driver);
411             }
412         }
413 
414         // second pass: now that shared parameter names are bound together,
415         // their selections status have been synchronized, we can filter them
416         selected.filter(true);
417 
418         // third pass: sort parameters lexicographically
419         selected.sort();
420 
421         // add the Jacobians column generators corresponding to parameters, and setup state accordingly
422         for (final DelegatingDriver driver : selected.getDrivers()) {
423 
424             DSSTIntegrableJacobianColumnGenerator generator = null;
425 
426             // check if we already have set up the providers
427             for (final AdditionalDerivativesProvider provider : getAdditionalDerivativesProviders()) {
428                 if (provider instanceof DSSTIntegrableJacobianColumnGenerator &&
429                     provider.getName().equals(driver.getName())) {
430                     // the Jacobian column generator has already been set up in a previous propagation
431                     generator = (DSSTIntegrableJacobianColumnGenerator) provider;
432                     break;
433                 }
434             }
435 
436             if (generator == null) {
437                 // this is the first time we need the Jacobian column generator, create it
438                 generator = new DSSTIntegrableJacobianColumnGenerator(stmGenerator, driver.getName());
439                 addAdditionalDerivativesProvider(generator);
440             }
441 
442             if (!getInitialIntegrationState().hasAdditionalState(driver.getName())) {
443                 // add the initial Jacobian column if it is not already there
444                 // (perhaps due to a previous propagation)
445                 setInitialState(getInitialState().addAdditionalState(driver.getName(),
446                                                                      getHarvester().getInitialJacobianColumn(driver.getName())),
447                                 getPropagationType());
448             }
449 
450         }
451 
452     }
453 
454     /** Check if the initial state is provided in osculating elements.
455      * @return true if initial state is provided in osculating elements
456      */
457     public boolean initialIsOsculating() {
458         return initialIsOsculating;
459     }
460 
461     /** Set the interpolation grid generator.
462      * <p>
463      * The generator will create an interpolation grid with a fixed
464      * number of points for each mean element integration step.
465      * </p>
466      * <p>
467      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
468      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
469      * by default the propagator is set as to 3 interpolations points per step.
470      * </p>
471      * @param interpolationPoints number of interpolation points at
472      * each integration step
473      * @see #setInterpolationGridToMaxTimeGap(double)
474      * @since 7.1
475      */
476     public void setInterpolationGridToFixedNumberOfPoints(final int interpolationPoints) {
477         interpolationgrid = new FixedNumberInterpolationGrid(interpolationPoints);
478     }
479 
480     /** Set the interpolation grid generator.
481      * <p>
482      * The generator will create an interpolation grid with a maximum
483      * time gap between interpolation points.
484      * </p>
485      * <p>
486      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
487      * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
488      * by default the propagator is set as to 3 interpolations points per step.
489      * </p>
490      * @param maxGap maximum time gap between interpolation points (seconds)
491      * @see #setInterpolationGridToFixedNumberOfPoints(int)
492      * @since 7.1
493      */
494     public void setInterpolationGridToMaxTimeGap(final double maxGap) {
495         interpolationgrid = new MaxGapInterpolationGrid(maxGap);
496     }
497 
498     /** Add a force model to the global perturbation model.
499      *  <p>
500      *  If this method is not called at all,
501      *  the integrated orbit will follow a Keplerian evolution only.
502      *  </p>
503      *  @param force perturbing {@link DSSTForceModel force} to add
504      *  @see #removeForceModels()
505      *  @see #setMu(double)
506      */
507     public void addForceModel(final DSSTForceModel force) {
508 
509         if (force instanceof DSSTNewtonianAttraction) {
510             // we want to add the central attraction force model
511 
512             // ensure we are notified of any mu change
513             force.getParametersDrivers().get(0).addObserver(new ParameterObserver() {
514                 /** {@inheritDoc} */
515                 @Override
516                 public void valueChanged(final double previousValue, final ParameterDriver driver) {
517                     superSetMu(driver.getValue());
518                 }
519             });
520 
521             if (hasNewtonianAttraction()) {
522                 // there is already a central attraction model, replace it
523                 forceModels.set(forceModels.size() - 1, force);
524             } else {
525                 // there are no central attraction model yet, add it at the end of the list
526                 forceModels.add(force);
527             }
528         } else {
529             // we want to add a perturbing force model
530             if (hasNewtonianAttraction()) {
531                 // insert the new force model before Newtonian attraction,
532                 // which should always be the last one in the list
533                 forceModels.add(forceModels.size() - 1, force);
534             } else {
535                 // we only have perturbing force models up to now, just append at the end of the list
536                 forceModels.add(force);
537             }
538         }
539 
540         force.registerAttitudeProvider(getAttitudeProvider());
541 
542     }
543 
544     /** Remove all perturbing force models from the global perturbation model
545      *  (except central attraction).
546      *  <p>
547      *  Once all perturbing forces have been removed (and as long as no new force model is added),
548      *  the integrated orbit will follow a Keplerian evolution only.
549      *  </p>
550      *  @see #addForceModel(DSSTForceModel)
551      */
552     public void removeForceModels() {
553         final int last = forceModels.size() - 1;
554         if (hasNewtonianAttraction()) {
555             // preserve the Newtonian attraction model at the end
556             final DSSTForceModel newton = forceModels.get(last);
557             forceModels.clear();
558             forceModels.add(newton);
559         } else {
560             forceModels.clear();
561         }
562     }
563 
564     /** Get all the force models, perturbing forces and Newtonian attraction included.
565      * @return list of perturbing force models, with Newtonian attraction being the
566      * last one
567      * @see #addForceModel(DSSTForceModel)
568      * @see #setMu(double)
569      */
570     public List<DSSTForceModel> getAllForceModels() {
571         return Collections.unmodifiableList(forceModels);
572     }
573 
574     /** Get propagation parameter type.
575      * @return orbit type used for propagation
576      */
577     public OrbitType getOrbitType() {
578         return super.getOrbitType();
579     }
580 
581     /** Get propagation parameter type.
582      * @return angle type to use for propagation
583      */
584     public PositionAngle getPositionAngleType() {
585         return super.getPositionAngleType();
586     }
587 
588     /** Conversion from mean to osculating orbit.
589      * <p>
590      * Compute osculating state <b>in a DSST sense</b>, corresponding to the
591      * mean SpacecraftState in input, and according to the Force models taken
592      * into account.
593      * </p><p>
594      * Since the osculating state is obtained by adding short-periodic variation
595      * of each force model, the resulting output will depend on the
596      * force models parameterized in input.
597      * </p>
598      * @param mean Mean state to convert
599      * @param forces Forces to take into account
600      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
601      * like atmospheric drag, radiation pressure or specific user-defined models)
602      * @return osculating state in a DSST sense
603      */
604     public static SpacecraftState computeOsculatingState(final SpacecraftState mean,
605                                                          final AttitudeProvider attitudeProvider,
606                                                          final Collection<DSSTForceModel> forces) {
607 
608         //Create the auxiliary object
609         final AuxiliaryElements aux = new AuxiliaryElements(mean.getOrbit(), I);
610 
611         // Set the force models
612         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
613         for (final DSSTForceModel force : forces) {
614             force.registerAttitudeProvider(attitudeProvider);
615             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters()));
616             force.updateShortPeriodTerms(force.getParameters(), mean);
617         }
618 
619         final EquinoctialOrbit osculatingOrbit = computeOsculatingOrbit(mean, shortPeriodTerms);
620 
621         return new SpacecraftState(osculatingOrbit, mean.getAttitude(), mean.getMass(),
622                                    mean.getAdditionalStatesValues(), mean.getAdditionalStatesDerivatives());
623 
624     }
625 
626     /** Conversion from osculating to mean orbit.
627      * <p>
628      * Compute mean state <b>in a DSST sense</b>, corresponding to the
629      * osculating SpacecraftState in input, and according to the Force models
630      * taken into account.
631      * </p><p>
632      * Since the osculating state is obtained with the computation of
633      * short-periodic variation of each force model, the resulting output will
634      * depend on the force models parameterized in input.
635      * </p><p>
636      * The computation is done through a fixed-point iteration process.
637      * </p>
638      * @param osculating Osculating state to convert
639      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
640      * like atmospheric drag, radiation pressure or specific user-defined models)
641      * @param forceModels Forces to take into account
642      * @return mean state in a DSST sense
643      */
644     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
645                                                    final AttitudeProvider attitudeProvider,
646                                                    final Collection<DSSTForceModel> forceModels) {
647         return computeMeanState(osculating, attitudeProvider, forceModels, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
648     }
649 
650     /** Conversion from osculating to mean orbit.
651      * <p>
652      * Compute mean state <b>in a DSST sense</b>, corresponding to the
653      * osculating SpacecraftState in input, and according to the Force models
654      * taken into account.
655      * </p><p>
656      * Since the osculating state is obtained with the computation of
657      * short-periodic variation of each force model, the resulting output will
658      * depend on the force models parameterized in input.
659      * </p><p>
660      * The computation is done through a fixed-point iteration process.
661      * </p>
662      * @param osculating Osculating state to convert
663      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
664      * like atmospheric drag, radiation pressure or specific user-defined models)
665      * @param forceModels Forces to take into account
666      * @param epsilon convergence threshold for mean parameters conversion
667      * @param maxIterations maximum iterations for mean parameters conversion
668      * @return mean state in a DSST sense
669      * @since 10.1
670      */
671     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
672                                                    final AttitudeProvider attitudeProvider,
673                                                    final Collection<DSSTForceModel> forceModels,
674                                                    final double epsilon,
675                                                    final int maxIterations) {
676         final Orbit meanOrbit = computeMeanOrbit(osculating, attitudeProvider, forceModels, epsilon, maxIterations);
677         return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(),
678                                    osculating.getAdditionalStatesValues(), osculating.getAdditionalStatesDerivatives());
679     }
680 
681      /** Override the default value of the parameter.
682      *  <p>
683      *  By default, if the initial orbit is defined as osculating,
684      *  it will be averaged over 2 satellite revolutions.
685      *  This can be changed by using this method.
686      *  </p>
687      *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
688      *                             elements
689      */
690     public void setSatelliteRevolution(final int satelliteRevolution) {
691         mapper.setSatelliteRevolution(satelliteRevolution);
692     }
693 
694     /** Get the number of satellite revolutions to use for converting osculating to mean elements.
695      *  @return number of satellite revolutions to use for converting osculating to mean elements
696      */
697     public int getSatelliteRevolution() {
698         return mapper.getSatelliteRevolution();
699     }
700 
701     /** Override the default value short periodic terms.
702     *  <p>
703     *  By default, short periodic terms are initialized before
704     *  the numerical integration of the mean orbital elements.
705     *  </p>
706     *  @param shortPeriodTerms short periodic terms
707     */
708     public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
709         mapper.setShortPeriodTerms(shortPeriodTerms);
710     }
711 
712    /** Get the short periodic terms.
713     *  @return the short periodic terms
714     */
715     public List<ShortPeriodTerms> getShortPeriodTerms() {
716         return mapper.getShortPeriodTerms();
717     }
718 
719     /** {@inheritDoc} */
720     @Override
721     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
722         super.setAttitudeProvider(attitudeProvider);
723 
724         //Register the attitude provider for each force model
725         for (final DSSTForceModel force : forceModels) {
726             force.registerAttitudeProvider(attitudeProvider);
727         }
728     }
729 
730     /** Method called just before integration.
731      * <p>
732      * The default implementation does nothing, it may be specialized in subclasses.
733      * </p>
734      * @param initialState initial state
735      * @param tEnd target date at which state should be propagated
736      */
737     @Override
738     protected void beforeIntegration(final SpacecraftState initialState,
739                                      final AbsoluteDate tEnd) {
740 
741         // check if only mean elements must be used
742         final PropagationType type = getPropagationType();
743 
744         // compute common auxiliary elements
745         final AuxiliaryElements aux = new AuxiliaryElements(initialState.getOrbit(), I);
746 
747         // initialize all perturbing forces
748         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
749         for (final DSSTForceModel force : forceModels) {
750             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, type, force.getParameters()));
751         }
752         mapper.setShortPeriodTerms(shortPeriodTerms);
753 
754         // if required, insert the special short periodics step handler
755         if (type == PropagationType.OSCULATING) {
756             final ShortPeriodicsHandler spHandler = new ShortPeriodicsHandler(forceModels);
757             // Compute short periodic coefficients for this point
758             for (DSSTForceModel forceModel : forceModels) {
759                 forceModel.updateShortPeriodTerms(forceModel.getParameters(), initialState);
760             }
761             final Collection<ODEStepHandler> stepHandlers = new ArrayList<ODEStepHandler>();
762             stepHandlers.add(spHandler);
763             final ODEIntegrator integrator = getIntegrator();
764             final Collection<ODEStepHandler> existing = integrator.getStepHandlers();
765             stepHandlers.addAll(existing);
766 
767             integrator.clearStepHandlers();
768 
769             // add back the existing handlers after the short periodics one
770             for (final ODEStepHandler sp : stepHandlers) {
771                 integrator.addStepHandler(sp);
772             }
773         }
774     }
775 
776     /** {@inheritDoc} */
777     @Override
778     protected void afterIntegration() {
779         // remove the special short periodics step handler if added before
780         if (getPropagationType() == PropagationType.OSCULATING) {
781             final List<ODEStepHandler> preserved = new ArrayList<ODEStepHandler>();
782             final ODEIntegrator integrator = getIntegrator();
783             for (final ODEStepHandler sp : integrator.getStepHandlers()) {
784                 if (!(sp instanceof ShortPeriodicsHandler)) {
785                     preserved.add(sp);
786                 }
787             }
788 
789             // clear the list
790             integrator.clearStepHandlers();
791 
792             // add back the step handlers that were important for the user
793             for (final ODEStepHandler sp : preserved) {
794                 integrator.addStepHandler(sp);
795             }
796         }
797     }
798 
799     /** Compute mean state from osculating state.
800      * <p>
801      * Compute in a DSST sense the mean state corresponding to the input osculating state.
802      * </p><p>
803      * The computing is done through a fixed-point iteration process.
804      * </p>
805      * @param osculating initial osculating state
806      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
807      * like atmospheric drag, radiation pressure or specific user-defined models)
808      * @param forceModels force models
809      * @param epsilon convergence threshold for mean parameters conversion
810      * @param maxIterations maximum iterations for mean parameters conversion
811      * @return mean state
812      */
813     private static Orbit computeMeanOrbit(final SpacecraftState osculating,
814                                           final AttitudeProvider attitudeProvider,
815                                           final Collection<DSSTForceModel> forceModels, final double epsilon, final int maxIterations) {
816 
817         // rough initialization of the mean parameters
818         EquinoctialOrbit meanOrbit = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(osculating.getOrbit());
819 
820         // threshold for each parameter
821         final double thresholdA = epsilon * (1 + FastMath.abs(meanOrbit.getA()));
822         final double thresholdE = epsilon * (1 + meanOrbit.getE());
823         final double thresholdI = epsilon * (1 + meanOrbit.getI());
824         final double thresholdL = epsilon * FastMath.PI;
825 
826         // ensure all Gaussian force models can rely on attitude
827         for (final DSSTForceModel force : forceModels) {
828             force.registerAttitudeProvider(attitudeProvider);
829         }
830 
831         int i = 0;
832         while (i++ < maxIterations) {
833 
834             final SpacecraftState meanState = new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass());
835 
836             //Create the auxiliary object
837             final AuxiliaryElements aux = new AuxiliaryElements(meanOrbit, I);
838 
839             // Set the force models
840             final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
841             for (final DSSTForceModel force : forceModels) {
842                 shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters()));
843                 force.updateShortPeriodTerms(force.getParameters(), meanState);
844             }
845 
846             // recompute the osculating parameters from the current mean parameters
847             final EquinoctialOrbit rebuilt = computeOsculatingOrbit(meanState, shortPeriodTerms);
848 
849             // adapted parameters residuals
850             final double deltaA  = osculating.getA() - rebuilt.getA();
851             final double deltaEx = osculating.getEquinoctialEx() - rebuilt.getEquinoctialEx();
852             final double deltaEy = osculating.getEquinoctialEy() - rebuilt.getEquinoctialEy();
853             final double deltaHx = osculating.getHx() - rebuilt.getHx();
854             final double deltaHy = osculating.getHy() - rebuilt.getHy();
855             final double deltaLv = MathUtils.normalizeAngle(osculating.getLv() - rebuilt.getLv(), 0.0);
856 
857             // check convergence
858             if (FastMath.abs(deltaA)  < thresholdA &&
859                 FastMath.abs(deltaEx) < thresholdE &&
860                 FastMath.abs(deltaEy) < thresholdE &&
861                 FastMath.abs(deltaHx) < thresholdI &&
862                 FastMath.abs(deltaHy) < thresholdI &&
863                 FastMath.abs(deltaLv) < thresholdL) {
864                 return meanOrbit;
865             }
866 
867             // update mean parameters
868             meanOrbit = new EquinoctialOrbit(meanOrbit.getA() + deltaA,
869                                              meanOrbit.getEquinoctialEx() + deltaEx,
870                                              meanOrbit.getEquinoctialEy() + deltaEy,
871                                              meanOrbit.getHx() + deltaHx,
872                                              meanOrbit.getHy() + deltaHy,
873                                              meanOrbit.getLv() + deltaLv,
874                                              PositionAngle.TRUE, meanOrbit.getFrame(),
875                                              meanOrbit.getDate(), meanOrbit.getMu());
876         }
877 
878         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_DSST_MEAN_PARAMETERS, i);
879 
880     }
881 
882     /** Compute osculating state from mean state.
883      * <p>
884      * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
885      * </p>
886      * @param meanState initial mean state
887      * @param shortPeriodTerms short period terms
888      * @return osculating state
889      */
890     private static EquinoctialOrbit computeOsculatingOrbit(final SpacecraftState meanState,
891                                                            final List<ShortPeriodTerms> shortPeriodTerms) {
892 
893         final double[] mean = new double[6];
894         final double[] meanDot = new double[6];
895         OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngle.MEAN, mean, meanDot);
896         final double[] y = mean.clone();
897         for (final ShortPeriodTerms spt : shortPeriodTerms) {
898             final double[] shortPeriodic = spt.value(meanState.getOrbit());
899             for (int i = 0; i < shortPeriodic.length; i++) {
900                 y[i] += shortPeriodic[i];
901             }
902         }
903         return (EquinoctialOrbit) OrbitType.EQUINOCTIAL.mapArrayToOrbit(y, meanDot,
904                                                                         PositionAngle.MEAN, meanState.getDate(),
905                                                                         meanState.getMu(), meanState.getFrame());
906     }
907 
908     /** {@inheritDoc} */
909     @Override
910     protected SpacecraftState getInitialIntegrationState() {
911         if (initialIsOsculating) {
912             // the initial state is an osculating state,
913             // it must be converted to mean state
914             return computeMeanState(getInitialState(), getAttitudeProvider(), forceModels);
915         } else {
916             // the initial state is already a mean state
917             return getInitialState();
918         }
919     }
920 
921     /** {@inheritDoc}
922      * <p>
923      * Note that for DSST, orbit type is hardcoded to {@link OrbitType#EQUINOCTIAL}
924      * and position angle type is hardcoded to {@link PositionAngle#MEAN}, so
925      * the corresponding parameters are ignored.
926      * </p>
927      */
928     @Override
929     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
930                                        final OrbitType ignoredOrbitType, final PositionAngle ignoredPositionAngleType,
931                                        final AttitudeProvider attitudeProvider, final Frame frame) {
932 
933         // create a mapper with the common settings provided as arguments
934         final MeanPlusShortPeriodicMapper newMapper =
935                 new MeanPlusShortPeriodicMapper(referenceDate, mu, attitudeProvider, frame);
936 
937         // copy the specific settings from the existing mapper
938         if (mapper != null) {
939             newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
940             newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
941             newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
942         }
943 
944         mapper = newMapper;
945         return mapper;
946 
947     }
948 
949 
950     /** Get the short period terms value.
951      * @param meanState the mean state
952      * @return shortPeriodTerms short period terms
953      * @since 7.1
954      */
955     public double[] getShortPeriodTermsValue(final SpacecraftState meanState) {
956         final double[] sptValue = new double[6];
957 
958         for (ShortPeriodTerms spt : mapper.getShortPeriodTerms()) {
959             final double[] shortPeriodic = spt.value(meanState.getOrbit());
960             for (int i = 0; i < shortPeriodic.length; i++) {
961                 sptValue[i] += shortPeriodic[i];
962             }
963         }
964         return sptValue;
965     }
966 
967 
968     /** Internal mapper using mean parameters plus short periodic terms. */
969     private static class MeanPlusShortPeriodicMapper extends StateMapper {
970 
971         /** Short periodic coefficients that must be stored as additional states. */
972         private Set<String>                selectedCoefficients;
973 
974         /** Number of satellite revolutions in the averaging interval. */
975         private int                        satelliteRevolution;
976 
977         /** Short period terms. */
978         private List<ShortPeriodTerms>     shortPeriodTerms;
979 
980         /** Simple constructor.
981          * @param referenceDate reference date
982          * @param mu central attraction coefficient (m³/s²)
983          * @param attitudeProvider attitude provider
984          * @param frame inertial frame
985          */
986         MeanPlusShortPeriodicMapper(final AbsoluteDate referenceDate, final double mu,
987                                     final AttitudeProvider attitudeProvider, final Frame frame) {
988 
989             super(referenceDate, mu, OrbitType.EQUINOCTIAL, PositionAngle.MEAN, attitudeProvider, frame);
990 
991             this.selectedCoefficients = null;
992 
993             // Default averaging period for conversion from osculating to mean elements
994             this.satelliteRevolution = 2;
995 
996             this.shortPeriodTerms    = Collections.emptyList();
997 
998         }
999 
1000         /** {@inheritDoc} */
1001         @Override
1002         public SpacecraftState mapArrayToState(final AbsoluteDate date,
1003                                                final double[] y, final double[] yDot,
1004                                                final PropagationType type) {
1005 
1006             // add short periodic variations to mean elements to get osculating elements
1007             // (the loop may not be performed if there are no force models and in the
1008             //  case we want to remain in mean parameters only)
1009             final double[] elements = y.clone();
1010             final DoubleArrayDictionary coefficients;
1011             if (type == PropagationType.MEAN) {
1012                 coefficients = null;
1013             } else {
1014                 final Orbit meanOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngle.MEAN, date, getMu(), getFrame());
1015                 coefficients = selectedCoefficients == null ? null : new DoubleArrayDictionary();
1016                 for (final ShortPeriodTerms spt : shortPeriodTerms) {
1017                     final double[] shortPeriodic = spt.value(meanOrbit);
1018                     for (int i = 0; i < shortPeriodic.length; i++) {
1019                         elements[i] += shortPeriodic[i];
1020                     }
1021                     if (selectedCoefficients != null) {
1022                         coefficients.putAll(spt.getCoefficients(date, selectedCoefficients));
1023                     }
1024                 }
1025             }
1026 
1027             final double mass = elements[6];
1028             if (mass <= 0.0) {
1029                 throw new OrekitException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, mass);
1030             }
1031 
1032             final Orbit orbit       = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngle.MEAN, date, getMu(), getFrame());
1033             final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());
1034 
1035             if (coefficients == null) {
1036                 return new SpacecraftState(orbit, attitude, mass);
1037             } else {
1038                 return new SpacecraftState(orbit, attitude, mass, coefficients);
1039             }
1040 
1041         }
1042 
1043         /** {@inheritDoc} */
1044         @Override
1045         public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot) {
1046 
1047             OrbitType.EQUINOCTIAL.mapOrbitToArray(state.getOrbit(), PositionAngle.MEAN, y, yDot);
1048             y[6] = state.getMass();
1049 
1050         }
1051 
1052         /** Set the number of satellite revolutions to use for converting osculating to mean elements.
1053          *  <p>
1054          *  By default, if the initial orbit is defined as osculating,
1055          *  it will be averaged over 2 satellite revolutions.
1056          *  This can be changed by using this method.
1057          *  </p>
1058          *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
1059          *                             elements
1060          */
1061         public void setSatelliteRevolution(final int satelliteRevolution) {
1062             this.satelliteRevolution = satelliteRevolution;
1063         }
1064 
1065         /** Get the number of satellite revolutions to use for converting osculating to mean elements.
1066          *  @return number of satellite revolutions to use for converting osculating to mean elements
1067          */
1068         public int getSatelliteRevolution() {
1069             return satelliteRevolution;
1070         }
1071 
1072         /** Set the selected short periodic coefficients that must be stored as additional states.
1073          * @param selectedCoefficients short periodic coefficients that must be stored as additional states
1074          * (null means no coefficients are selected, empty set means all coefficients are selected)
1075          */
1076         public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
1077             this.selectedCoefficients = selectedCoefficients;
1078         }
1079 
1080         /** Get the selected short periodic coefficients that must be stored as additional states.
1081          * @return short periodic coefficients that must be stored as additional states
1082          * (null means no coefficients are selected, empty set means all coefficients are selected)
1083          */
1084         public Set<String> getSelectedCoefficients() {
1085             return selectedCoefficients;
1086         }
1087 
1088         /** Set the short period terms.
1089          * @param shortPeriodTerms short period terms
1090          * @since 7.1
1091          */
1092         public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
1093             this.shortPeriodTerms = shortPeriodTerms;
1094         }
1095 
1096         /** Get the short period terms.
1097          * @return shortPeriodTerms short period terms
1098          * @since 7.1
1099          */
1100         public List<ShortPeriodTerms> getShortPeriodTerms() {
1101             return shortPeriodTerms;
1102         }
1103 
1104     }
1105 
1106     /** {@inheritDoc} */
1107     @Override
1108     protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
1109         return new Main(integrator);
1110     }
1111 
1112     /** Internal class for mean parameters integration. */
1113     private class Main implements MainStateEquations {
1114 
1115         /** Derivatives array. */
1116         private final double[] yDot;
1117 
1118         /** Simple constructor.
1119          * @param integrator numerical integrator to use for propagation.
1120          */
1121         Main(final ODEIntegrator integrator) {
1122             yDot = new double[7];
1123 
1124             for (final DSSTForceModel forceModel : forceModels) {
1125                 final EventDetector[] modelDetectors = forceModel.getEventsDetectors();
1126                 if (modelDetectors != null) {
1127                     for (final EventDetector detector : modelDetectors) {
1128                         setUpEventDetector(integrator, detector);
1129                     }
1130                 }
1131             }
1132 
1133         }
1134 
1135         /** {@inheritDoc} */
1136         @Override
1137         public void init(final SpacecraftState initialState, final AbsoluteDate target) {
1138             forceModels.forEach(fm -> fm.init(initialState, target));
1139         }
1140 
1141         /** {@inheritDoc} */
1142         @Override
1143         public double[] computeDerivatives(final SpacecraftState state) {
1144 
1145             Arrays.fill(yDot, 0.0);
1146 
1147             // compute common auxiliary elements
1148             final AuxiliaryElements auxiliaryElements = new AuxiliaryElements(state.getOrbit(), I);
1149 
1150             // compute the contributions of all perturbing forces
1151             for (final DSSTForceModel forceModel : forceModels) {
1152                 final double[] daidt = elementRates(forceModel, state, auxiliaryElements, forceModel.getParameters());
1153                 for (int i = 0; i < daidt.length; i++) {
1154                     yDot[i] += daidt[i];
1155                 }
1156             }
1157 
1158             return yDot.clone();
1159         }
1160 
1161         /** This method allows to compute the mean equinoctial elements rates da<sub>i</sub> / dt
1162          *  for a specific force model.
1163          *  @param forceModel force to take into account
1164          *  @param state current state
1165          *  @param auxiliaryElements auxiliary elements related to the current orbit
1166          *  @param parameters force model parameters
1167          *  @return the mean equinoctial elements rates da<sub>i</sub> / dt
1168          */
1169         private double[] elementRates(final DSSTForceModel forceModel,
1170                                       final SpacecraftState state,
1171                                       final AuxiliaryElements auxiliaryElements,
1172                                       final double[] parameters) {
1173             return forceModel.getMeanElementRate(state, auxiliaryElements, parameters);
1174         }
1175 
1176     }
1177 
1178     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
1179      *  <p>
1180      *  The errors are estimated from partial derivatives properties of orbits,
1181      *  starting from a scalar position error specified by the user.
1182      *  Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
1183      *  we get at constant energy (i.e. on a Keplerian trajectory):
1184      *
1185      *  <pre>
1186      *  V r² |dV| = mu |dr|
1187      *  </pre>
1188      *
1189      *  <p> So we deduce a scalar velocity error consistent with the position error. From here, we apply
1190      *  orbits Jacobians matrices to get consistent errors on orbital parameters.
1191      *
1192      *  <p>
1193      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
1194      *  local estimates, not global ones. So some care must be taken when using these tolerances.
1195      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
1196      *  position after several orbits integration.
1197      *  </p>
1198      *
1199      * @param dP user specified position error (m)
1200      * @param orbit reference orbit
1201      * @return a two rows array, row 0 being the absolute tolerance error
1202      *                       and row 1 being the relative tolerance error
1203      */
1204     public static double[][] tolerances(final double dP, final Orbit orbit) {
1205 
1206         return NumericalPropagator.tolerances(dP, orbit, OrbitType.EQUINOCTIAL);
1207 
1208     }
1209 
1210     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
1211      *  <p>
1212      *  The errors are estimated from partial derivatives properties of orbits,
1213      *  starting from scalar position and velocity errors specified by the user.
1214      *  <p>
1215      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
1216      *  local estimates, not global ones. So some care must be taken when using these tolerances.
1217      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
1218      *  position after several orbits integration.
1219      *  </p>
1220      *
1221      * @param dP user specified position error (m)
1222      * @param dV user specified velocity error (m/s)
1223      * @param orbit reference orbit
1224      * @return a two rows array, row 0 being the absolute tolerance error
1225      *                       and row 1 being the relative tolerance error
1226      * @since 10.3
1227      */
1228     public static double[][] tolerances(final double dP, final double dV, final Orbit orbit) {
1229 
1230         return NumericalPropagator.tolerances(dP, dV, orbit, OrbitType.EQUINOCTIAL);
1231 
1232     }
1233 
1234     /** Step handler used to compute the parameters for the short periodic contributions.
1235      * @author Lucian Barbulescu
1236      */
1237     private class ShortPeriodicsHandler implements ODEStepHandler {
1238 
1239         /** Force models used to compute short periodic terms. */
1240         private final List<DSSTForceModel> forceModels;
1241 
1242         /** Constructor.
1243          * @param forceModels force models
1244          */
1245         ShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
1246             this.forceModels = forceModels;
1247         }
1248 
1249         /** {@inheritDoc} */
1250         @Override
1251         public void handleStep(final ODEStateInterpolator interpolator) {
1252 
1253             // Get the grid points to compute
1254             final double[] interpolationPoints =
1255                     interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
1256                                                     interpolator.getCurrentState().getTime());
1257 
1258             final SpacecraftState[] meanStates = new SpacecraftState[interpolationPoints.length];
1259             for (int i = 0; i < interpolationPoints.length; ++i) {
1260 
1261                 // Build the mean state interpolated at grid point
1262                 final double time = interpolationPoints[i];
1263                 final ODEStateAndDerivative sd = interpolator.getInterpolatedState(time);
1264                 meanStates[i] = mapper.mapArrayToState(time,
1265                                                        sd.getPrimaryState(),
1266                                                        sd.getPrimaryDerivative(),
1267                                                        PropagationType.MEAN);
1268             }
1269 
1270             // Computate short periodic coefficients for this step
1271             for (DSSTForceModel forceModel : forceModels) {
1272                 forceModel.updateShortPeriodTerms(forceModel.getParameters(), meanStates);
1273             }
1274 
1275         }
1276     }
1277 }