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