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