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