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