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