1   /* Copyright 2002-2018 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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.analytical;
18  
19  import java.io.NotSerializableException;
20  import java.io.Serializable;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.SortedSet;
24  
25  import org.hipparchus.analysis.differentiation.DSFactory;
26  import org.hipparchus.analysis.differentiation.DerivativeStructure;
27  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.hipparchus.util.FastMath;
30  import org.hipparchus.util.MathUtils;
31  import org.orekit.attitudes.AttitudeProvider;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitInternalError;
34  import org.orekit.errors.OrekitMessages;
35  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
36  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics;
37  import org.orekit.orbits.CartesianOrbit;
38  import org.orekit.orbits.CircularOrbit;
39  import org.orekit.orbits.Orbit;
40  import org.orekit.orbits.OrbitType;
41  import org.orekit.orbits.PositionAngle;
42  import org.orekit.propagation.AdditionalStateProvider;
43  import org.orekit.propagation.SpacecraftState;
44  import org.orekit.time.AbsoluteDate;
45  import org.orekit.utils.TimeSpanMap;
46  import org.orekit.utils.TimeStampedPVCoordinates;
47  
48  /** This class propagates a {@link org.orekit.propagation.SpacecraftState}
49   *  using the analytical Eckstein-Hechler model.
50   * <p>The Eckstein-Hechler model is suited for near circular orbits
51   * (e &lt; 0.1, with poor accuracy between 0.005 and 0.1) and inclination
52   * neither equatorial (direct or retrograde) nor critical (direct or
53   * retrograde).</p>
54   * <p>
55   * Note that before version 7.0, there was a large inconsistency in the generated
56   * orbits, and it was fixed as of version 7.0 of Orekit, with a visible side effect.
57   * The problems is that if the circular parameters produced by the Eckstein-Hechler
58   * model are used to build an orbit considered to be osculating, the velocity deduced
59   * from this orbit was <em>inconsistent with the position evolution</em>! The reason is
60   * that the model includes non-Keplerian effects but it does not include a corresponding
61   * circular/Cartesian conversion. As a consequence, all subsequent computation involving
62   * velocity were wrong. This includes attitude modes like yaw compensation and Doppler
63   * effect. As this effect was considered serious enough and as accurate velocities were
64   * considered important, the propagator now generates {@link CartesianOrbit Cartesian
65   * orbits} which are built in a special way to ensure consistency throughout propagation.
66   * A side effect is that if circular parameters are rebuilt by user from these propagated
67   * Cartesian orbit, the circular parameters will generally <em>not</em> match the initial
68   * orbit (differences in semi-major axis can exceed 120 m). The position however <em>will</em>
69   * match to sub-micrometer level, and this position will be identical to the positions
70   * that were generated by previous versions (in other words, the internals of the models
71   * have not been changed, only the output parameters have been changed). The correctness
72   * of the initialization has been assessed and is good, as it allows the subsequent orbit
73   * to remain close to a numerical reference orbit.
74   * </p>
75   * <p>
76   * If users need a more definitive initialization of an Eckstein-Hechler propagator, they
77   * should consider using a {@link org.orekit.propagation.conversion.PropagatorConverter
78   * propagator converter} to initialize their Eckstein-Hechler propagator using a complete
79   * sample instead of just a single initial orbit.
80   * </p>
81   * @see Orbit
82   * @author Guylaine Prat
83   */
84  public class EcksteinHechlerPropagator extends AbstractAnalyticalPropagator implements Serializable {
85  
86      /** Serializable UID. */
87      private static final long serialVersionUID = 20151202L;
88  
89      /** Initial Eckstein-Hechler model. */
90      private EHModel initialModel;
91  
92      /** All models. */
93      private transient TimeSpanMap<EHModel> models;
94  
95      /** Reference radius of the central body attraction model (m). */
96      private double referenceRadius;
97  
98      /** Central attraction coefficient (m³/s²). */
99      private double mu;
100 
101     /** Un-normalized zonal coefficients. */
102     private double[] ck0;
103 
104     /** Build a propagator from orbit and potential provider.
105      * <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
106      * @param initialOrbit initial orbit
107      * @param provider for un-normalized zonal coefficients
108      * @exception OrekitException if the zonal coefficients cannot be retrieved or
109      * if the mean parameters cannot be computed
110      */
111     public EcksteinHechlerPropagator(final Orbit initialOrbit,
112                                      final UnnormalizedSphericalHarmonicsProvider provider)
113         throws OrekitException {
114         this(initialOrbit, DEFAULT_LAW, DEFAULT_MASS, provider,
115              provider.onDate(initialOrbit.getDate()));
116     }
117 
118     /**
119      * Private helper constructor.
120      * @param initialOrbit initial orbit
121      * @param attitude attitude provider
122      * @param mass spacecraft mass
123      * @param provider for un-normalized zonal coefficients
124      * @param harmonics {@code provider.onDate(initialOrbit.getDate())}
125      * @exception OrekitException if the zonal coefficients cannot be retrieved
126      */
127     public EcksteinHechlerPropagator(final Orbit initialOrbit,
128                                      final AttitudeProvider attitude,
129                                      final double mass,
130                                      final UnnormalizedSphericalHarmonicsProvider provider,
131                                      final UnnormalizedSphericalHarmonics harmonics)
132         throws OrekitException {
133         this(initialOrbit, attitude, mass, provider.getAe(), provider.getMu(),
134              harmonics.getUnnormalizedCnm(2, 0),
135              harmonics.getUnnormalizedCnm(3, 0),
136              harmonics.getUnnormalizedCnm(4, 0),
137              harmonics.getUnnormalizedCnm(5, 0),
138              harmonics.getUnnormalizedCnm(6, 0));
139     }
140 
141     /** Build a propagator from orbit and potential.
142      * <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
143      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
144      * are related to both the normalized coefficients
145      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
146      *  and the J<sub>n</sub> one as follows:</p>
147      *
148      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
149      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
150      *
151      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
152      *
153      * @param initialOrbit initial orbit
154      * @param referenceRadius reference radius of the Earth for the potential model (m)
155      * @param mu central attraction coefficient (m³/s²)
156      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
157      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
158      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
159      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
160      * @param c60 un-normalized zonal coefficient (about -5.41e-7 for Earth)
161      * @exception OrekitException if the mean parameters cannot be computed
162      * @see org.orekit.utils.Constants
163      */
164     public EcksteinHechlerPropagator(final Orbit initialOrbit,
165                                      final double referenceRadius, final double mu,
166                                      final double c20, final double c30, final double c40,
167                                      final double c50, final double c60)
168         throws OrekitException {
169         this(initialOrbit, DEFAULT_LAW, DEFAULT_MASS, referenceRadius, mu, c20, c30, c40, c50, c60);
170     }
171 
172     /** Build a propagator from orbit, mass and potential provider.
173      * <p>Attitude law is set to an unspecified non-null arbitrary value.</p>
174      * @param initialOrbit initial orbit
175      * @param mass spacecraft mass
176      * @param provider for un-normalized zonal coefficients
177      * @exception OrekitException if the zonal coefficients cannot be retrieved or
178      * if the mean parameters cannot be computed
179      */
180     public EcksteinHechlerPropagator(final Orbit initialOrbit, final double mass,
181                                      final UnnormalizedSphericalHarmonicsProvider provider)
182         throws OrekitException {
183         this(initialOrbit, DEFAULT_LAW, mass, provider, provider.onDate(initialOrbit.getDate()));
184     }
185 
186     /** Build a propagator from orbit, mass and potential.
187      * <p>Attitude law is set to an unspecified non-null arbitrary value.</p>
188      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
189      * are related to both the normalized coefficients
190      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
191      *  and the J<sub>n</sub> one as follows:</p>
192      *
193      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
194      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
195      *
196      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
197      *
198      * @param initialOrbit initial orbit
199      * @param mass spacecraft mass
200      * @param referenceRadius reference radius of the Earth for the potential model (m)
201      * @param mu central attraction coefficient (m³/s²)
202      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
203      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
204      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
205      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
206      * @param c60 un-normalized zonal coefficient (about -5.41e-7 for Earth)
207      * @exception OrekitException if the mean parameters cannot be computed
208      */
209     public EcksteinHechlerPropagator(final Orbit initialOrbit, final double mass,
210                                      final double referenceRadius, final double mu,
211                                      final double c20, final double c30, final double c40,
212                                      final double c50, final double c60)
213         throws OrekitException {
214         this(initialOrbit, DEFAULT_LAW, mass, referenceRadius, mu, c20, c30, c40, c50, c60);
215     }
216 
217     /** Build a propagator from orbit, attitude provider and potential provider.
218      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
219      * @param initialOrbit initial orbit
220      * @param attitudeProv attitude provider
221      * @param provider for un-normalized zonal coefficients
222      * @exception OrekitException if the zonal coefficients cannot be retrieved or
223      * if the mean parameters cannot be computed
224      */
225     public EcksteinHechlerPropagator(final Orbit initialOrbit,
226                                      final AttitudeProvider attitudeProv,
227                                      final UnnormalizedSphericalHarmonicsProvider provider)
228         throws OrekitException {
229         this(initialOrbit, attitudeProv, DEFAULT_MASS, provider, provider.onDate(initialOrbit.getDate()));
230     }
231 
232     /** Build a propagator from orbit, attitude provider and potential.
233      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
234      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
235      * are related to both the normalized coefficients
236      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
237      *  and the J<sub>n</sub> one as follows:</p>
238      *
239      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
240      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
241      *
242      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
243      *
244      * @param initialOrbit initial orbit
245      * @param attitudeProv attitude provider
246      * @param referenceRadius reference radius of the Earth for the potential model (m)
247      * @param mu central attraction coefficient (m³/s²)
248      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
249      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
250      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
251      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
252      * @param c60 un-normalized zonal coefficient (about -5.41e-7 for Earth)
253      * @exception OrekitException if the mean parameters cannot be computed
254      */
255     public EcksteinHechlerPropagator(final Orbit initialOrbit,
256                                      final AttitudeProvider attitudeProv,
257                                      final double referenceRadius, final double mu,
258                                      final double c20, final double c30, final double c40,
259                                      final double c50, final double c60)
260         throws OrekitException {
261         this(initialOrbit, attitudeProv, DEFAULT_MASS, referenceRadius, mu, c20, c30, c40, c50, c60);
262     }
263 
264     /** Build a propagator from orbit, attitude provider, mass and potential provider.
265      * @param initialOrbit initial orbit
266      * @param attitudeProv attitude provider
267      * @param mass spacecraft mass
268      * @param provider for un-normalized zonal coefficients
269      * @exception OrekitException if the zonal coefficients cannot be retrieved or
270      * if the mean parameters cannot be computed
271      */
272     public EcksteinHechlerPropagator(final Orbit initialOrbit,
273                                      final AttitudeProvider attitudeProv,
274                                      final double mass,
275                                      final UnnormalizedSphericalHarmonicsProvider provider)
276         throws OrekitException {
277         this(initialOrbit, attitudeProv, mass, provider, provider.onDate(initialOrbit.getDate()));
278     }
279 
280     /** Build a propagator from orbit, attitude provider, mass and potential.
281      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
282      * are related to both the normalized coefficients
283      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
284      *  and the J<sub>n</sub> one as follows:</p>
285      *
286      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
287      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
288      *
289      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
290      *
291      * @param initialOrbit initial orbit
292      * @param attitudeProv attitude provider
293      * @param mass spacecraft mass
294      * @param referenceRadius reference radius of the Earth for the potential model (m)
295      * @param mu central attraction coefficient (m³/s²)
296      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
297      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
298      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
299      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
300      * @param c60 un-normalized zonal coefficient (about -5.41e-7 for Earth)
301      * @exception OrekitException if the mean parameters cannot be computed
302      */
303     public EcksteinHechlerPropagator(final Orbit initialOrbit,
304                                      final AttitudeProvider attitudeProv,
305                                      final double mass,
306                                      final double referenceRadius, final double mu,
307                                      final double c20, final double c30, final double c40,
308                                      final double c50, final double c60)
309         throws OrekitException {
310 
311         super(attitudeProv);
312 
313         // store model coefficients
314         this.referenceRadius = referenceRadius;
315         this.mu  = mu;
316         this.ck0 = new double[] {
317             0.0, 0.0, c20, c30, c40, c50, c60
318         };
319 
320         // compute mean parameters
321         // transform into circular adapted parameters used by the Eckstein-Hechler model
322         resetInitialState(new SpacecraftState(initialOrbit,
323                                               attitudeProv.getAttitude(initialOrbit,
324                                                                        initialOrbit.getDate(),
325                                                                        initialOrbit.getFrame()),
326                                               mass));
327 
328     }
329 
330     /** {@inheritDoc} */
331     public void resetInitialState(final SpacecraftState state)
332         throws OrekitException {
333         super.resetInitialState(state);
334         this.initialModel = computeMeanParameters((CircularOrbit) OrbitType.CIRCULAR.convertType(state.getOrbit()),
335                                                   state.getMass());
336         this.models       = new TimeSpanMap<EHModel>(initialModel);
337     }
338 
339     /** {@inheritDoc} */
340     protected void resetIntermediateState(final SpacecraftState state, final boolean forward)
341         throws OrekitException {
342         final EHModel newModel = computeMeanParameters((CircularOrbit) OrbitType.CIRCULAR.convertType(state.getOrbit()),
343                                                        state.getMass());
344         if (forward) {
345             models.addValidAfter(newModel, state.getDate());
346         } else {
347             models.addValidBefore(newModel, state.getDate());
348         }
349     }
350 
351     /** Compute mean parameters according to the Eckstein-Hechler analytical model.
352      * @param osculating osculating orbit
353      * @param mass constant mass
354      * @return Eckstein-Hechler mean model
355      * @exception OrekitException if orbit goes outside of supported range
356      * (trajectory inside the Brillouin sphere, too eccentric, equatorial, critical
357      * inclination) or if convergence cannot be reached
358      */
359     private EHModel computeMeanParameters(final CircularOrbit osculating, final double mass)
360         throws OrekitException {
361 
362         // sanity check
363         if (osculating.getA() < referenceRadius) {
364             throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE,
365                                            osculating.getA());
366         }
367 
368         // rough initialization of the mean parameters
369         EHModel current = new EHModel(osculating, mass, referenceRadius, mu, ck0);
370 
371         // threshold for each parameter
372         final double epsilon         = 1.0e-13;
373         final double thresholdA      = epsilon * (1 + FastMath.abs(current.mean.getA()));
374         final double thresholdE      = epsilon * (1 + current.mean.getE());
375         final double thresholdAngles = epsilon * FastMath.PI;
376 
377         int i = 0;
378         while (i++ < 100) {
379 
380             // recompute the osculating parameters from the current mean parameters
381             final DerivativeStructure[] parameters = current.propagateParameters(current.mean.getDate());
382 
383             // adapted parameters residuals
384             final double deltaA      = osculating.getA()          - parameters[0].getValue();
385             final double deltaEx     = osculating.getCircularEx() - parameters[1].getValue();
386             final double deltaEy     = osculating.getCircularEy() - parameters[2].getValue();
387             final double deltaI      = osculating.getI()          - parameters[3].getValue();
388             final double deltaRAAN   = MathUtils.normalizeAngle(osculating.getRightAscensionOfAscendingNode() -
389                                                                 parameters[4].getValue(),
390                                                                 0.0);
391             final double deltaAlphaM = MathUtils.normalizeAngle(osculating.getAlphaM() - parameters[5].getValue(), 0.0);
392 
393             // update mean parameters
394             current = new EHModel(new CircularOrbit(current.mean.getA()          + deltaA,
395                                                     current.mean.getCircularEx() + deltaEx,
396                                                     current.mean.getCircularEy() + deltaEy,
397                                                     current.mean.getI()          + deltaI,
398                                                     current.mean.getRightAscensionOfAscendingNode() + deltaRAAN,
399                                                     current.mean.getAlphaM()     + deltaAlphaM,
400                                                     PositionAngle.MEAN,
401                                                     current.mean.getFrame(),
402                                                     current.mean.getDate(), mu),
403                                   mass, referenceRadius, mu, ck0);
404 
405             // check convergence
406             if ((FastMath.abs(deltaA)      < thresholdA) &&
407                 (FastMath.abs(deltaEx)     < thresholdE) &&
408                 (FastMath.abs(deltaEy)     < thresholdE) &&
409                 (FastMath.abs(deltaI)      < thresholdAngles) &&
410                 (FastMath.abs(deltaRAAN)   < thresholdAngles) &&
411                 (FastMath.abs(deltaAlphaM) < thresholdAngles)) {
412                 return current;
413             }
414 
415         }
416 
417         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_ECKSTEIN_HECHLER_MEAN_PARAMETERS, i);
418 
419     }
420 
421     /** {@inheritDoc} */
422     public CartesianOrbit propagateOrbit(final AbsoluteDate date)
423         throws OrekitException {
424         // compute Cartesian parameters, taking derivatives into account
425         // to make sure velocity and acceleration are consistent
426         final EHModel current = models.get(date);
427         return new CartesianOrbit(toCartesian(date, current.propagateParameters(date)),
428                                   current.mean.getFrame(), mu);
429     }
430 
431     /** Local class for Eckstein-Hechler model, with fixed mean parameters. */
432     private static class EHModel implements Serializable {
433 
434         /** Serializable UID. */
435         private static final long serialVersionUID = 20160115L;
436 
437         /** Factory for derivatives. */
438         private static final DSFactory FACTORY = new DSFactory(1, 2);
439 
440         /** Mean orbit. */
441         private final CircularOrbit mean;
442 
443         /** Constant mass. */
444         private final double mass;
445 
446         // CHECKSTYLE: stop JavadocVariable check
447 
448         // preprocessed values
449         private final double xnotDot;
450         private final double rdpom;
451         private final double rdpomp;
452         private final double eps1;
453         private final double eps2;
454         private final double xim;
455         private final double ommD;
456         private final double rdl;
457         private final double aMD;
458 
459         private final double kh;
460         private final double kl;
461 
462         private final double ax1;
463         private final double ay1;
464         private final double as1;
465         private final double ac2;
466         private final double axy3;
467         private final double as3;
468         private final double ac4;
469         private final double as5;
470         private final double ac6;
471 
472         private final double ex1;
473         private final double exx2;
474         private final double exy2;
475         private final double ex3;
476         private final double ex4;
477 
478         private final double ey1;
479         private final double eyx2;
480         private final double eyy2;
481         private final double ey3;
482         private final double ey4;
483 
484         private final double rx1;
485         private final double ry1;
486         private final double r2;
487         private final double r3;
488         private final double rl;
489 
490         private final double iy1;
491         private final double ix1;
492         private final double i2;
493         private final double i3;
494         private final double ih;
495 
496         private final double lx1;
497         private final double ly1;
498         private final double l2;
499         private final double l3;
500         private final double ll;
501 
502         // CHECKSTYLE: resume JavadocVariable check
503 
504         /** Create a model for specified mean orbit.
505          * @param mean mean orbit
506          * @param mass constant mass
507          * @param referenceRadius reference radius of the central body attraction model (m)
508          * @param mu central attraction coefficient (m³/s²)
509          * @param ck0 un-normalized zonal coefficients
510          * @exception OrekitException if mean orbit is not within model supported domain
511          */
512         EHModel(final CircularOrbit mean, final double mass,
513                 final double referenceRadius, final double mu, final double[] ck0)
514             throws OrekitException {
515 
516             this.mean            = mean;
517             this.mass            = mass;
518 
519             // preliminary processing
520             double q = referenceRadius / mean.getA();
521             double ql = q * q;
522             final double g2 = ck0[2] * ql;
523             ql *= q;
524             final double g3 = ck0[3] * ql;
525             ql *= q;
526             final double g4 = ck0[4] * ql;
527             ql *= q;
528             final double g5 = ck0[5] * ql;
529             ql *= q;
530             final double g6 = ck0[6] * ql;
531 
532             final double cosI1 = FastMath.cos(mean.getI());
533             final double sinI1 = FastMath.sin(mean.getI());
534             final double sinI2 = sinI1 * sinI1;
535             final double sinI4 = sinI2 * sinI2;
536             final double sinI6 = sinI2 * sinI4;
537 
538             if (sinI2 < 1.0e-10) {
539                 throw new OrekitException(OrekitMessages.ALMOST_EQUATORIAL_ORBIT,
540                                           FastMath.toDegrees(mean.getI()));
541             }
542 
543             if (FastMath.abs(sinI2 - 4.0 / 5.0) < 1.0e-3) {
544                 throw new OrekitException(OrekitMessages.ALMOST_CRITICALLY_INCLINED_ORBIT,
545                                           FastMath.toDegrees(mean.getI()));
546             }
547 
548             if (mean.getE() > 0.1) {
549                 // if 0.005 < e < 0.1 no error is triggered, but accuracy is poor
550                 throw new OrekitException(OrekitMessages.TOO_LARGE_ECCENTRICITY_FOR_PROPAGATION_MODEL,
551                                           mean.getE());
552             }
553 
554             xnotDot = FastMath.sqrt(mu / mean.getA()) / mean.getA();
555 
556             rdpom = -0.75 * g2 * (4.0 - 5.0 * sinI2);
557             rdpomp = 7.5 * g4 * (1.0 - 31.0 / 8.0 * sinI2 + 49.0 / 16.0 * sinI4) -
558                     13.125 * g6 * (1.0 - 8.0 * sinI2 + 129.0 / 8.0 * sinI4 - 297.0 / 32.0 * sinI6);
559 
560             q = 3.0 / (32.0 * rdpom);
561             eps1 = q * g4 * sinI2 * (30.0 - 35.0 * sinI2) -
562                     175.0 * q * g6 * sinI2 * (1.0 - 3.0 * sinI2 + 2.0625 * sinI4);
563             q = 3.0 * sinI1 / (8.0 * rdpom);
564             eps2 = q * g3 * (4.0 - 5.0 * sinI2) - q * g5 * (10.0 - 35.0 * sinI2 + 26.25 * sinI4);
565 
566             xim = mean.getI();
567             ommD = cosI1 * (1.50    * g2 - 2.25 * g2 * g2 * (2.5 - 19.0 / 6.0 * sinI2) +
568                             0.9375  * g4 * (7.0 * sinI2 - 4.0) +
569                             3.28125 * g6 * (2.0 - 9.0 * sinI2 + 8.25 * sinI4));
570 
571             rdl = 1.0 - 1.50 * g2 * (3.0 - 4.0 * sinI2);
572             aMD = rdl +
573                     2.25 * g2 * g2 * (9.0 - 263.0 / 12.0 * sinI2 + 341.0 / 24.0 * sinI4) +
574                     15.0 / 16.0 * g4 * (8.0 - 31.0 * sinI2 + 24.5 * sinI4) +
575                     105.0 / 32.0 * g6 * (-10.0 / 3.0 + 25.0 * sinI2 - 48.75 * sinI4 + 27.5 * sinI6);
576 
577             final double qq = -1.5 * g2 / rdl;
578             final double qA   = 0.75 * g2 * g2 * sinI2;
579             final double qB   = 0.25 * g4 * sinI2;
580             final double qC   = 105.0 / 16.0 * g6 * sinI2;
581             final double qD   = -0.75 * g3 * sinI1;
582             final double qE   = 3.75 * g5 * sinI1;
583             kh = 0.375 / rdpom;
584             kl = kh / sinI1;
585 
586             ax1 = qq * (2.0 - 3.5 * sinI2);
587             ay1 = qq * (2.0 - 2.5 * sinI2);
588             as1 = qD * (4.0 - 5.0 * sinI2) +
589                   qE * (2.625 * sinI4 - 3.5 * sinI2 + 1.0);
590             ac2 = qq * sinI2 +
591                   qA * 7.0 * (2.0 - 3.0 * sinI2) +
592                   qB * (15.0 - 17.5 * sinI2) +
593                   qC * (3.0 * sinI2 - 1.0 - 33.0 / 16.0 * sinI4);
594             axy3 = qq * 3.5 * sinI2;
595             as3 = qD * 5.0 / 3.0 * sinI2 +
596                   qE * 7.0 / 6.0 * sinI2 * (1.0 - 1.125 * sinI2);
597             ac4 = qA * sinI2 +
598                   qB * 4.375 * sinI2 +
599                   qC * 0.75 * (1.1 * sinI4 - sinI2);
600 
601             as5 = qE * 21.0 / 80.0 * sinI4;
602 
603             ac6 = qC * -11.0 / 80.0 * sinI4;
604 
605             ex1 = qq * (1.0 - 1.25 * sinI2);
606             exx2 = qq * 0.5 * (3.0 - 5.0 * sinI2);
607             exy2 = qq * (2.0 - 1.5 * sinI2);
608             ex3 = qq * 7.0 / 12.0 * sinI2;
609             ex4 = qq * 17.0 / 8.0 * sinI2;
610 
611             ey1 = qq * (1.0 - 1.75 * sinI2);
612             eyx2 = qq * (1.0 - 3.0 * sinI2);
613             eyy2 = qq * (2.0 * sinI2 - 1.5);
614             ey3 = qq * 7.0 / 12.0 * sinI2;
615             ey4 = qq * 17.0 / 8.0 * sinI2;
616 
617             q  = -qq * cosI1;
618             rx1 =  3.5 * q;
619             ry1 = -2.5 * q;
620             r2 = -0.5 * q;
621             r3 =  7.0 / 6.0 * q;
622             rl = g3 * cosI1 * (4.0 - 15.0 * sinI2) -
623                  2.5 * g5 * cosI1 * (4.0 - 42.0 * sinI2 + 52.5 * sinI4);
624 
625             q = 0.5 * qq * sinI1 * cosI1;
626             iy1 =  q;
627             ix1 = -q;
628             i2 =  q;
629             i3 =  q * 7.0 / 3.0;
630             ih = -g3 * cosI1 * (4.0 - 5.0 * sinI2) +
631                  2.5 * g5 * cosI1 * (4.0 - 14.0 * sinI2 + 10.5 * sinI4);
632 
633             lx1 = qq * (7.0 - 77.0 / 8.0 * sinI2);
634             ly1 = qq * (55.0 / 8.0 * sinI2 - 7.50);
635             l2 = qq * (1.25 * sinI2 - 0.5);
636             l3 = qq * (77.0 / 24.0 * sinI2 - 7.0 / 6.0);
637             ll = g3 * (53.0 * sinI2 - 4.0 - 57.5 * sinI4) +
638                  2.5 * g5 * (4.0 - 96.0 * sinI2 + 269.5 * sinI4 - 183.75 * sinI6);
639 
640         }
641 
642         /** Extrapolate an orbit up to a specific target date.
643          * @param date target date for the orbit
644          * @return propagated parameters
645          * @exception OrekitException if some parameters are out of bounds
646          */
647         public DerivativeStructure[] propagateParameters(final AbsoluteDate date)
648             throws OrekitException {
649 
650             // Keplerian evolution
651             final DerivativeStructure dt = FACTORY.variable(0, date.durationFrom(mean.getDate()));
652             final DerivativeStructure xnot = dt.multiply(xnotDot);
653 
654             // secular effects
655 
656             // eccentricity
657             final DerivativeStructure x   = xnot.multiply(rdpom + rdpomp);
658             final DerivativeStructure cx  = x.cos();
659             final DerivativeStructure sx  = x.sin();
660             final DerivativeStructure exm = cx.multiply(mean.getCircularEx()).
661                                             add(sx.multiply(eps2 - (1.0 - eps1) * mean.getCircularEy()));
662             final DerivativeStructure eym = sx.multiply((1.0 + eps1) * mean.getCircularEx()).
663                                             add(cx.multiply(mean.getCircularEy() - eps2)).
664                                             add(eps2);
665 
666             // no secular effect on inclination
667 
668             // right ascension of ascending node
669             final DerivativeStructure omm =
670                             FACTORY.build(MathUtils.normalizeAngle(mean.getRightAscensionOfAscendingNode() + ommD * xnot.getValue(),
671                                                            FastMath.PI),
672                                   ommD * xnotDot,
673                                   0.0);
674 
675             // latitude argument
676             final DerivativeStructure xlm =
677                             FACTORY.build(MathUtils.normalizeAngle(mean.getAlphaM() + aMD * xnot.getValue(), FastMath.PI),
678                                   aMD * xnotDot,
679                                   0.0);
680 
681             // periodical terms
682             final DerivativeStructure cl1 = xlm.cos();
683             final DerivativeStructure sl1 = xlm.sin();
684             final DerivativeStructure cl2 = cl1.multiply(cl1).subtract(sl1.multiply(sl1));
685             final DerivativeStructure sl2 = cl1.multiply(sl1).add(sl1.multiply(cl1));
686             final DerivativeStructure cl3 = cl2.multiply(cl1).subtract(sl2.multiply(sl1));
687             final DerivativeStructure sl3 = cl2.multiply(sl1).add(sl2.multiply(cl1));
688             final DerivativeStructure cl4 = cl3.multiply(cl1).subtract(sl3.multiply(sl1));
689             final DerivativeStructure sl4 = cl3.multiply(sl1).add(sl3.multiply(cl1));
690             final DerivativeStructure cl5 = cl4.multiply(cl1).subtract(sl4.multiply(sl1));
691             final DerivativeStructure sl5 = cl4.multiply(sl1).add(sl4.multiply(cl1));
692             final DerivativeStructure cl6 = cl5.multiply(cl1).subtract(sl5.multiply(sl1));
693 
694             final DerivativeStructure qh  = eym.subtract(eps2).multiply(kh);
695             final DerivativeStructure ql  = exm.multiply(kl);
696 
697             final DerivativeStructure exmCl1 = exm.multiply(cl1);
698             final DerivativeStructure exmSl1 = exm.multiply(sl1);
699             final DerivativeStructure eymCl1 = eym.multiply(cl1);
700             final DerivativeStructure eymSl1 = eym.multiply(sl1);
701             final DerivativeStructure exmCl2 = exm.multiply(cl2);
702             final DerivativeStructure exmSl2 = exm.multiply(sl2);
703             final DerivativeStructure eymCl2 = eym.multiply(cl2);
704             final DerivativeStructure eymSl2 = eym.multiply(sl2);
705             final DerivativeStructure exmCl3 = exm.multiply(cl3);
706             final DerivativeStructure exmSl3 = exm.multiply(sl3);
707             final DerivativeStructure eymCl3 = eym.multiply(cl3);
708             final DerivativeStructure eymSl3 = eym.multiply(sl3);
709             final DerivativeStructure exmCl4 = exm.multiply(cl4);
710             final DerivativeStructure exmSl4 = exm.multiply(sl4);
711             final DerivativeStructure eymCl4 = eym.multiply(cl4);
712             final DerivativeStructure eymSl4 = eym.multiply(sl4);
713 
714             // semi major axis
715             final DerivativeStructure rda = exmCl1.multiply(ax1).
716                                             add(eymSl1.multiply(ay1)).
717                                             add(sl1.multiply(as1)).
718                                             add(cl2.multiply(ac2)).
719                                             add(exmCl3.add(eymSl3).multiply(axy3)).
720                                             add(sl3.multiply(as3)).
721                                             add(cl4.multiply(ac4)).
722                                             add(sl5.multiply(as5)).
723                                             add(cl6.multiply(ac6));
724 
725             // eccentricity
726             final DerivativeStructure rdex = cl1.multiply(ex1).
727                                              add(exmCl2.multiply(exx2)).
728                                              add(eymSl2.multiply(exy2)).
729                                              add(cl3.multiply(ex3)).
730                                              add(exmCl4.add(eymSl4).multiply(ex4));
731             final DerivativeStructure rdey = sl1.multiply(ey1).
732                                              add(exmSl2.multiply(eyx2)).
733                                              add(eymCl2.multiply(eyy2)).
734                                              add(sl3.multiply(ey3)).
735                                              add(exmSl4.subtract(eymCl4).multiply(ey4));
736 
737             // ascending node
738             final DerivativeStructure rdom = exmSl1.multiply(rx1).
739                                              add(eymCl1.multiply(ry1)).
740                                              add(sl2.multiply(r2)).
741                                              add(eymCl3.subtract(exmSl3).multiply(r3)).
742                                              add(ql.multiply(rl));
743 
744             // inclination
745             final DerivativeStructure rdxi = eymSl1.multiply(iy1).
746                                              add(exmCl1.multiply(ix1)).
747                                              add(cl2.multiply(i2)).
748                                              add(exmCl3.add(eymSl3).multiply(i3)).
749                                              add(qh.multiply(ih));
750 
751             // latitude argument
752             final DerivativeStructure rdxl = exmSl1.multiply(lx1).
753                                              add(eymCl1.multiply(ly1)).
754                                              add(sl2.multiply(l2)).
755                                              add(exmSl3.subtract(eymCl3).multiply(l3)).
756                                              add(ql.multiply(ll));
757 
758             // osculating parameters
759             return new DerivativeStructure[] {
760                 rda.add(1.0).multiply(mean.getA()),
761                 rdex.add(exm),
762                 rdey.add(eym),
763                 rdxi.add(xim),
764                 rdom.add(omm),
765                 rdxl.add(xlm)
766             };
767 
768         }
769 
770     }
771 
772     /** Convert circular parameters <em>with derivatives</em> to Cartesian coordinates.
773      * @param date date of the orbital parameters
774      * @param parameters circular parameters (a, ex, ey, i, raan, alphaM)
775      * @return Cartesian coordinates consistent with values and derivatives
776      */
777     private TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final DerivativeStructure[] parameters) {
778 
779         // evaluate coordinates in the orbit canonical reference frame
780         final DerivativeStructure cosOmega = parameters[4].cos();
781         final DerivativeStructure sinOmega = parameters[4].sin();
782         final DerivativeStructure cosI     = parameters[3].cos();
783         final DerivativeStructure sinI     = parameters[3].sin();
784         final DerivativeStructure alphaE   = meanToEccentric(parameters[5], parameters[1], parameters[2]);
785         final DerivativeStructure cosAE    = alphaE.cos();
786         final DerivativeStructure sinAE    = alphaE.sin();
787         final DerivativeStructure ex2      = parameters[1].multiply(parameters[1]);
788         final DerivativeStructure ey2      = parameters[2].multiply(parameters[2]);
789         final DerivativeStructure exy      = parameters[1].multiply(parameters[2]);
790         final DerivativeStructure q        = ex2.add(ey2).subtract(1).negate().sqrt();
791         final DerivativeStructure beta     = q.add(1).reciprocal();
792         final DerivativeStructure bx2      = beta.multiply(ex2);
793         final DerivativeStructure by2      = beta.multiply(ey2);
794         final DerivativeStructure bxy      = beta.multiply(exy);
795         final DerivativeStructure u        = bxy.multiply(sinAE).subtract(parameters[1].add(by2.subtract(1).multiply(cosAE)));
796         final DerivativeStructure v        = bxy.multiply(cosAE).subtract(parameters[2].add(bx2.subtract(1).multiply(sinAE)));
797         final DerivativeStructure x        = parameters[0].multiply(u);
798         final DerivativeStructure y        = parameters[0].multiply(v);
799 
800         // canonical orbit reference frame
801         final FieldVector3D<DerivativeStructure> p =
802                 new FieldVector3D<>(x.multiply(cosOmega).subtract(y.multiply(cosI.multiply(sinOmega))),
803                                     x.multiply(sinOmega).add(y.multiply(cosI.multiply(cosOmega))),
804                                     y.multiply(sinI));
805 
806         // dispatch derivatives
807         final Vector3D p0 = new Vector3D(p.getX().getValue(),
808                                          p.getY().getValue(),
809                                          p.getZ().getValue());
810         final Vector3D p1 = new Vector3D(p.getX().getPartialDerivative(1),
811                                          p.getY().getPartialDerivative(1),
812                                          p.getZ().getPartialDerivative(1));
813         final Vector3D p2 = new Vector3D(p.getX().getPartialDerivative(2),
814                                          p.getY().getPartialDerivative(2),
815                                          p.getZ().getPartialDerivative(2));
816         return new TimeStampedPVCoordinates(date, p0, p1, p2);
817 
818     }
819 
820     /** Computes the eccentric latitude argument from the mean latitude argument.
821      * @param alphaM = M + Ω mean latitude argument (rad)
822      * @param ex e cos(Ω), first component of circular eccentricity vector
823      * @param ey e sin(Ω), second component of circular eccentricity vector
824      * @return the eccentric latitude argument.
825      */
826     private DerivativeStructure meanToEccentric(final DerivativeStructure alphaM,
827                                                 final DerivativeStructure ex,
828                                                 final DerivativeStructure ey) {
829         // Generalization of Kepler equation to circular parameters
830         // with alphaE = PA + E and
831         //      alphaM = PA + M = alphaE - ex.sin(alphaE) + ey.cos(alphaE)
832         DerivativeStructure alphaE        = alphaM;
833         DerivativeStructure shift         = alphaM.getField().getZero();
834         DerivativeStructure alphaEMalphaM = alphaM.getField().getZero();
835         DerivativeStructure cosAlphaE     = alphaE.cos();
836         DerivativeStructure sinAlphaE     = alphaE.sin();
837         int                 iter          = 0;
838         do {
839             final DerivativeStructure f2 = ex.multiply(sinAlphaE).subtract(ey.multiply(cosAlphaE));
840             final DerivativeStructure f1 = alphaM.getField().getOne().subtract(ex.multiply(cosAlphaE)).subtract(ey.multiply(sinAlphaE));
841             final DerivativeStructure f0 = alphaEMalphaM.subtract(f2);
842 
843             final DerivativeStructure f12 = f1.multiply(2);
844             shift = f0.multiply(f12).divide(f1.multiply(f12).subtract(f0.multiply(f2)));
845 
846             alphaEMalphaM  = alphaEMalphaM.subtract(shift);
847             alphaE         = alphaM.add(alphaEMalphaM);
848             cosAlphaE      = alphaE.cos();
849             sinAlphaE      = alphaE.sin();
850 
851         } while ((++iter < 50) && (FastMath.abs(shift.getValue()) > 1.0e-12));
852 
853         return alphaE;
854 
855     }
856 
857     /** {@inheritDoc} */
858     protected double getMass(final AbsoluteDate date) {
859         return models.get(date).mass;
860     }
861 
862     /** Replace the instance with a data transfer object for serialization.
863      * @return data transfer object that will be serialized
864      * @exception NotSerializableException if an additional state provider is not serializable
865      */
866     private Object writeReplace() throws NotSerializableException {
867         try {
868             // managed states providers
869             final List<AdditionalStateProvider> serializableProviders = new ArrayList<AdditionalStateProvider>();
870             for (final AdditionalStateProvider provider : getAdditionalStateProviders()) {
871                 if (provider instanceof Serializable) {
872                     serializableProviders.add(provider);
873                 } else {
874                     throw new NotSerializableException(provider.getClass().getName());
875                 }
876             }
877 
878             // states transitions
879             final AbsoluteDate[]  transitionDates;
880             final CircularOrbit[] allOrbits;
881             final double[]        allMasses;
882             final SortedSet<TimeSpanMap.Transition<EHModel>> transitions = models.getTransitions();
883             if (transitions.size() == 1  && transitions.first().getBefore() == transitions.first().getAfter()) {
884                 // the single entry is a dummy one, without a real transition
885                 // we ignore it completely
886                 transitionDates = null;
887                 allOrbits       = null;
888                 allMasses       = null;
889             } else {
890                 transitionDates = new AbsoluteDate[transitions.size()];
891                 allOrbits       = new CircularOrbit[transitions.size() + 1];
892                 allMasses       = new double[transitions.size() + 1];
893                 int i = 0;
894                 for (final TimeSpanMap.Transition<EHModel> transition : transitions) {
895                     if (i == 0) {
896                         // model before the first transition
897                         allOrbits[i] = transition.getBefore().mean;
898                         allMasses[i] = transition.getBefore().mass;
899                     }
900                     transitionDates[i] = transition.getDate();
901                     allOrbits[++i]     = transition.getAfter().mean;
902                     allMasses[i]       = transition.getAfter().mass;
903                 }
904             }
905 
906             return new DataTransferObject(getInitialState().getOrbit(), initialModel.mass,
907                                           referenceRadius, mu, ck0, getAttitudeProvider(),
908                                           transitionDates, allOrbits, allMasses,
909                                           serializableProviders.toArray(new AdditionalStateProvider[serializableProviders.size()]));
910         } catch (OrekitException orekitException) {
911             // this should never happen
912             throw new OrekitInternalError(null);
913         }
914 
915     }
916 
917     /** Internal class used only for serialization. */
918     private static class DataTransferObject implements Serializable {
919 
920         /** Serializable UID. */
921         private static final long serialVersionUID = 20151202L;
922 
923         /** Initial orbit. */
924         private final Orbit orbit;
925 
926         /** Attitude provider. */
927         private final AttitudeProvider attitudeProvider;
928 
929         /** Mass and gravity field. */
930         private double[] g;
931 
932         /** Transition dates (may be null). */
933         private final AbsoluteDate[] transitionDates;
934 
935         /** Orbits before and after transitions (may be null). */
936         private final CircularOrbit[] allOrbits;
937 
938         /** Masses before and after transitions (may be null). */
939         private final double[] allMasses;
940 
941         /** Providers for additional states. */
942         private final AdditionalStateProvider[] providers;
943 
944         /** Simple constructor.
945          * @param orbit initial orbit
946          * @param mass spacecraft mass
947          * @param referenceRadius reference radius of the Earth for the potential model (m)
948          * @param mu central attraction coefficient (m³/s²)
949          * @param ck0 un-normalized zonal coefficients
950          * @param attitudeProvider attitude provider
951          * @param transitionDates transition dates (may be null)
952          * @param allOrbits orbits before and after transitions (may be null)
953          * @param allMasses masses before and after transitions (may be null)
954          * @param providers providers for additional states
955          */
956         DataTransferObject(final Orbit orbit, final double mass,
957                            final double referenceRadius, final double mu,
958                            final double[] ck0,
959                            final AttitudeProvider attitudeProvider,
960                            final AbsoluteDate[] transitionDates,
961                            final CircularOrbit[] allOrbits,
962                            final double[] allMasses,
963                            final AdditionalStateProvider[] providers) {
964             this.orbit            = orbit;
965             this.attitudeProvider = attitudeProvider;
966             this.g = new double[] {
967                 mass, referenceRadius, mu,
968                 ck0[2], ck0[3], ck0[4], ck0[5], ck0[6] // ck0[0] and ck0[1] are both zero so not serialized
969             };
970             this.transitionDates  = transitionDates;
971             this.allOrbits        = allOrbits;
972             this.allMasses        = allMasses;
973             this.providers        = providers;
974         }
975 
976         /** Replace the deserialized data transfer object with a {@link EcksteinHechlerPropagator}.
977          * @return replacement {@link EcksteinHechlerPropagator}
978          */
979         private Object readResolve() {
980             try {
981                 final EcksteinHechlerPropagator propagator =
982                                 new EcksteinHechlerPropagator(orbit, attitudeProvider,
983                                                               g[0], g[1], g[2],              // mass, referenceRadius, mu
984                                                               g[3], g[4], g[5], g[6], g[7]); // c20, c30, c40, c50, c60
985                 for (final AdditionalStateProvider provider : providers) {
986                     propagator.addAdditionalStateProvider(provider);
987 
988                 }
989                 if (transitionDates != null) {
990                     // override the state transitions
991                     final double[] ck0 = new double[] {
992                         0, 0, g[3], g[4], g[5], g[6], g[7]
993                     };
994                     propagator.models = new TimeSpanMap<EHModel>(new EHModel(allOrbits[0], allMasses[0],
995                                                                              g[1], g[2], ck0));
996                     for (int i = 0; i < transitionDates.length; ++i) {
997                         propagator.models.addValidAfter(new EHModel(allOrbits[i + 1], allMasses[i + 1],
998                                                                     g[1], g[2], ck0),
999                                                         transitionDates[i]);
1000                     }
1001                 }
1002 
1003                 return propagator;
1004             } catch (OrekitException oe) {
1005                 throw new OrekitInternalError(oe);
1006             }
1007         }
1008 
1009     }
1010 
1011 }