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