1   /* Copyright 2002-2022 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.orbits;
18  
19  import java.io.Serializable;
20  import java.util.stream.Stream;
21  
22  import org.hipparchus.analysis.differentiation.UnivariateDerivative2;
23  import org.hipparchus.exception.LocalizedCoreFormats;
24  import org.hipparchus.exception.MathIllegalStateException;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Rotation;
27  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.hipparchus.util.FastMath;
30  import org.hipparchus.util.SinCos;
31  import org.orekit.annotation.DefaultDataContext;
32  import org.orekit.data.DataContext;
33  import org.orekit.frames.Frame;
34  import org.orekit.time.AbsoluteDate;
35  import org.orekit.utils.CartesianDerivativesFilter;
36  import org.orekit.utils.FieldPVCoordinates;
37  import org.orekit.utils.PVCoordinates;
38  import org.orekit.utils.TimeStampedPVCoordinates;
39  
40  
41  /** This class holds Cartesian orbital parameters.
42  
43   * <p>
44   * The parameters used internally are the Cartesian coordinates:
45   *   <ul>
46   *     <li>x</li>
47   *     <li>y</li>
48   *     <li>z</li>
49   *     <li>xDot</li>
50   *     <li>yDot</li>
51   *     <li>zDot</li>
52   *   </ul>
53   * contained in {@link PVCoordinates}.
54   *
55  
56   * <p>
57   * Note that the implementation of this class delegates all non-Cartesian related
58   * computations ({@link #getA()}, {@link #getEquinoctialEx()}, ...) to an underlying
59   * instance of the {@link EquinoctialOrbit} class. This implies that using this class
60   * only for analytical computations which are always based on non-Cartesian
61   * parameters is perfectly possible but somewhat sub-optimal.
62   * </p>
63   * <p>
64   * The instance <code>CartesianOrbit</code> is guaranteed to be immutable.
65   * </p>
66   * @see    Orbit
67   * @see    KeplerianOrbit
68   * @see    CircularOrbit
69   * @see    EquinoctialOrbit
70   * @author Luc Maisonobe
71   * @author Guylaine Prat
72   * @author Fabien Maussion
73   * @author V&eacute;ronique Pommier-Maurussane
74   */
75  public class CartesianOrbit extends Orbit {
76  
77      /** Serializable UID. */
78      private static final long serialVersionUID = 20170414L;
79  
80      /** Indicator for non-Keplerian derivatives. */
81      private final transient boolean hasNonKeplerianAcceleration;
82  
83      /** Underlying equinoctial orbit to which high-level methods are delegated. */
84      private transient EquinoctialOrbit equinoctial;
85  
86      /** Constructor from Cartesian parameters.
87       *
88       * <p> The acceleration provided in {@code pvCoordinates} is accessible using
89       * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
90       * use {@code mu} and the position to compute the acceleration, including
91       * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
92       *
93       * @param pvaCoordinates the position, velocity and acceleration of the satellite.
94       * @param frame the frame in which the {@link PVCoordinates} are defined
95       * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
96       * @param mu central attraction coefficient (m³/s²)
97       * @exception IllegalArgumentException if frame is not a {@link
98       * Frame#isPseudoInertial pseudo-inertial frame}
99       */
100     public CartesianOrbit(final TimeStampedPVCoordinates pvaCoordinates,
101                           final Frame frame, final double mu)
102         throws IllegalArgumentException {
103         super(pvaCoordinates, frame, mu);
104         hasNonKeplerianAcceleration = hasNonKeplerianAcceleration(pvaCoordinates, mu);
105         equinoctial = null;
106     }
107 
108     /** Constructor from Cartesian parameters.
109      *
110      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
111      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
112      * use {@code mu} and the position to compute the acceleration, including
113      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
114      *
115      * @param pvaCoordinates the position and velocity of the satellite.
116      * @param frame the frame in which the {@link PVCoordinates} are defined
117      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
118      * @param date date of the orbital parameters
119      * @param mu central attraction coefficient (m³/s²)
120      * @exception IllegalArgumentException if frame is not a {@link
121      * Frame#isPseudoInertial pseudo-inertial frame}
122      */
123     public CartesianOrbit(final PVCoordinates pvaCoordinates, final Frame frame,
124                           final AbsoluteDate date, final double mu)
125         throws IllegalArgumentException {
126         this(new TimeStampedPVCoordinates(date, pvaCoordinates), frame, mu);
127     }
128 
129     /** Constructor from any kind of orbital parameters.
130      * @param op orbital parameters to copy
131      */
132     public CartesianOrbit(final Orbit op) {
133         super(op.getPVCoordinates(), op.getFrame(), op.getMu());
134         hasNonKeplerianAcceleration = op.hasDerivatives();
135         if (op instanceof EquinoctialOrbit) {
136             equinoctial = (EquinoctialOrbit) op;
137         } else if (op instanceof CartesianOrbit) {
138             equinoctial = ((CartesianOrbit) op).equinoctial;
139         } else {
140             equinoctial = null;
141         }
142     }
143 
144     /** {@inheritDoc} */
145     public OrbitType getType() {
146         return OrbitType.CARTESIAN;
147     }
148 
149     /** Lazy evaluation of equinoctial parameters. */
150     private void initEquinoctial() {
151         if (equinoctial == null) {
152             if (hasDerivatives()) {
153                 // getPVCoordinates includes accelerations that will be interpreted as derivatives
154                 equinoctial = new EquinoctialOrbit(getPVCoordinates(), getFrame(), getDate(), getMu());
155             } else {
156                 // get rid of Keplerian acceleration so we don't assume
157                 // we have derivatives when in fact we don't have them
158                 equinoctial = new EquinoctialOrbit(new PVCoordinates(getPVCoordinates().getPosition(),
159                                                                      getPVCoordinates().getVelocity()),
160                                                    getFrame(), getDate(), getMu());
161             }
162         }
163     }
164 
165     /** Get the position/velocity with derivatives.
166      * @return position/velocity with derivatives
167      * @since 10.2
168      */
169     private FieldPVCoordinates<UnivariateDerivative2> getPVDerivatives() {
170         // PVA coordinates
171         final PVCoordinates pva = getPVCoordinates();
172         final Vector3D      p   = pva.getPosition();
173         final Vector3D      v   = pva.getVelocity();
174         final Vector3D      a   = pva.getAcceleration();
175         // Field coordinates
176         final FieldVector3D<UnivariateDerivative2> pG = new FieldVector3D<>(new UnivariateDerivative2(p.getX(), v.getX(), a.getX()),
177                                                                new UnivariateDerivative2(p.getY(), v.getY(), a.getY()),
178                                                                new UnivariateDerivative2(p.getZ(), v.getZ(), a.getZ()));
179         final FieldVector3D<UnivariateDerivative2> vG = new FieldVector3D<>(new UnivariateDerivative2(v.getX(), a.getX(), 0.0),
180                                                                new UnivariateDerivative2(v.getY(), a.getY(), 0.0),
181                                                                new UnivariateDerivative2(v.getZ(), a.getZ(), 0.0));
182         return new FieldPVCoordinates<>(pG, vG);
183     }
184 
185     /** {@inheritDoc} */
186     public double getA() {
187         final double r  = getPVCoordinates().getPosition().getNorm();
188         final double V2 = getPVCoordinates().getVelocity().getNormSq();
189         return r / (2 - r * V2 / getMu());
190     }
191 
192     /** {@inheritDoc} */
193     public double getADot() {
194         if (hasDerivatives()) {
195             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
196             final UnivariateDerivative2 r  = pv.getPosition().getNorm();
197             final UnivariateDerivative2 V2 = pv.getVelocity().getNormSq();
198             final UnivariateDerivative2 a  = r.divide(r.multiply(V2).divide(getMu()).subtract(2).negate());
199             return a.getDerivative(1);
200         } else {
201             return Double.NaN;
202         }
203     }
204 
205     /** {@inheritDoc} */
206     public double getE() {
207         final double muA = getMu() * getA();
208         if (muA > 0) {
209             // elliptic or circular orbit
210             final Vector3D pvP   = getPVCoordinates().getPosition();
211             final Vector3D pvV   = getPVCoordinates().getVelocity();
212             final double rV2OnMu = pvP.getNorm() * pvV.getNormSq() / getMu();
213             final double eSE     = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(muA);
214             final double eCE     = rV2OnMu - 1;
215             return FastMath.sqrt(eCE * eCE + eSE * eSE);
216         } else {
217             // hyperbolic orbit
218             final Vector3D pvM = getPVCoordinates().getMomentum();
219             return FastMath.sqrt(1 - pvM.getNormSq() / muA);
220         }
221     }
222 
223     /** {@inheritDoc} */
224     public double getEDot() {
225         if (hasDerivatives()) {
226             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
227             final FieldVector3D<UnivariateDerivative2> pvP   = pv.getPosition();
228             final FieldVector3D<UnivariateDerivative2> pvV   = pv.getVelocity();
229             final UnivariateDerivative2 r       = pvP.getNorm();
230             final UnivariateDerivative2 V2      = pvV.getNormSq();
231             final UnivariateDerivative2 rV2OnMu = r.multiply(V2).divide(getMu());
232             final UnivariateDerivative2 a       = r.divide(rV2OnMu.negate().add(2));
233             final UnivariateDerivative2 eSE     = FieldVector3D.dotProduct(pvP, pvV).divide(a.multiply(getMu()).sqrt());
234             final UnivariateDerivative2 eCE     = rV2OnMu.subtract(1);
235             final UnivariateDerivative2 e       = eCE.multiply(eCE).add(eSE.multiply(eSE)).sqrt();
236             return e.getDerivative(1);
237         } else {
238             return Double.NaN;
239         }
240     }
241 
242     /** {@inheritDoc} */
243     public double getI() {
244         return Vector3D.angle(Vector3D.PLUS_K, getPVCoordinates().getMomentum());
245     }
246 
247     /** {@inheritDoc} */
248     public double getIDot() {
249         if (hasDerivatives()) {
250             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
251             final FieldVector3D<UnivariateDerivative2> momentum =
252                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity());
253             final UnivariateDerivative2 i = FieldVector3D.angle(Vector3D.PLUS_K, momentum);
254             return i.getDerivative(1);
255         } else {
256             return Double.NaN;
257         }
258     }
259 
260     /** {@inheritDoc} */
261     public double getEquinoctialEx() {
262         initEquinoctial();
263         return equinoctial.getEquinoctialEx();
264     }
265 
266     /** {@inheritDoc} */
267     public double getEquinoctialExDot() {
268         initEquinoctial();
269         return equinoctial.getEquinoctialExDot();
270     }
271 
272     /** {@inheritDoc} */
273     public double getEquinoctialEy() {
274         initEquinoctial();
275         return equinoctial.getEquinoctialEy();
276     }
277 
278     /** {@inheritDoc} */
279     public double getEquinoctialEyDot() {
280         initEquinoctial();
281         return equinoctial.getEquinoctialEyDot();
282     }
283 
284     /** {@inheritDoc} */
285     public double getHx() {
286         final Vector3D w = getPVCoordinates().getMomentum().normalize();
287         // Check for equatorial retrograde orbit
288         if ((w.getX() * w.getX() + w.getY() * w.getY()) == 0 && w.getZ() < 0) {
289             return Double.NaN;
290         }
291         return -w.getY() / (1 + w.getZ());
292     }
293 
294     /** {@inheritDoc} */
295     public double getHxDot() {
296         if (hasDerivatives()) {
297             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
298             final FieldVector3D<UnivariateDerivative2> w =
299                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
300             // Check for equatorial retrograde orbit
301             final double x = w.getX().getValue();
302             final double y = w.getY().getValue();
303             final double z = w.getZ().getValue();
304             if ((x * x + y * y) == 0 && z < 0) {
305                 return Double.NaN;
306             }
307             final UnivariateDerivative2 hx = w.getY().negate().divide(w.getZ().add(1));
308             return hx.getDerivative(1);
309         } else {
310             return Double.NaN;
311         }
312     }
313 
314     /** {@inheritDoc} */
315     public double getHy() {
316         final Vector3D w = getPVCoordinates().getMomentum().normalize();
317         // Check for equatorial retrograde orbit
318         if ((w.getX() * w.getX() + w.getY() * w.getY()) == 0 && w.getZ() < 0) {
319             return Double.NaN;
320         }
321         return  w.getX() / (1 + w.getZ());
322     }
323 
324     /** {@inheritDoc} */
325     public double getHyDot() {
326         if (hasDerivatives()) {
327             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
328             final FieldVector3D<UnivariateDerivative2> w =
329                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
330             // Check for equatorial retrograde orbit
331             final double x = w.getX().getValue();
332             final double y = w.getY().getValue();
333             final double z = w.getZ().getValue();
334             if ((x * x + y * y) == 0 && z < 0) {
335                 return Double.NaN;
336             }
337             final UnivariateDerivative2 hy = w.getX().divide(w.getZ().add(1));
338             return hy.getDerivative(1);
339         } else {
340             return Double.NaN;
341         }
342     }
343 
344     /** {@inheritDoc} */
345     public double getLv() {
346         initEquinoctial();
347         return equinoctial.getLv();
348     }
349 
350     /** {@inheritDoc} */
351     public double getLvDot() {
352         initEquinoctial();
353         return equinoctial.getLvDot();
354     }
355 
356     /** {@inheritDoc} */
357     public double getLE() {
358         initEquinoctial();
359         return equinoctial.getLE();
360     }
361 
362     /** {@inheritDoc} */
363     public double getLEDot() {
364         initEquinoctial();
365         return equinoctial.getLEDot();
366     }
367 
368     /** {@inheritDoc} */
369     public double getLM() {
370         initEquinoctial();
371         return equinoctial.getLM();
372     }
373 
374     /** {@inheritDoc} */
375     public double getLMDot() {
376         initEquinoctial();
377         return equinoctial.getLMDot();
378     }
379 
380     /** {@inheritDoc} */
381     public boolean hasDerivatives() {
382         return hasNonKeplerianAcceleration;
383     }
384 
385     /** {@inheritDoc} */
386     protected TimeStampedPVCoordinates initPVCoordinates() {
387         // nothing to do here, as the canonical elements are already the Cartesian ones
388         return getPVCoordinates();
389     }
390 
391     /** {@inheritDoc} */
392     public CartesianOrbit shiftedBy(final double dt) {
393         final PVCoordinates shiftedPV = (getA() < 0) ? shiftPVHyperbolic(dt) : shiftPVElliptic(dt);
394         return new CartesianOrbit(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu());
395     }
396 
397     /** {@inheritDoc}
398      * <p>
399      * The interpolated instance is created by polynomial Hermite interpolation
400      * ensuring velocity remains the exact derivative of position.
401      * </p>
402      * <p>
403      * As this implementation of interpolation is polynomial, it should be used only
404      * with small samples (about 10-20 points) in order to avoid <a
405      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
406      * and numerical problems (including NaN appearing).
407      * </p>
408      * <p>
409      * If orbit interpolation on large samples is needed, using the {@link
410      * org.orekit.propagation.analytical.Ephemeris} class is a better way than using this
411      * low-level interpolation. The Ephemeris class automatically handles selection of
412      * a neighboring sub-sample with a predefined number of point from a large global sample
413      * in a thread-safe way.
414      * </p>
415      */
416     public CartesianOrbit interpolate(final AbsoluteDate date, final Stream<Orbit> sample) {
417         final TimeStampedPVCoordinates interpolated =
418                 TimeStampedPVCoordinates.interpolate(date, CartesianDerivativesFilter.USE_PVA,
419                                                      sample.map(orbit -> orbit.getPVCoordinates()));
420         return new CartesianOrbit(interpolated, getFrame(), date, getMu());
421     }
422 
423     /** Compute shifted position and velocity in elliptic case.
424      * @param dt time shift
425      * @return shifted position and velocity
426      */
427     private PVCoordinates shiftPVElliptic(final double dt) {
428 
429         // preliminary computation
430         final Vector3D pvP   = getPVCoordinates().getPosition();
431         final Vector3D pvV   = getPVCoordinates().getVelocity();
432         final double r2      = pvP.getNormSq();
433         final double r       = FastMath.sqrt(r2);
434         final double rV2OnMu = r * pvV.getNormSq() / getMu();
435         final double a       = getA();
436         final double eSE     = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(getMu() * a);
437         final double eCE     = rV2OnMu - 1;
438         final double e2      = eCE * eCE + eSE * eSE;
439 
440         // we can use any arbitrary reference 2D frame in the orbital plane
441         // in order to simplify some equations below, we use the current position as the u axis
442         final Vector3D u     = pvP.normalize();
443         final Vector3D v     = Vector3D.crossProduct(getPVCoordinates().getMomentum(), u).normalize();
444 
445         // the following equations rely on the specific choice of u explained above,
446         // some coefficients that vanish to 0 in this case have already been removed here
447         final double ex        = (eCE - e2) * a / r;
448         final double ey        = -FastMath.sqrt(1 - e2) * eSE * a / r;
449         final double beta      = 1 / (1 + FastMath.sqrt(1 - e2));
450         final double thetaE0   = FastMath.atan2(ey + eSE * beta * ex, r / a + ex - eSE * beta * ey);
451         final SinCos scThetaE0 = FastMath.sinCos(thetaE0);
452         final double thetaM0   = thetaE0 - ex * scThetaE0.sin() + ey * scThetaE0.cos();
453 
454         // compute in-plane shifted eccentric argument
455         final double thetaM1 = thetaM0 + getKeplerianMeanMotion() * dt;
456         final double thetaE1 = meanToEccentric(thetaM1, ex, ey);
457         final SinCos scTE    = FastMath.sinCos(thetaE1);
458         final double cTE     = scTE.cos();
459         final double sTE     = scTE.sin();
460 
461         // compute shifted in-plane Cartesian coordinates
462         final double exey   = ex * ey;
463         final double exCeyS = ex * cTE + ey * sTE;
464         final double x      = a * ((1 - beta * ey * ey) * cTE + beta * exey * sTE - ex);
465         final double y      = a * ((1 - beta * ex * ex) * sTE + beta * exey * cTE - ey);
466         final double factor = FastMath.sqrt(getMu() / a) / (1 - exCeyS);
467         final double xDot   = factor * (-sTE + beta * ey * exCeyS);
468         final double yDot   = factor * ( cTE - beta * ex * exCeyS);
469 
470         final Vector3D shiftedP = new Vector3D(x, u, y, v);
471         final Vector3D shiftedV = new Vector3D(xDot, u, yDot, v);
472         if (hasNonKeplerianAcceleration) {
473 
474             // extract non-Keplerian part of the initial acceleration
475             final Vector3D nonKeplerianAcceleration = new Vector3D(1, getPVCoordinates().getAcceleration(),
476                                                                    getMu() / (r2 * r), pvP);
477 
478             // add the quadratic motion due to the non-Keplerian acceleration to the Keplerian motion
479             final Vector3D fixedP   = new Vector3D(1, shiftedP,
480                                                    0.5 * dt * dt, nonKeplerianAcceleration);
481             final double   fixedR2 = fixedP.getNormSq();
482             final double   fixedR  = FastMath.sqrt(fixedR2);
483             final Vector3D fixedV  = new Vector3D(1, shiftedV,
484                                                   dt, nonKeplerianAcceleration);
485             final Vector3D fixedA  = new Vector3D(-getMu() / (fixedR2 * fixedR), shiftedP,
486                                                   1, nonKeplerianAcceleration);
487 
488             return new PVCoordinates(fixedP, fixedV, fixedA);
489 
490         } else {
491             // don't include acceleration,
492             // so the shifted orbit is not considered to have derivatives
493             return new PVCoordinates(shiftedP, shiftedV);
494         }
495 
496     }
497 
498     /** Compute shifted position and velocity in hyperbolic case.
499      * @param dt time shift
500      * @return shifted position and velocity
501      */
502     private PVCoordinates shiftPVHyperbolic(final double dt) {
503 
504         final PVCoordinates pv = getPVCoordinates();
505         final Vector3D pvP   = pv.getPosition();
506         final Vector3D pvV   = pv.getVelocity();
507         final Vector3D pvM   = pv.getMomentum();
508         final double r2      = pvP.getNormSq();
509         final double r       = FastMath.sqrt(r2);
510         final double rV2OnMu = r * pvV.getNormSq() / getMu();
511         final double a       = getA();
512         final double muA     = getMu() * a;
513         final double e       = FastMath.sqrt(1 - Vector3D.dotProduct(pvM, pvM) / muA);
514         final double sqrt    = FastMath.sqrt((e + 1) / (e - 1));
515 
516         // compute mean anomaly
517         final double eSH     = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(-muA);
518         final double eCH     = rV2OnMu - 1;
519         final double H0      = FastMath.log((eCH + eSH) / (eCH - eSH)) / 2;
520         final double M0      = e * FastMath.sinh(H0) - H0;
521 
522         // find canonical 2D frame with p pointing to perigee
523         final double v0      = 2 * FastMath.atan(sqrt * FastMath.tanh(H0 / 2));
524         final Vector3D p     = new Rotation(pvM, v0, RotationConvention.FRAME_TRANSFORM).applyTo(pvP).normalize();
525         final Vector3D q     = Vector3D.crossProduct(pvM, p).normalize();
526 
527         // compute shifted eccentric anomaly
528         final double M1      = M0 + getKeplerianMeanMotion() * dt;
529         final double H1      = KeplerianAnomalyUtility.hyperbolicMeanToEccentric(e, M1);
530 
531         // compute shifted in-plane Cartesian coordinates
532         final double cH     = FastMath.cosh(H1);
533         final double sH     = FastMath.sinh(H1);
534         final double sE2m1  = FastMath.sqrt((e - 1) * (e + 1));
535 
536         // coordinates of position and velocity in the orbital plane
537         final double x      = a * (cH - e);
538         final double y      = -a * sE2m1 * sH;
539         final double factor = FastMath.sqrt(getMu() / -a) / (e * cH - 1);
540         final double xDot   = -factor * sH;
541         final double yDot   =  factor * sE2m1 * cH;
542 
543         final Vector3D shiftedP = new Vector3D(x, p, y, q);
544         final Vector3D shiftedV = new Vector3D(xDot, p, yDot, q);
545         if (hasNonKeplerianAcceleration) {
546 
547             // extract non-Keplerian part of the initial acceleration
548             final Vector3D nonKeplerianAcceleration = new Vector3D(1, getPVCoordinates().getAcceleration(),
549                                                                    getMu() / (r2 * r), pvP);
550 
551             // add the quadratic motion due to the non-Keplerian acceleration to the Keplerian motion
552             final Vector3D fixedP   = new Vector3D(1, shiftedP,
553                                                    0.5 * dt * dt, nonKeplerianAcceleration);
554             final double   fixedR2 = fixedP.getNormSq();
555             final double   fixedR  = FastMath.sqrt(fixedR2);
556             final Vector3D fixedV  = new Vector3D(1, shiftedV,
557                                                   dt, nonKeplerianAcceleration);
558             final Vector3D fixedA  = new Vector3D(-getMu() / (fixedR2 * fixedR), shiftedP,
559                                                   1, nonKeplerianAcceleration);
560 
561             return new PVCoordinates(fixedP, fixedV, fixedA);
562 
563         } else {
564             // don't include acceleration,
565             // so the shifted orbit is not considered to have derivatives
566             return new PVCoordinates(shiftedP, shiftedV);
567         }
568 
569     }
570 
571     /** Computes the eccentric in-plane argument from the mean in-plane argument.
572      * @param thetaM = mean in-plane argument (rad)
573      * @param ex first component of eccentricity vector
574      * @param ey second component of eccentricity vector
575      * @return the eccentric in-plane argument.
576      */
577     private double meanToEccentric(final double thetaM, final double ex, final double ey) {
578         // Generalization of Kepler equation to in-plane parameters
579         // with thetaE = eta + E and
580         //      thetaM = eta + M = thetaE - ex.sin(thetaE) + ey.cos(thetaE)
581         // and eta being counted from an arbitrary reference in the orbital plane
582         double thetaE        = thetaM;
583         double thetaEMthetaM = 0.0;
584         int    iter          = 0;
585         do {
586             final SinCos scThetaE = FastMath.sinCos(thetaE);
587 
588             final double f2 = ex * scThetaE.sin() - ey * scThetaE.cos();
589             final double f1 = 1.0 - ex * scThetaE.cos() - ey * scThetaE.sin();
590             final double f0 = thetaEMthetaM - f2;
591 
592             final double f12 = 2.0 * f1;
593             final double shift = f0 * f12 / (f1 * f12 - f0 * f2);
594 
595             thetaEMthetaM -= shift;
596             thetaE         = thetaM + thetaEMthetaM;
597 
598             if (FastMath.abs(shift) <= 1.0e-12) {
599                 return thetaE;
600             }
601 
602         } while (++iter < 50);
603 
604         throw new MathIllegalStateException(LocalizedCoreFormats.CONVERGENCE_FAILED);
605 
606     }
607 
608     /** Create a 6x6 identity matrix.
609      * @return 6x6 identity matrix
610      */
611     private double[][] create6x6Identity() {
612         // this is the fastest way to set the 6x6 identity matrix
613         final double[][] identity = new double[6][6];
614         for (int i = 0; i < 6; i++) {
615             identity[i][i] = 1.0;
616         }
617         return identity;
618     }
619 
620     @Override
621     protected double[][] computeJacobianMeanWrtCartesian() {
622         return create6x6Identity();
623     }
624 
625     @Override
626     protected double[][] computeJacobianEccentricWrtCartesian() {
627         return create6x6Identity();
628     }
629 
630     @Override
631     protected double[][] computeJacobianTrueWrtCartesian() {
632         return create6x6Identity();
633     }
634 
635     /** {@inheritDoc} */
636     public void addKeplerContribution(final PositionAngle type, final double gm,
637                                       final double[] pDot) {
638 
639         final PVCoordinates pv = getPVCoordinates();
640 
641         // position derivative is velocity
642         final Vector3D velocity = pv.getVelocity();
643         pDot[0] += velocity.getX();
644         pDot[1] += velocity.getY();
645         pDot[2] += velocity.getZ();
646 
647         // velocity derivative is Newtonian acceleration
648         final Vector3D position = pv.getPosition();
649         final double r2         = position.getNormSq();
650         final double coeff      = -gm / (r2 * FastMath.sqrt(r2));
651         pDot[3] += coeff * position.getX();
652         pDot[4] += coeff * position.getY();
653         pDot[5] += coeff * position.getZ();
654 
655     }
656 
657     /**  Returns a string representation of this Orbit object.
658      * @return a string representation of this object
659      */
660     public String toString() {
661         // use only the six defining elements, like the other Orbit.toString() methods
662         final String comma = ", ";
663         final PVCoordinates pv = getPVCoordinates();
664         final Vector3D p = pv.getPosition();
665         final Vector3D v = pv.getVelocity();
666         return "Cartesian parameters: {P(" +
667                 p.getX() + comma +
668                 p.getY() + comma +
669                 p.getZ() + "), V(" +
670                 v.getX() + comma +
671                 v.getY() + comma +
672                 v.getZ() + ")}";
673     }
674 
675     /** Replace the instance with a data transfer object for serialization.
676      * <p>
677      * This intermediate class serializes all needed parameters,
678      * including position-velocity which are <em>not</em> serialized by parent
679      * {@link Orbit} class.
680      * </p>
681      * @return data transfer object that will be serialized
682      */
683     @DefaultDataContext
684     private Object writeReplace() {
685         return new DTO(this);
686     }
687 
688     /** Internal class used only for serialization. */
689     @DefaultDataContext
690     private static class DTO implements Serializable {
691 
692         /** Serializable UID. */
693         private static final long serialVersionUID = 20170414L;
694 
695         /** Double values. */
696         private double[] d;
697 
698         /** Frame in which are defined the orbital parameters. */
699         private final Frame frame;
700 
701         /** Simple constructor.
702          * @param orbit instance to serialize
703          */
704         private DTO(final CartesianOrbit orbit) {
705 
706             final TimeStampedPVCoordinates pv = orbit.getPVCoordinates();
707 
708             // decompose date
709             final AbsoluteDate j2000Epoch =
710                     DataContext.getDefault().getTimeScales().getJ2000Epoch();
711             final double epoch  = FastMath.floor(pv.getDate().durationFrom(j2000Epoch));
712             final double offset = pv.getDate().durationFrom(j2000Epoch.shiftedBy(epoch));
713 
714             if (orbit.hasDerivatives()) {
715                 this.d = new double[] {
716                     epoch, offset, orbit.getMu(),
717                     pv.getPosition().getX(),     pv.getPosition().getY(),     pv.getPosition().getZ(),
718                     pv.getVelocity().getX(),     pv.getVelocity().getY(),     pv.getVelocity().getZ(),
719                     pv.getAcceleration().getX(), pv.getAcceleration().getY(), pv.getAcceleration().getZ()
720                 };
721             } else {
722                 this.d = new double[] {
723                     epoch, offset, orbit.getMu(),
724                     pv.getPosition().getX(),     pv.getPosition().getY(),     pv.getPosition().getZ(),
725                     pv.getVelocity().getX(),     pv.getVelocity().getY(),     pv.getVelocity().getZ()
726                 };
727             }
728 
729             this.frame = orbit.getFrame();
730 
731         }
732 
733         /** Replace the deserialized data transfer object with a {@link CartesianOrbit}.
734          * @return replacement {@link CartesianOrbit}
735          */
736         private Object readResolve() {
737             final AbsoluteDate j2000Epoch =
738                     DataContext.getDefault().getTimeScales().getJ2000Epoch();
739             if (d.length >= 12) {
740                 // we have derivatives
741                 return new CartesianOrbit(new TimeStampedPVCoordinates(j2000Epoch.shiftedBy(d[0]).shiftedBy(d[1]),
742                                                                        new Vector3D(d[3], d[ 4], d[ 5]),
743                                                                        new Vector3D(d[6], d[ 7], d[ 8]),
744                                                                        new Vector3D(d[9], d[10], d[11])),
745                                           frame, d[2]);
746             } else {
747                 // we don't have derivatives
748                 return new CartesianOrbit(new TimeStampedPVCoordinates(j2000Epoch.shiftedBy(d[0]).shiftedBy(d[1]),
749                                                                        new Vector3D(d[3], d[ 4], d[ 5]),
750                                                                        new Vector3D(d[6], d[ 7], d[ 8])),
751                                           frame, d[2]);
752             }
753         }
754 
755     }
756 
757 }