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                             getPropagationType());
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                                     getPropagationType());
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         final OsculatingToMeanConverter converter = new FixedPointConverter(EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT,
702                                                                             FixedPointConverter.DEFAULT_DAMPING);
703         return computeMeanState(osculating, attitudeProvider, forceModels, converter);
704     }
705 
706     /** Conversion from osculating to mean orbit.
707      * <p>
708      * Compute mean state <b>in a DSST sense</b>, corresponding to the
709      * osculating SpacecraftState in input, and according to the Force models
710      * taken into account.
711      * </p><p>
712      * Since the osculating state is obtained with the computation of
713      * short-periodic variation of each force model, the resulting output will
714      * depend on the force models parameterized in input.
715      * </p><p>
716      * The computation is done through a fixed-point iteration process.
717      * </p>
718      * @param osculating Osculating state to convert
719      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
720      * like atmospheric drag, radiation pressure or specific user-defined models)
721      * @param forceModels Forces to take into account
722      * @param epsilon convergence threshold for mean parameters conversion
723      * @param maxIterations maximum iterations for mean parameters conversion
724      * @return mean state in a DSST sense
725      * @since 10.1
726      */
727     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
728                                                    final AttitudeProvider attitudeProvider,
729                                                    final Collection<DSSTForceModel> forceModels,
730                                                    final double epsilon,
731                                                    final int maxIterations) {
732         final OsculatingToMeanConverter converter = new FixedPointConverter(epsilon, maxIterations,
733                                                                             FixedPointConverter.DEFAULT_DAMPING);
734         return computeMeanState(osculating, attitudeProvider, forceModels, converter);
735     }
736 
737     /** Conversion from osculating to mean orbit.
738      * <p>
739      * Compute mean state <b>in a DSST sense</b>, corresponding to the
740      * osculating SpacecraftState in input, and according to the Force models
741      * taken into account.
742      * </p><p>
743      * Since the osculating state is obtained with the computation of
744      * short-periodic variation of each force model, the resulting output will
745      * depend on the force models parameterized in input.
746      * </p><p>
747      * The computation is done using the given osculating to mean orbit converter.
748      * </p>
749      * @param osculating       osculating state to convert
750      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
751      *                         like atmospheric drag, radiation pressure or specific user-defined models)
752      * @param forceModels      forces to take into account
753      * @param converter        osculating to mean orbit converter
754      * @return mean state in a DSST sense
755      * @since 13.0
756      */
757     public static SpacecraftState computeMeanState(final SpacecraftState osculating,
758                                                    final AttitudeProvider attitudeProvider,
759                                                    final Collection<DSSTForceModel> forceModels,
760                                                    final OsculatingToMeanConverter converter) {
761 
762         final MeanTheory theory = new DSSTTheory(forceModels, attitudeProvider, osculating.getMass());
763         converter.setMeanTheory(theory);
764         final Orbit meanOrbit = converter.convertToMean(osculating.getOrbit());
765         return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(),
766                                    osculating.getAdditionalDataValues(), osculating.getAdditionalStatesDerivatives());
767     }
768 
769      /** Override the default value of the parameter.
770      *  <p>
771      *  By default, if the initial orbit is defined as osculating,
772      *  it will be averaged over 2 satellite revolutions.
773      *  This can be changed by using this method.
774      *  </p>
775      *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
776      *                             elements
777      */
778     public void setSatelliteRevolution(final int satelliteRevolution) {
779         mapper.setSatelliteRevolution(satelliteRevolution);
780     }
781 
782     /** Get the number of satellite revolutions to use for converting osculating to mean elements.
783      *  @return number of satellite revolutions to use for converting osculating to mean elements
784      */
785     public int getSatelliteRevolution() {
786         return mapper.getSatelliteRevolution();
787     }
788 
789     /** Override the default value short periodic terms.
790     *  <p>
791     *  By default, short periodic terms are initialized before
792     *  the numerical integration of the mean orbital elements.
793     *  </p>
794     *  @param shortPeriodTerms short periodic terms
795     */
796     public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
797         mapper.setShortPeriodTerms(shortPeriodTerms);
798     }
799 
800    /** Get the short periodic terms.
801     *  @return the short periodic terms
802     */
803     public List<ShortPeriodTerms> getShortPeriodTerms() {
804         return mapper.getShortPeriodTerms();
805     }
806 
807     /** {@inheritDoc} */
808     @Override
809     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
810         super.setAttitudeProvider(attitudeProvider);
811 
812         //Register the attitude provider for each force model
813         for (final DSSTForceModel force : forceModels) {
814             force.registerAttitudeProvider(attitudeProvider);
815         }
816     }
817 
818     /** Method called just before integration.
819      * <p>
820      * The default implementation does nothing, it may be specialized in subclasses.
821      * </p>
822      * @param initialState initial state
823      * @param tEnd target date at which state should be propagated
824      */
825     @Override
826     protected void beforeIntegration(final SpacecraftState initialState,
827                                      final AbsoluteDate tEnd) {
828         // If this method is updated also update DSSTStateTransitionMatrixGenerator.init(...)
829 
830         // check if only mean elements must be used
831         final PropagationType type = getPropagationType();
832 
833         // compute common auxiliary elements
834         final AuxiliaryElements aux = new AuxiliaryElements(initialState.getOrbit(), I);
835 
836         // initialize all perturbing forces
837         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<>();
838         for (final DSSTForceModel force : forceModels) {
839             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, type, force.getParameters(initialState.getDate())));
840         }
841         mapper.setShortPeriodTerms(shortPeriodTerms);
842 
843         // if required, insert the special short periodics step handler
844         if (type == PropagationType.OSCULATING) {
845             final ShortPeriodicsHandler spHandler = new ShortPeriodicsHandler(forceModels);
846             // Compute short periodic coefficients for this point
847             for (DSSTForceModel forceModel : forceModels) {
848                 forceModel.updateShortPeriodTerms(forceModel.getParametersAllValues(), initialState);
849             }
850             final Collection<ODEStepHandler> stepHandlers = new ArrayList<>();
851             stepHandlers.add(spHandler);
852             final ODEIntegrator integrator = getIntegrator();
853             final Collection<ODEStepHandler> existing = integrator.getStepHandlers();
854             stepHandlers.addAll(existing);
855 
856             integrator.clearStepHandlers();
857 
858             // add back the existing handlers after the short periodics one
859             for (final ODEStepHandler sp : stepHandlers) {
860                 integrator.addStepHandler(sp);
861             }
862         }
863     }
864 
865     /** {@inheritDoc} */
866     @Override
867     protected void afterIntegration() {
868         // remove the special short periodics step handler if added before
869         if (getPropagationType() == PropagationType.OSCULATING) {
870             final List<ODEStepHandler> preserved = new ArrayList<>();
871             final ODEIntegrator integrator = getIntegrator();
872             for (final ODEStepHandler sp : integrator.getStepHandlers()) {
873                 if (!(sp instanceof ShortPeriodicsHandler)) {
874                     preserved.add(sp);
875                 }
876             }
877 
878             // clear the list
879             integrator.clearStepHandlers();
880 
881             // add back the step handlers that were important for the user
882             for (final ODEStepHandler sp : preserved) {
883                 integrator.addStepHandler(sp);
884             }
885         }
886     }
887 
888     /** Compute osculating state from mean state.
889      * <p>
890      * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
891      * </p>
892      * @param meanState initial mean state
893      * @param shortPeriodTerms short period terms
894      * @return osculating state
895      */
896     private static EquinoctialOrbit computeOsculatingOrbit(final SpacecraftState meanState,
897                                                            final List<ShortPeriodTerms> shortPeriodTerms) {
898 
899         final double[] mean = new double[6];
900         final double[] meanDot = new double[6];
901         OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngleType.MEAN, mean, meanDot);
902         final double[] y = mean.clone();
903         for (final ShortPeriodTerms spt : shortPeriodTerms) {
904             final double[] shortPeriodic = spt.value(meanState.getOrbit());
905             for (int i = 0; i < shortPeriodic.length; i++) {
906                 y[i] += shortPeriodic[i];
907             }
908         }
909         return (EquinoctialOrbit) OrbitType.EQUINOCTIAL.mapArrayToOrbit(y, meanDot,
910                                                                         PositionAngleType.MEAN, meanState.getDate(),
911                                                                         meanState.getOrbit().getMu(), meanState.getFrame());
912     }
913 
914     /** {@inheritDoc} */
915     @Override
916     protected SpacecraftState getInitialIntegrationState() {
917         if (initialIsOsculating) {
918             // the initial state is an osculating state,
919             // it must be converted to mean state
920             return computeMeanState(getInitialState(), getAttitudeProvider(), forceModels);
921         } else {
922             // the initial state is already a mean state
923             return getInitialState();
924         }
925     }
926 
927     /** {@inheritDoc}
928      * <p>
929      * Note that for DSST, orbit type is hardcoded to {@link OrbitType#EQUINOCTIAL}
930      * and position angle type is hardcoded to {@link PositionAngleType#MEAN}, so
931      * the corresponding parameters are ignored.
932      * </p>
933      */
934     @Override
935     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
936                                        final OrbitType ignoredOrbitType, final PositionAngleType ignoredPositionAngleType,
937                                        final AttitudeProvider attitudeProvider, final Frame frame) {
938 
939         // create a mapper with the common settings provided as arguments
940         final MeanPlusShortPeriodicMapper newMapper =
941                 new MeanPlusShortPeriodicMapper(referenceDate, mu, attitudeProvider, frame);
942 
943         // copy the specific settings from the existing mapper
944         if (mapper != null) {
945             newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
946             newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
947             newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
948         }
949 
950         mapper = newMapper;
951         return mapper;
952 
953     }
954 
955 
956     /** Get the short period terms value.
957      * @param meanState the mean state
958      * @return shortPeriodTerms short period terms
959      * @since 7.1
960      */
961     public double[] getShortPeriodTermsValue(final SpacecraftState meanState) {
962         final double[] sptValue = new double[6];
963 
964         for (ShortPeriodTerms spt : mapper.getShortPeriodTerms()) {
965             final double[] shortPeriodic = spt.value(meanState.getOrbit());
966             for (int i = 0; i < shortPeriodic.length; i++) {
967                 sptValue[i] += shortPeriodic[i];
968             }
969         }
970         return sptValue;
971     }
972 
973 
974     /** Internal mapper using mean parameters plus short periodic terms. */
975     private static class MeanPlusShortPeriodicMapper extends StateMapper {
976 
977         /** Short periodic coefficients that must be stored as additional states. */
978         private Set<String>                selectedCoefficients;
979 
980         /** Number of satellite revolutions in the averaging interval. */
981         private int                        satelliteRevolution;
982 
983         /** Short period terms. */
984         private List<ShortPeriodTerms>     shortPeriodTerms;
985 
986         /** Simple constructor.
987          * @param referenceDate reference date
988          * @param mu central attraction coefficient (m³/s²)
989          * @param attitudeProvider attitude provider
990          * @param frame inertial frame
991          */
992         MeanPlusShortPeriodicMapper(final AbsoluteDate referenceDate, final double mu,
993                                     final AttitudeProvider attitudeProvider, final Frame frame) {
994 
995             super(referenceDate, mu, OrbitType.EQUINOCTIAL, PositionAngleType.MEAN, attitudeProvider, frame);
996 
997             this.selectedCoefficients = null;
998 
999             // Default averaging period for conversion from osculating to mean elements
1000             this.satelliteRevolution = 2;
1001 
1002             this.shortPeriodTerms    = Collections.emptyList();
1003 
1004         }
1005 
1006         /** {@inheritDoc} */
1007         @Override
1008         public SpacecraftState mapArrayToState(final AbsoluteDate date,
1009                                                final double[] y, final double[] yDot,
1010                                                final PropagationType type) {
1011 
1012             // add short periodic variations to mean elements to get osculating elements
1013             // (the loop may not be performed if there are no force models and in the
1014             //  case we want to remain in mean parameters only)
1015             final double[] elements = y.clone();
1016             final DataDictionary coefficients;
1017             if (type == PropagationType.MEAN) {
1018                 coefficients = null;
1019             } else {
1020                 final Orbit meanOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngleType.MEAN, date, getMu(), getFrame());
1021                 coefficients = selectedCoefficients == null ? null : new DataDictionary();
1022                 for (final ShortPeriodTerms spt : shortPeriodTerms) {
1023                     final double[] shortPeriodic = spt.value(meanOrbit);
1024                     for (int i = 0; i < shortPeriodic.length; i++) {
1025                         elements[i] += shortPeriodic[i];
1026                     }
1027                     if (selectedCoefficients != null) {
1028                         coefficients.putAllDoubles(spt.getCoefficients(date, selectedCoefficients));
1029                     }
1030                 }
1031             }
1032 
1033             final double mass = elements[6];
1034             if (mass <= 0.0) {
1035                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS, mass);
1036             }
1037 
1038             final Orbit orbit       = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngleType.MEAN, date, getMu(), getFrame());
1039             final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());
1040 
1041             return new SpacecraftState(orbit, attitude, mass, coefficients, null);
1042 
1043         }
1044 
1045         /** {@inheritDoc} */
1046         @Override
1047         public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot) {
1048 
1049             OrbitType.EQUINOCTIAL.mapOrbitToArray(state.getOrbit(), PositionAngleType.MEAN, y, yDot);
1050             y[6] = state.getMass();
1051 
1052         }
1053 
1054         /** Set the number of satellite revolutions to use for converting osculating to mean elements.
1055          *  <p>
1056          *  By default, if the initial orbit is defined as osculating,
1057          *  it will be averaged over 2 satellite revolutions.
1058          *  This can be changed by using this method.
1059          *  </p>
1060          *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
1061          *                             elements
1062          */
1063         public void setSatelliteRevolution(final int satelliteRevolution) {
1064             this.satelliteRevolution = satelliteRevolution;
1065         }
1066 
1067         /** Get the number of satellite revolutions to use for converting osculating to mean elements.
1068          *  @return number of satellite revolutions to use for converting osculating to mean elements
1069          */
1070         public int getSatelliteRevolution() {
1071             return satelliteRevolution;
1072         }
1073 
1074         /** Set the selected short periodic coefficients that must be stored as additional states.
1075          * @param selectedCoefficients short periodic coefficients that must be stored as additional states
1076          * (null means no coefficients are selected, empty set means all coefficients are selected)
1077          */
1078         public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
1079             this.selectedCoefficients = selectedCoefficients;
1080         }
1081 
1082         /** Get the selected short periodic coefficients that must be stored as additional states.
1083          * @return short periodic coefficients that must be stored as additional states
1084          * (null means no coefficients are selected, empty set means all coefficients are selected)
1085          */
1086         public Set<String> getSelectedCoefficients() {
1087             return selectedCoefficients;
1088         }
1089 
1090         /** Set the short period terms.
1091          * @param shortPeriodTerms short period terms
1092          * @since 7.1
1093          */
1094         public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
1095             this.shortPeriodTerms = shortPeriodTerms;
1096         }
1097 
1098         /** Get the short period terms.
1099          * @return shortPeriodTerms short period terms
1100          * @since 7.1
1101          */
1102         public List<ShortPeriodTerms> getShortPeriodTerms() {
1103             return shortPeriodTerms;
1104         }
1105 
1106     }
1107 
1108     /** {@inheritDoc} */
1109     @Override
1110     protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
1111         return new Main(integrator);
1112     }
1113 
1114     /** Internal class for mean parameters integration. */
1115     private class Main implements MainStateEquations {
1116 
1117         /** Derivatives array. */
1118         private final double[] yDot;
1119 
1120         /** Simple constructor.
1121          * @param integrator numerical integrator to use for propagation.
1122          */
1123         Main(final ODEIntegrator integrator) {
1124             yDot = new double[7];
1125 
1126             // Setup event detectors for each force model
1127             forceModels.forEach(dsstForceModel -> dsstForceModel.getEventDetectors().
1128                                 forEach(eventDetector -> setUpEventDetector(integrator, eventDetector)));
1129         }
1130 
1131         /** {@inheritDoc} */
1132         @Override
1133         public void init(final SpacecraftState initialState, final AbsoluteDate target) {
1134             forceModels.forEach(fm -> fm.init(initialState, target));
1135         }
1136 
1137         /** {@inheritDoc} */
1138         @Override
1139         public double[] computeDerivatives(final SpacecraftState state) {
1140 
1141             Arrays.fill(yDot, 0.0);
1142 
1143             // compute common auxiliary elements
1144             final AuxiliaryElements auxiliaryElements = new AuxiliaryElements(state.getOrbit(), I);
1145 
1146             // compute the contributions of all perturbing forces
1147             for (final DSSTForceModel forceModel : forceModels) {
1148                 final double[] daidt = elementRates(forceModel, state, auxiliaryElements, forceModel.getParameters(state.getDate()));
1149                 for (int i = 0; i < daidt.length; i++) {
1150                     yDot[i] += daidt[i];
1151                 }
1152             }
1153 
1154             return yDot.clone();
1155         }
1156 
1157         /** This method allows to compute the mean equinoctial elements rates da<sub>i</sub> / dt
1158          *  for a specific force model.
1159          *  @param forceModel force to take into account
1160          *  @param state current state
1161          *  @param auxiliaryElements auxiliary elements related to the current orbit
1162          *  @param parameters force model parameters at state date (only 1 value for
1163          *  each parameter
1164          *  @return the mean equinoctial elements rates da<sub>i</sub> / dt
1165          */
1166         private double[] elementRates(final DSSTForceModel forceModel,
1167                                       final SpacecraftState state,
1168                                       final AuxiliaryElements auxiliaryElements,
1169                                       final double[] parameters) {
1170             return forceModel.getMeanElementRate(state, auxiliaryElements, parameters);
1171         }
1172 
1173     }
1174 
1175     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
1176      *  <p>
1177      *  The errors are estimated from partial derivatives properties of orbits,
1178      *  starting from a scalar position error specified by the user.
1179      *  Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
1180      *  we get at constant energy (i.e. on a Keplerian trajectory):
1181      *
1182      *  <pre>
1183      *  V r² |dV| = mu |dr|
1184      *  </pre>
1185      *
1186      *  <p> So we deduce a scalar velocity error consistent with the position error. From here, we apply
1187      *  orbits Jacobians matrices to get consistent errors on orbital parameters.
1188      *
1189      *  <p>
1190      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
1191      *  local estimates, not global ones. So some care must be taken when using these tolerances.
1192      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
1193      *  position after several orbits integration.
1194      *  </p>
1195      *
1196      * @param dP user specified position error (m)
1197      * @param orbit reference orbit
1198      * @return a two rows array, row 0 being the absolute tolerance error
1199      *                       and row 1 being the relative tolerance error
1200      * @deprecated since 13.0. Use {@link ToleranceProvider} for default and custom tolerances.
1201      */
1202     @Deprecated
1203     public static double[][] tolerances(final double dP, final Orbit orbit) {
1204         // estimate the scalar velocity error
1205         final PVCoordinates pv = orbit.getPVCoordinates();
1206         final double r2 = pv.getPosition().getNormSq();
1207         final double v  = pv.getVelocity().getNorm();
1208         final double dV = orbit.getMu() * dP / (v * r2);
1209 
1210         return DSSTPropagator.tolerances(dP, dV, orbit);
1211 
1212     }
1213 
1214     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
1215      *  <p>
1216      *  The errors are estimated from partial derivatives properties of orbits,
1217      *  starting from scalar position and velocity errors specified by the user.
1218      *  <p>
1219      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
1220      *  local estimates, not global ones. So some care must be taken when using these tolerances.
1221      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
1222      *  position after several orbits integration.
1223      *  </p>
1224      *
1225      * @param dP user specified position error (m)
1226      * @param dV user specified velocity error (m/s)
1227      * @param orbit reference orbit
1228      * @return a two rows array, row 0 being the absolute tolerance error
1229      *                       and row 1 being the relative tolerance error
1230      * @since 10.3
1231      * @deprecated since 13.0. Use {@link ToleranceProvider} for default and custom tolerances.
1232      */
1233     @Deprecated
1234     public static double[][] tolerances(final double dP, final double dV, final Orbit orbit) {
1235 
1236         return ToleranceProvider.of(CartesianToleranceProvider.of(dP, dV, CartesianToleranceProvider.DEFAULT_ABSOLUTE_MASS_TOLERANCE))
1237             .getTolerances(orbit, OrbitType.EQUINOCTIAL, PositionAngleType.MEAN);
1238     }
1239 
1240     /** Step handler used to compute the parameters for the short periodic contributions.
1241      * @author Lucian Barbulescu
1242      */
1243     private class ShortPeriodicsHandler implements ODEStepHandler {
1244 
1245         /** Force models used to compute short periodic terms. */
1246         private final List<DSSTForceModel> forceModels;
1247 
1248         /** Constructor.
1249          * @param forceModels force models
1250          */
1251         ShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
1252             this.forceModels = forceModels;
1253         }
1254 
1255         /** {@inheritDoc} */
1256         @Override
1257         public void handleStep(final ODEStateInterpolator interpolator) {
1258 
1259             // Get the grid points to compute
1260             final double[] interpolationPoints =
1261                     interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
1262                                                     interpolator.getCurrentState().getTime());
1263 
1264             final SpacecraftState[] meanStates = new SpacecraftState[interpolationPoints.length];
1265             for (int i = 0; i < interpolationPoints.length; ++i) {
1266 
1267                 // Build the mean state interpolated at grid point
1268                 final double time = interpolationPoints[i];
1269                 final ODEStateAndDerivative sd = interpolator.getInterpolatedState(time);
1270                 meanStates[i] = mapper.mapArrayToState(time,
1271                                                        sd.getPrimaryState(),
1272                                                        sd.getPrimaryDerivative(),
1273                                                        PropagationType.MEAN);
1274             }
1275 
1276             // Compute short periodic coefficients for this step
1277             for (DSSTForceModel forceModel : forceModels) {
1278                 forceModel.updateShortPeriodTerms(forceModel.getParametersAllValues(), meanStates);
1279             }
1280         }
1281     }
1282 }