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