1   /* Copyright 2002-2025 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 org.hipparchus.analysis.differentiation.UnivariateDerivative1;
20  import org.hipparchus.geometry.euclidean.threed.Vector3D;
21  import org.hipparchus.util.FastMath;
22  import org.hipparchus.util.SinCos;
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitIllegalArgumentException;
25  import org.orekit.errors.OrekitInternalError;
26  import org.orekit.errors.OrekitMessages;
27  import org.orekit.frames.Frame;
28  import org.orekit.frames.KinematicTransform;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.time.TimeOffset;
31  import org.orekit.utils.PVCoordinates;
32  import org.orekit.utils.TimeStampedPVCoordinates;
33  
34  
35  /**
36   * This class handles traditional Keplerian orbital parameters.
37  
38   * <p>
39   * The parameters used internally are the classical Keplerian elements:
40   *   <pre>
41   *     a
42   *     e
43   *     i
44   *     ω
45   *     Ω
46   *     v
47   *   </pre>
48   * where ω stands for the Perigee Argument, Ω stands for the
49   * Right Ascension of the Ascending Node and v stands for the true anomaly.
50   *
51   * <p>
52   * This class supports hyperbolic orbits, using the convention that semi major
53   * axis is negative for such orbits (and of course eccentricity is greater than 1).
54   * </p>
55   * <p>
56   * When orbit is either equatorial or circular, some Keplerian elements
57   * (more precisely ω and Ω) become ambiguous so this class should not
58   * be used for such orbits. For this reason, {@link EquinoctialOrbit equinoctial
59   * orbits} is the recommended way to represent orbits.
60   * </p>
61  
62   * <p>
63   * The instance <code>KeplerianOrbit</code> is guaranteed to be immutable.
64   * </p>
65   * @see     Orbit
66   * @see    CircularOrbit
67   * @see    CartesianOrbit
68   * @see    EquinoctialOrbit
69   * @author Luc Maisonobe
70   * @author Guylaine Prat
71   * @author Fabien Maussion
72   * @author V&eacute;ronique Pommier-Maurussane
73   */
74  public class KeplerianOrbit extends Orbit implements PositionAngleBased<KeplerianOrbit> {
75  
76      /** Name of the eccentricity parameter. */
77      private static final String ECCENTRICITY = "eccentricity";
78  
79      /** Semi-major axis (m). */
80      private final double a;
81  
82      /** Eccentricity. */
83      private final double e;
84  
85      /** Inclination (rad). */
86      private final double i;
87  
88      /** Perigee Argument (rad). */
89      private final double pa;
90  
91      /** Right Ascension of Ascending Node (rad). */
92      private final double raan;
93  
94      /** Cached anomaly (rad). */
95      private final double cachedAnomaly;
96  
97      /** Semi-major axis derivative (m/s). */
98      private final double aDot;
99  
100     /** Eccentricity derivative. */
101     private final double eDot;
102 
103     /** Inclination derivative (rad/s). */
104     private final double iDot;
105 
106     /** Perigee Argument derivative (rad/s). */
107     private final double paDot;
108 
109     /** Right Ascension of Ascending Node derivative (rad/s). */
110     private final double raanDot;
111 
112     /** Derivative of cached anomaly (rad/s). */
113     private final double cachedAnomalyDot;
114 
115     /** Cached type of position angle. */
116     private final PositionAngleType cachedPositionAngleType;
117 
118     /** Partial Cartesian coordinates (position and velocity are valid, acceleration may be missing). */
119     private PVCoordinates partialPV;
120 
121     /** Creates a new instance.
122      * @param a  semi-major axis (m), negative for hyperbolic orbits
123      * @param e eccentricity (positive or equal to 0)
124      * @param i inclination (rad)
125      * @param pa perigee argument (ω, rad)
126      * @param raan right ascension of ascending node (Ω, rad)
127      * @param anomaly mean, eccentric or true anomaly (rad)
128      * @param type type of anomaly
129      * @param cachedPositionAngleType type of cached anomaly
130      * @param frame the frame in which the parameters are defined
131      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
132      * @param date date of the orbital parameters
133      * @param mu central attraction coefficient (m³/s²)
134      * @exception IllegalArgumentException if frame is not a {@link
135      * Frame#isPseudoInertial pseudo-inertial frame} or a and e don't match for hyperbolic orbits,
136      * or v is out of range for hyperbolic orbits
137      * @since 12.1
138      */
139     public KeplerianOrbit(final double a, final double e, final double i,
140                           final double pa, final double raan, final double anomaly,
141                           final PositionAngleType type, final PositionAngleType cachedPositionAngleType,
142                           final Frame frame, final AbsoluteDate date, final double mu)
143             throws IllegalArgumentException {
144         this(a, e, i, pa, raan, anomaly,
145                 0., 0., 0., 0., 0., computeKeplerianAnomalyDot(type, a, e, mu, anomaly, type),
146                 type, cachedPositionAngleType, frame, date, mu);
147     }
148 
149     /** Creates a new instance without derivatives and with cached position angle same as value inputted.
150      * @param a  semi-major axis (m), negative for hyperbolic orbits
151      * @param e eccentricity (positive or equal to 0)
152      * @param i inclination (rad)
153      * @param pa perigee argument (ω, rad)
154      * @param raan right ascension of ascending node (Ω, rad)
155      * @param anomaly mean, eccentric or true anomaly (rad)
156      * @param type type of anomaly
157      * @param frame the frame in which the parameters are defined
158      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
159      * @param date date of the orbital parameters
160      * @param mu central attraction coefficient (m³/s²)
161      * @exception IllegalArgumentException if frame is not a {@link
162      * Frame#isPseudoInertial pseudo-inertial frame} or a and e don't match for hyperbolic orbits,
163      * or v is out of range for hyperbolic orbits
164      */
165     public KeplerianOrbit(final double a, final double e, final double i,
166                           final double pa, final double raan, final double anomaly,
167                           final PositionAngleType type,
168                           final Frame frame, final AbsoluteDate date, final double mu)
169             throws IllegalArgumentException {
170         this(a, e, i, pa, raan, anomaly, type, type, frame, date, mu);
171     }
172 
173     /** Creates a new instance.
174      * @param a  semi-major axis (m), negative for hyperbolic orbits
175      * @param e eccentricity (positive or equal to 0)
176      * @param i inclination (rad)
177      * @param pa perigee argument (ω, rad)
178      * @param raan right ascension of ascending node (Ω, rad)
179      * @param anomaly mean, eccentric or true anomaly (rad)
180      * @param aDot  semi-major axis derivative (m/s)
181      * @param eDot eccentricity derivative
182      * @param iDot inclination derivative (rad/s)
183      * @param paDot perigee argument derivative (rad/s)
184      * @param raanDot right ascension of ascending node derivative (rad/s)
185      * @param anomalyDot mean, eccentric or true anomaly derivative (rad/s)
186      * @param type type of input anomaly
187      * @param cachedPositionAngleType type of cached anomaly
188      * @param frame the frame in which the parameters are defined
189      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
190      * @param date date of the orbital parameters
191      * @param mu central attraction coefficient (m³/s²)
192      * @exception IllegalArgumentException if frame is not a {@link
193      * Frame#isPseudoInertial pseudo-inertial frame} or a and e don't match for hyperbolic orbits,
194      * or v is out of range for hyperbolic orbits
195      * @since 12.1
196      */
197     public KeplerianOrbit(final double a, final double e, final double i,
198                           final double pa, final double raan, final double anomaly,
199                           final double aDot, final double eDot, final double iDot,
200                           final double paDot, final double raanDot, final double anomalyDot,
201                           final PositionAngleType type, final PositionAngleType cachedPositionAngleType,
202                           final Frame frame, final AbsoluteDate date, final double mu)
203             throws IllegalArgumentException {
204         super(frame, date, mu);
205         this.cachedPositionAngleType = cachedPositionAngleType;
206 
207         if (a * (1 - e) < 0) {
208             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE, a, e);
209         }
210 
211         // Checking eccentricity range
212         checkParameterRangeInclusive(ECCENTRICITY, e, 0.0, Double.POSITIVE_INFINITY);
213 
214         this.a       = a;
215         this.aDot    = aDot;
216         this.e       = e;
217         this.eDot    = eDot;
218         this.i       = i;
219         this.iDot    = iDot;
220         this.pa      = pa;
221         this.paDot   = paDot;
222         this.raan    = raan;
223         this.raanDot = raanDot;
224 
225         final UnivariateDerivative1 cachedAnomalyUD = initializeCachedAnomaly(anomaly, anomalyDot, type);
226         this.cachedAnomaly = cachedAnomalyUD.getValue();
227         this.cachedAnomalyDot = cachedAnomalyUD.getFirstDerivative();
228 
229         // check true anomaly range
230         if (!isElliptical()) {
231             final double trueAnomaly = getTrueAnomaly();
232             if (1 + e * FastMath.cos(trueAnomaly) <= 0) {
233                 final double vMax = FastMath.acos(-1 / e);
234                 throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_ANOMALY_OUT_OF_HYPERBOLIC_RANGE,
235                         trueAnomaly, e, -vMax, vMax);
236             }
237         }
238 
239         this.partialPV = null;
240 
241     }
242 
243     /** Creates a new instance with cached position angle same as value inputted.
244      * @param a  semi-major axis (m), negative for hyperbolic orbits
245      * @param e eccentricity (positive or equal to 0)
246      * @param i inclination (rad)
247      * @param pa perigee argument (ω, rad)
248      * @param raan right ascension of ascending node (Ω, rad)
249      * @param anomaly mean, eccentric or true anomaly (rad)
250      * @param aDot  semi-major axis derivative (m/s)
251      * @param eDot eccentricity derivative
252      * @param iDot inclination derivative (rad/s)
253      * @param paDot perigee argument derivative (rad/s)
254      * @param raanDot right ascension of ascending node derivative (rad/s)
255      * @param anomalyDot mean, eccentric or true anomaly derivative (rad/s)
256      * @param type type of anomaly
257      * @param frame the frame in which the parameters are defined
258      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
259      * @param date date of the orbital parameters
260      * @param mu central attraction coefficient (m³/s²)
261      * @exception IllegalArgumentException if frame is not a {@link
262      * Frame#isPseudoInertial pseudo-inertial frame} or a and e don't match for hyperbolic orbits,
263      * or v is out of range for hyperbolic orbits
264      * @since 9.0
265      */
266     public KeplerianOrbit(final double a, final double e, final double i,
267                           final double pa, final double raan, final double anomaly,
268                           final double aDot, final double eDot, final double iDot,
269                           final double paDot, final double raanDot, final double anomalyDot,
270                           final PositionAngleType type,
271                           final Frame frame, final AbsoluteDate date, final double mu)
272             throws IllegalArgumentException {
273         this(a, e, i, pa, raan, anomaly, aDot, eDot, iDot, paDot, raanDot, anomalyDot, type, type,
274                 frame, date, mu);
275     }
276 
277     /** Constructor from Cartesian parameters.
278      *
279      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
280      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
281      * use {@code mu} and the position to compute the acceleration, including
282      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
283      *
284      * @param pvCoordinates the PVCoordinates of the satellite
285      * @param frame the frame in which are defined the {@link PVCoordinates}
286      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
287      * @param mu central attraction coefficient (m³/s²)
288      * @exception IllegalArgumentException if frame is not a {@link
289      * Frame#isPseudoInertial pseudo-inertial frame}
290      */
291     public KeplerianOrbit(final TimeStampedPVCoordinates pvCoordinates,
292                           final Frame frame, final double mu)
293             throws IllegalArgumentException {
294         this(pvCoordinates, frame, mu, hasNonKeplerianAcceleration(pvCoordinates, mu));
295     }
296 
297     /** Constructor from Cartesian parameters.
298      *
299      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
300      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
301      * use {@code mu} and the position to compute the acceleration, including
302      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
303      *
304      * @param pvCoordinates the PVCoordinates of the satellite
305      * @param frame the frame in which are defined the {@link PVCoordinates}
306      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
307      * @param mu central attraction coefficient (m³/s²)
308      * @param reliableAcceleration if true, the acceleration is considered to be reliable
309      * @exception IllegalArgumentException if frame is not a {@link
310      * Frame#isPseudoInertial pseudo-inertial frame}
311      */
312     private KeplerianOrbit(final TimeStampedPVCoordinates pvCoordinates,
313                            final Frame frame, final double mu,
314                            final boolean reliableAcceleration)
315             throws IllegalArgumentException {
316         super(pvCoordinates, frame, mu);
317 
318         // compute inclination
319         final Vector3D momentum = pvCoordinates.getMomentum();
320         final double m2 = momentum.getNormSq();
321         i = Vector3D.angle(momentum, Vector3D.PLUS_K);
322 
323         // compute right ascension of ascending node
324         raan = Vector3D.crossProduct(Vector3D.PLUS_K, momentum).getAlpha();
325 
326         // preliminary computations for parameters depending on orbit shape (elliptic or hyperbolic)
327         final Vector3D pvP     = pvCoordinates.getPosition();
328         final Vector3D pvV     = pvCoordinates.getVelocity();
329         final Vector3D pvA     = pvCoordinates.getAcceleration();
330         final double   r2      = pvP.getNormSq();
331         final double   r       = FastMath.sqrt(r2);
332         final double   V2      = pvV.getNormSq();
333         final double   rV2OnMu = r * V2 / mu;
334 
335         // compute semi-major axis (will be negative for hyperbolic orbits)
336         a = r / (2 - rV2OnMu);
337         final double muA = mu * a;
338 
339         // compute cached anomaly
340         if (isElliptical()) {
341             // elliptic or circular orbit
342             final double eSE = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(muA);
343             final double eCE = rV2OnMu - 1;
344             e = FastMath.sqrt(eSE * eSE + eCE * eCE);
345             this.cachedPositionAngleType = PositionAngleType.ECCENTRIC;
346             cachedAnomaly = FastMath.atan2(eSE, eCE);
347         } else {
348             // hyperbolic orbit
349             final double eSH = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(-muA);
350             final double eCH = rV2OnMu - 1;
351             e = FastMath.sqrt(1 - m2 / muA);
352             this.cachedPositionAngleType = PositionAngleType.TRUE;
353             cachedAnomaly = KeplerianAnomalyUtility.hyperbolicEccentricToTrue(e, FastMath.log((eCH + eSH) / (eCH - eSH)) / 2);
354         }
355 
356         // Checking eccentricity range
357         checkParameterRangeInclusive(ECCENTRICITY, e, 0.0, Double.POSITIVE_INFINITY);
358 
359         // compute perigee argument
360         final Vector3D node = new Vector3D(raan, 0.0);
361         final double px = Vector3D.dotProduct(pvP, node);
362         final double py = Vector3D.dotProduct(pvP, Vector3D.crossProduct(momentum, node)) / FastMath.sqrt(m2);
363         pa = FastMath.atan2(py, px) - getTrueAnomaly();
364 
365         partialPV = pvCoordinates;
366 
367         if (reliableAcceleration) {
368             // we have a relevant acceleration, we can compute derivatives
369 
370             final double[][] jacobian = new double[6][6];
371             getJacobianWrtCartesian(PositionAngleType.MEAN, jacobian);
372 
373             final Vector3D keplerianAcceleration    = new Vector3D(-mu / (r * r2), pvP);
374             final Vector3D nonKeplerianAcceleration = pvA.subtract(keplerianAcceleration);
375             final double   aX                       = nonKeplerianAcceleration.getX();
376             final double   aY                       = nonKeplerianAcceleration.getY();
377             final double   aZ                       = nonKeplerianAcceleration.getZ();
378             aDot    = jacobian[0][3] * aX + jacobian[0][4] * aY + jacobian[0][5] * aZ;
379             eDot    = jacobian[1][3] * aX + jacobian[1][4] * aY + jacobian[1][5] * aZ;
380             iDot    = jacobian[2][3] * aX + jacobian[2][4] * aY + jacobian[2][5] * aZ;
381             paDot   = jacobian[3][3] * aX + jacobian[3][4] * aY + jacobian[3][5] * aZ;
382             raanDot = jacobian[4][3] * aX + jacobian[4][4] * aY + jacobian[4][5] * aZ;
383 
384             // in order to compute cached anomaly derivative, we must compute
385             // mean anomaly derivative including Keplerian motion and convert to required anomaly
386             final double MDot = getKeplerianMeanMotion() +
387                     jacobian[5][3] * aX + jacobian[5][4] * aY + jacobian[5][5] * aZ;
388             final UnivariateDerivative1 eUD = new UnivariateDerivative1(e, eDot);
389             final UnivariateDerivative1 MUD = new UnivariateDerivative1(getMeanAnomaly(), MDot);
390             if (cachedPositionAngleType == PositionAngleType.ECCENTRIC) {
391                 final UnivariateDerivative1 EUD = (a < 0) ?
392                         FieldKeplerianAnomalyUtility.hyperbolicMeanToEccentric(eUD, MUD) :
393                         FieldKeplerianAnomalyUtility.ellipticMeanToEccentric(eUD, MUD);
394                 cachedAnomalyDot = EUD.getFirstDerivative();
395             } else { // TRUE
396                 final UnivariateDerivative1 vUD = (a < 0) ?
397                         FieldKeplerianAnomalyUtility.hyperbolicMeanToTrue(eUD, MUD) :
398                         FieldKeplerianAnomalyUtility.ellipticMeanToTrue(eUD, MUD);
399                 cachedAnomalyDot = vUD.getFirstDerivative();
400             }
401 
402         } else {
403             // acceleration is either almost zero or NaN,
404             // we assume acceleration was not known
405             aDot    = 0.;
406             eDot    = 0.;
407             iDot    = 0.;
408             paDot   = 0.;
409             raanDot = 0.;
410             cachedAnomalyDot = computeKeplerianAnomalyDot(cachedPositionAngleType, a, e, mu, cachedAnomaly, cachedPositionAngleType);
411         }
412 
413     }
414 
415     /** Constructor from Cartesian parameters.
416      *
417      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
418      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
419      * use {@code mu} and the position to compute the acceleration, including
420      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
421      *
422      * @param pvCoordinates the PVCoordinates of the satellite
423      * @param frame the frame in which are defined the {@link PVCoordinates}
424      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
425      * @param date date of the orbital parameters
426      * @param mu central attraction coefficient (m³/s²)
427      * @exception IllegalArgumentException if frame is not a {@link
428      * Frame#isPseudoInertial pseudo-inertial frame}
429      */
430     public KeplerianOrbit(final PVCoordinates pvCoordinates,
431                           final Frame frame, final AbsoluteDate date, final double mu)
432             throws IllegalArgumentException {
433         this(new TimeStampedPVCoordinates(date, pvCoordinates), frame, mu);
434     }
435 
436     /** Constructor from any kind of orbital parameters.
437      * @param op orbital parameters to copy
438      */
439     public KeplerianOrbit(final Orbit op) {
440         this(op.getPVCoordinates(), op.getFrame(), op.getMu(), op.hasNonKeplerianAcceleration());
441     }
442 
443     /** {@inheritDoc} */
444     @Override
445     public boolean hasNonKeplerianAcceleration() {
446         return aDot != 0. || eDot != 0. || paDot != 0. || iDot != 0. || raanDot != 0. ||
447                 FastMath.abs(cachedAnomalyDot - computeKeplerianAnomalyDot(cachedPositionAngleType, a, e, getMu(), cachedAnomaly, cachedPositionAngleType)) > TOLERANCE_POSITION_ANGLE_RATE;
448     }
449 
450     /** {@inheritDoc} */
451     @Override
452     public OrbitType getType() {
453         return OrbitType.KEPLERIAN;
454     }
455 
456     /** {@inheritDoc} */
457     @Override
458     public double getA() {
459         return a;
460     }
461 
462     /** {@inheritDoc} */
463     @Override
464     public double getADot() {
465         return aDot;
466     }
467 
468     /** {@inheritDoc} */
469     @Override
470     public double getE() {
471         return e;
472     }
473 
474     /** {@inheritDoc} */
475     @Override
476     public double getEDot() {
477         return eDot;
478     }
479 
480     /** {@inheritDoc} */
481     @Override
482     public double getI() {
483         return i;
484     }
485 
486     /** {@inheritDoc} */
487     @Override
488     public double getIDot() {
489         return iDot;
490     }
491 
492     /** Get the perigee argument.
493      * @return perigee argument (rad)
494      */
495     public double getPerigeeArgument() {
496         return pa;
497     }
498 
499     /** Get the perigee argument derivative.
500      * <p>
501      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
502      * </p>
503      * @return perigee argument derivative (rad/s)
504      * @since 9.0
505      */
506     public double getPerigeeArgumentDot() {
507         return paDot;
508     }
509 
510     /** Get the right ascension of the ascending node.
511      * @return right ascension of the ascending node (rad)
512      */
513     public double getRightAscensionOfAscendingNode() {
514         return raan;
515     }
516 
517     /** Get the right ascension of the ascending node derivative.
518      * <p>
519      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
520      * </p>
521      * @return right ascension of the ascending node derivative (rad/s)
522      * @since 9.0
523      */
524     public double getRightAscensionOfAscendingNodeDot() {
525         return raanDot;
526     }
527 
528     /** Get the true anomaly.
529      * @return true anomaly (rad)
530      */
531     public double getTrueAnomaly() {
532         switch (cachedPositionAngleType) {
533             case MEAN: return (a < 0.) ? KeplerianAnomalyUtility.hyperbolicMeanToTrue(e, cachedAnomaly) :
534                     KeplerianAnomalyUtility.ellipticMeanToTrue(e, cachedAnomaly);
535 
536             case TRUE: return cachedAnomaly;
537 
538             case ECCENTRIC: return (a < 0.) ? KeplerianAnomalyUtility.hyperbolicEccentricToTrue(e, cachedAnomaly) :
539                     KeplerianAnomalyUtility.ellipticEccentricToTrue(e, cachedAnomaly);
540 
541             default: throw new OrekitInternalError(null);
542         }
543     }
544 
545     /** Get the true anomaly derivative.
546      * @return true anomaly derivative (rad/s)
547      */
548     public double getTrueAnomalyDot() {
549         switch (cachedPositionAngleType) {
550             case MEAN:
551                 final UnivariateDerivative1 eUD = new UnivariateDerivative1(e, eDot);
552                 final UnivariateDerivative1 MUD = new UnivariateDerivative1(cachedAnomaly, cachedAnomalyDot);
553                 final UnivariateDerivative1 vUD = (a < 0) ?
554                         FieldKeplerianAnomalyUtility.hyperbolicMeanToTrue(eUD, MUD) :
555                         FieldKeplerianAnomalyUtility.ellipticMeanToTrue(eUD, MUD);
556                 return vUD.getFirstDerivative();
557 
558             case TRUE:
559                 return cachedAnomalyDot;
560 
561             case ECCENTRIC:
562                 final UnivariateDerivative1 eUD2 = new UnivariateDerivative1(e, eDot);
563                 final UnivariateDerivative1 EUD = new UnivariateDerivative1(cachedAnomaly, cachedAnomalyDot);
564                 final UnivariateDerivative1 vUD2 = (a < 0) ?
565                         FieldKeplerianAnomalyUtility.hyperbolicEccentricToTrue(eUD2, EUD) :
566                         FieldKeplerianAnomalyUtility.ellipticEccentricToTrue(eUD2, EUD);
567                 return vUD2.getFirstDerivative();
568 
569             default:
570                 throw new OrekitInternalError(null);
571         }
572     }
573 
574     /** Get the eccentric anomaly.
575      * @return eccentric anomaly (rad)
576      */
577     public double getEccentricAnomaly() {
578         switch (cachedPositionAngleType) {
579             case MEAN:
580                 return (a < 0.) ? KeplerianAnomalyUtility.hyperbolicMeanToEccentric(e, cachedAnomaly) :
581                     KeplerianAnomalyUtility.ellipticMeanToEccentric(e, cachedAnomaly);
582 
583             case ECCENTRIC:
584                 return cachedAnomaly;
585 
586             case TRUE:
587                 return (a < 0.) ? KeplerianAnomalyUtility.hyperbolicTrueToEccentric(e, cachedAnomaly) :
588                     KeplerianAnomalyUtility.ellipticTrueToEccentric(e, cachedAnomaly);
589 
590             default:
591                 throw new OrekitInternalError(null);
592         }
593     }
594 
595     /** Get the eccentric anomaly derivative.
596      * @return eccentric anomaly derivative (rad/s)
597      * @since 9.0
598      */
599     public double getEccentricAnomalyDot() {
600         switch (cachedPositionAngleType) {
601             case ECCENTRIC:
602                 return cachedAnomalyDot;
603 
604             case TRUE:
605                 final UnivariateDerivative1 eUD = new UnivariateDerivative1(e, eDot);
606                 final UnivariateDerivative1 vUD = new UnivariateDerivative1(cachedAnomaly, cachedAnomalyDot);
607                 final UnivariateDerivative1 EUD = (a < 0) ?
608                         FieldKeplerianAnomalyUtility.hyperbolicTrueToEccentric(eUD, vUD) :
609                         FieldKeplerianAnomalyUtility.ellipticTrueToEccentric(eUD, vUD);
610                 return EUD.getFirstDerivative();
611 
612             case MEAN:
613                 final UnivariateDerivative1 eUD2 = new UnivariateDerivative1(e, eDot);
614                 final UnivariateDerivative1 MUD = new UnivariateDerivative1(cachedAnomaly, cachedAnomalyDot);
615                 final UnivariateDerivative1 EUD2 = (a < 0) ?
616                         FieldKeplerianAnomalyUtility.hyperbolicMeanToEccentric(eUD2, MUD) :
617                         FieldKeplerianAnomalyUtility.ellipticMeanToEccentric(eUD2, MUD);
618                 return EUD2.getFirstDerivative();
619 
620             default:
621                 throw new OrekitInternalError(null);
622         }
623     }
624 
625     /** Get the mean anomaly.
626      * @return mean anomaly (rad)
627      */
628     public double getMeanAnomaly() {
629         switch (cachedPositionAngleType) {
630             case ECCENTRIC: return (a < 0.) ? KeplerianAnomalyUtility.hyperbolicEccentricToMean(e, cachedAnomaly) :
631                     KeplerianAnomalyUtility.ellipticEccentricToMean(e, cachedAnomaly);
632 
633             case MEAN: return cachedAnomaly;
634 
635             case TRUE: return (a < 0.) ? KeplerianAnomalyUtility.hyperbolicTrueToMean(e, cachedAnomaly) :
636                     KeplerianAnomalyUtility.ellipticTrueToMean(e, cachedAnomaly);
637 
638             default: throw new OrekitInternalError(null);
639         }
640     }
641 
642     /** Get the mean anomaly derivative.
643      * @return mean anomaly derivative (rad/s)
644      * @since 9.0
645      */
646     public double getMeanAnomalyDot() {
647         switch (cachedPositionAngleType) {
648             case MEAN:
649                 return cachedAnomalyDot;
650 
651             case ECCENTRIC:
652                 final UnivariateDerivative1 eUD = new UnivariateDerivative1(e, eDot);
653                 final UnivariateDerivative1 EUD = new UnivariateDerivative1(cachedAnomaly, cachedAnomalyDot);
654                 final UnivariateDerivative1 MUD = (a < 0) ?
655                         FieldKeplerianAnomalyUtility.hyperbolicEccentricToMean(eUD, EUD) :
656                         FieldKeplerianAnomalyUtility.ellipticEccentricToMean(eUD, EUD);
657                 return MUD.getFirstDerivative();
658 
659             case TRUE:
660                 final UnivariateDerivative1 eUD2 = new UnivariateDerivative1(e, eDot);
661                 final UnivariateDerivative1 vUD = new UnivariateDerivative1(cachedAnomaly, cachedAnomalyDot);
662                 final UnivariateDerivative1 MUD2 = (a < 0) ?
663                         FieldKeplerianAnomalyUtility.hyperbolicTrueToMean(eUD2, vUD) :
664                         FieldKeplerianAnomalyUtility.ellipticTrueToMean(eUD2, vUD);
665                 return MUD2.getFirstDerivative();
666 
667             default:
668                 throw new OrekitInternalError(null);
669         }
670     }
671 
672     /** Get the anomaly.
673      * @param type type of the angle
674      * @return anomaly (rad)
675      */
676     public double getAnomaly(final PositionAngleType type) {
677         return (type == PositionAngleType.MEAN) ? getMeanAnomaly() :
678                 ((type == PositionAngleType.ECCENTRIC) ? getEccentricAnomaly() :
679                         getTrueAnomaly());
680     }
681 
682     /** Get the anomaly derivative.
683      * @param type type of the angle
684      * @return anomaly derivative (rad/s)
685      * @since 9.0
686      */
687     public double getAnomalyDot(final PositionAngleType type) {
688         return (type == PositionAngleType.MEAN) ? getMeanAnomalyDot() :
689                 ((type == PositionAngleType.ECCENTRIC) ? getEccentricAnomalyDot() :
690                         getTrueAnomalyDot());
691     }
692 
693     /** {@inheritDoc} */
694     @Override
695     public double getEquinoctialEx() {
696         return e * FastMath.cos(pa + raan);
697     }
698 
699     /** {@inheritDoc} */
700     @Override
701     public double getEquinoctialExDot() {
702         if (!hasNonKeplerianAcceleration()) {
703             return 0.;
704         }
705         final double paPraan = pa + raan;
706         final SinCos sc      = FastMath.sinCos(paPraan);
707         return eDot * sc.cos() - e * sc.sin() * (paDot + raanDot);
708     }
709 
710     /** {@inheritDoc} */
711     @Override
712     public double getEquinoctialEy() {
713         return e * FastMath.sin(pa + raan);
714     }
715 
716     /** {@inheritDoc} */
717     @Override
718     public double getEquinoctialEyDot() {
719         if (!hasNonKeplerianAcceleration()) {
720             return 0.;
721         }
722         final double paPraan = pa + raan;
723         final SinCos sc      = FastMath.sinCos(paPraan);
724         return eDot * sc.sin() + e * sc.cos() * (paDot + raanDot);
725     }
726 
727     /** {@inheritDoc} */
728     @Override
729     public double getHx() {
730         // Check for equatorial retrograde orbit
731         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
732             return Double.NaN;
733         }
734         return FastMath.cos(raan) * FastMath.tan(0.5 * i);
735     }
736 
737     /** {@inheritDoc} */
738     @Override
739     public double getHxDot() {
740         // Check for equatorial retrograde orbit
741         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
742             return Double.NaN;
743         }
744         if (!hasNonKeplerianAcceleration()) {
745             return 0.;
746         }
747         final SinCos sc      = FastMath.sinCos(raan);
748         final double tan     = FastMath.tan(0.5 * i);
749         return 0.5 * (1 + tan * tan) * sc.cos() * iDot - tan * sc.sin() * raanDot;
750     }
751 
752     /** {@inheritDoc} */
753     @Override
754     public double getHy() {
755         // Check for equatorial retrograde orbit
756         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
757             return Double.NaN;
758         }
759         return FastMath.sin(raan) * FastMath.tan(0.5 * i);
760     }
761 
762     /** {@inheritDoc} */
763     @Override
764     public double getHyDot() {
765         // Check for equatorial retrograde orbit
766         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
767             return Double.NaN;
768         }
769         if (!hasNonKeplerianAcceleration()) {
770             return 0.;
771         }
772         final SinCos sc      = FastMath.sinCos(raan);
773         final double tan     = FastMath.tan(0.5 * i);
774         return 0.5 * (1 + tan * tan) * sc.sin() * iDot + tan * sc.cos() * raanDot;
775     }
776 
777     /** {@inheritDoc} */
778     @Override
779     public double getLv() {
780         return pa + raan + getTrueAnomaly();
781     }
782 
783     /** {@inheritDoc} */
784     @Override
785     public double getLvDot() {
786         return paDot + raanDot + getTrueAnomalyDot();
787     }
788 
789     /** {@inheritDoc} */
790     @Override
791     public double getLE() {
792         return pa + raan + getEccentricAnomaly();
793     }
794 
795     /** {@inheritDoc} */
796     @Override
797     public double getLEDot() {
798         return paDot + raanDot + getEccentricAnomalyDot();
799     }
800 
801     /** {@inheritDoc} */
802     @Override
803     public double getLM() {
804         return pa + raan + getMeanAnomaly();
805     }
806 
807     /** {@inheritDoc} */
808     @Override
809     public double getLMDot() {
810         return paDot + raanDot + getMeanAnomalyDot();
811     }
812 
813     /** Initialize cached anomaly with rate.
814      * @param anomaly input anomaly
815      * @param anomalyDot rate of input anomaly
816      * @param inputType position angle type passed as input
817      * @return anomaly to cache with rate
818      * @since 12.1
819      */
820     private UnivariateDerivative1 initializeCachedAnomaly(final double anomaly, final double anomalyDot,
821                                                           final PositionAngleType inputType) {
822         if (cachedPositionAngleType == inputType) {
823             return new UnivariateDerivative1(anomaly, anomalyDot);
824 
825         } else {
826             final UnivariateDerivative1 eUD = new UnivariateDerivative1(e, eDot);
827             final UnivariateDerivative1 anomalyUD = new UnivariateDerivative1(anomaly, anomalyDot);
828 
829             if (a < 0) {
830                 switch (cachedPositionAngleType) {
831                     case MEAN:
832                         if (inputType == PositionAngleType.ECCENTRIC) {
833                             return FieldKeplerianAnomalyUtility.hyperbolicEccentricToMean(eUD, anomalyUD);
834                         } else {
835                             return FieldKeplerianAnomalyUtility.hyperbolicTrueToMean(eUD, anomalyUD);
836                         }
837 
838                     case ECCENTRIC:
839                         if (inputType == PositionAngleType.MEAN) {
840                             return FieldKeplerianAnomalyUtility.hyperbolicMeanToEccentric(eUD, anomalyUD);
841                         } else {
842                             return FieldKeplerianAnomalyUtility.hyperbolicTrueToEccentric(eUD, anomalyUD);
843                         }
844 
845                     case TRUE:
846                         if (inputType == PositionAngleType.MEAN) {
847                             return FieldKeplerianAnomalyUtility.hyperbolicMeanToTrue(eUD, anomalyUD);
848                         } else {
849                             return FieldKeplerianAnomalyUtility.hyperbolicEccentricToTrue(eUD, anomalyUD);
850                         }
851 
852                     default:
853                         break;
854                 }
855 
856             } else {
857                 switch (cachedPositionAngleType) {
858                     case MEAN:
859                         if (inputType == PositionAngleType.ECCENTRIC) {
860                             return FieldKeplerianAnomalyUtility.ellipticEccentricToMean(eUD, anomalyUD);
861                         } else {
862                             return FieldKeplerianAnomalyUtility.ellipticTrueToMean(eUD, anomalyUD);
863                         }
864 
865                     case ECCENTRIC:
866                         if (inputType == PositionAngleType.MEAN) {
867                             return FieldKeplerianAnomalyUtility.ellipticMeanToEccentric(eUD, anomalyUD);
868                         } else {
869                             return FieldKeplerianAnomalyUtility.ellipticTrueToEccentric(eUD, anomalyUD);
870                         }
871 
872                     case TRUE:
873                         if (inputType == PositionAngleType.MEAN) {
874                             return FieldKeplerianAnomalyUtility.ellipticMeanToTrue(eUD, anomalyUD);
875                         } else {
876                             return FieldKeplerianAnomalyUtility.ellipticEccentricToTrue(eUD, anomalyUD);
877                         }
878 
879                     default:
880                         break;
881                 }
882 
883             }
884             throw new OrekitInternalError(null);
885         }
886 
887     }
888 
889     /** Compute reference axes.
890      * @return reference axes
891      * @since 12.0
892      */
893     private Vector3D[] referenceAxes() {
894         // preliminary variables
895         final SinCos scRaan  = FastMath.sinCos(raan);
896         final SinCos scPa    = FastMath.sinCos(pa);
897         final SinCos scI     = FastMath.sinCos(i);
898         final double cosRaan = scRaan.cos();
899         final double sinRaan = scRaan.sin();
900         final double cosPa   = scPa.cos();
901         final double sinPa   = scPa.sin();
902         final double cosI    = scI.cos();
903         final double sinI    = scI.sin();
904 
905         final double crcp    = cosRaan * cosPa;
906         final double crsp    = cosRaan * sinPa;
907         final double srcp    = sinRaan * cosPa;
908         final double srsp    = sinRaan * sinPa;
909 
910         // reference axes defining the orbital plane
911         return new Vector3D[] {
912             new Vector3D( crcp - cosI * srsp,  srcp + cosI * crsp, sinI * sinPa),
913             new Vector3D(-crsp - cosI * srcp, -srsp + cosI * crcp, sinI * cosPa)
914         };
915 
916     }
917 
918     /** Compute position and velocity but not acceleration.
919      */
920     private void computePVWithoutA() {
921 
922         if (partialPV != null) {
923             // already computed
924             return;
925         }
926 
927         final Vector3D[] axes = referenceAxes();
928 
929         if (isElliptical()) {
930 
931             // elliptical case
932 
933             // elliptic eccentric anomaly
934             final double uME2   = (1 - e) * (1 + e);
935             final double s1Me2  = FastMath.sqrt(uME2);
936             final SinCos scE    = FastMath.sinCos(getEccentricAnomaly());
937             final double cosE   = scE.cos();
938             final double sinE   = scE.sin();
939 
940             // coordinates of position and velocity in the orbital plane
941             final double x      = a * (cosE - e);
942             final double y      = a * sinE * s1Me2;
943             final double factor = FastMath.sqrt(getMu() / a) / (1 - e * cosE);
944             final double xDot   = -sinE * factor;
945             final double yDot   =  cosE * s1Me2 * factor;
946 
947             final Vector3D position = new Vector3D(x, axes[0], y, axes[1]);
948             final Vector3D velocity = new Vector3D(xDot, axes[0], yDot, axes[1]);
949             partialPV = new PVCoordinates(position, velocity);
950 
951         } else {
952 
953             // hyperbolic case
954 
955             // compute position and velocity factors
956             final SinCos scV       = FastMath.sinCos(getTrueAnomaly());
957             final double sinV      = scV.sin();
958             final double cosV      = scV.cos();
959             final double f         = a * (1 - e * e);
960             final double posFactor = f / (1 + e * cosV);
961             final double velFactor = FastMath.sqrt(getMu() / f);
962 
963             final double   x            =  posFactor * cosV;
964             final double   y            =  posFactor * sinV;
965             final double   xDot         = -velFactor * sinV;
966             final double   yDot         =  velFactor * (e + cosV);
967 
968             final Vector3D position = new Vector3D(x, axes[0], y, axes[1]);
969             final Vector3D velocity = new Vector3D(xDot, axes[0], yDot, axes[1]);
970             partialPV = new PVCoordinates(position, velocity);
971 
972         }
973 
974     }
975 
976     /** {@inheritDoc} */
977     @Override
978     protected Vector3D initPosition() {
979 
980         final Vector3D[] axes = referenceAxes();
981 
982         if (isElliptical()) {
983 
984             // elliptical case
985 
986             // elliptic eccentric anomaly
987             final double uME2   = (1 - e) * (1 + e);
988             final double s1Me2  = FastMath.sqrt(uME2);
989             final SinCos scE    = FastMath.sinCos(getEccentricAnomaly());
990             final double cosE   = scE.cos();
991             final double sinE   = scE.sin();
992 
993             return new Vector3D(a * (cosE - e), axes[0], a * sinE * s1Me2, axes[1]);
994 
995         } else {
996 
997             // hyperbolic case
998 
999             // compute position and velocity factors
1000             final SinCos scV       = FastMath.sinCos(getTrueAnomaly());
1001             final double sinV      = scV.sin();
1002             final double cosV      = scV.cos();
1003             final double f         = a * (1 - e * e);
1004             final double posFactor = f / (1 + e * cosV);
1005 
1006             return new Vector3D(posFactor * cosV, axes[0], posFactor * sinV, axes[1]);
1007 
1008         }
1009 
1010     }
1011 
1012     /** {@inheritDoc} */
1013     @Override
1014     protected TimeStampedPVCoordinates initPVCoordinates() {
1015 
1016         // position and velocity
1017         computePVWithoutA();
1018 
1019         // acceleration
1020         final double r2 = partialPV.getPosition().getNormSq();
1021         final Vector3D keplerianAcceleration = new Vector3D(-getMu() / (r2 * FastMath.sqrt(r2)), partialPV.getPosition());
1022         final Vector3D acceleration = hasNonKeplerianAcceleration() ?
1023                 keplerianAcceleration.add(nonKeplerianAcceleration()) :
1024                 keplerianAcceleration;
1025 
1026         return new TimeStampedPVCoordinates(getDate(), partialPV.getPosition(), partialPV.getVelocity(), acceleration);
1027 
1028     }
1029 
1030     /** {@inheritDoc} */
1031     @Override
1032     public KeplerianOrbit inFrame(final Frame inertialFrame) {
1033         final PVCoordinates pvCoordinates;
1034         if (hasNonKeplerianAcceleration()) {
1035             pvCoordinates = getPVCoordinates(inertialFrame);
1036         } else {
1037             final KinematicTransform transform = getFrame().getKinematicTransformTo(inertialFrame, getDate());
1038             pvCoordinates = transform.transformOnlyPV(getPVCoordinates());
1039         }
1040         final KeplerianOrbit keplerianOrbit = new KeplerianOrbit(pvCoordinates, inertialFrame, getDate(), getMu());
1041         if (keplerianOrbit.getCachedPositionAngleType() == getCachedPositionAngleType()) {
1042             return keplerianOrbit;
1043         } else {
1044             return keplerianOrbit.withCachedPositionAngleType(getCachedPositionAngleType());
1045         }
1046     }
1047 
1048     /** {@inheritDoc} */
1049     @Override
1050     public KeplerianOrbit withCachedPositionAngleType(final PositionAngleType positionAngleType) {
1051         return new KeplerianOrbit(a, e, i, pa, raan, getAnomaly(positionAngleType), aDot, eDot, iDot, paDot, raanDot,
1052                 getAnomalyDot(positionAngleType), positionAngleType, getFrame(), getDate(), getMu());
1053     }
1054 
1055     /** {@inheritDoc} */
1056     @Override
1057     public KeplerianOrbit shiftedBy(final double dt) {
1058         return shiftedBy(new TimeOffset(dt));
1059     }
1060 
1061     /** {@inheritDoc} */
1062     @Override
1063     public KeplerianOrbit shiftedBy(final TimeOffset dt) {
1064 
1065         final double dtS = dt.toDouble();
1066 
1067         // use Keplerian-only motion
1068         final KeplerianOrbit keplerianShifted = new KeplerianOrbit(a, e, i, pa, raan,
1069                 getMeanAnomaly() + getKeplerianMeanMotion() * dtS, PositionAngleType.MEAN,
1070                 cachedPositionAngleType, getFrame(), getDate().shiftedBy(dt), getMu());
1071 
1072         if (dtS != 0. && hasNonKeplerianAcceleration()) {
1073 
1074             // extract non-Keplerian acceleration from first time derivatives
1075             final Vector3D nonKeplerianAcceleration = nonKeplerianAcceleration();
1076 
1077             // add quadratic effect of non-Keplerian acceleration to Keplerian-only shift
1078             keplerianShifted.computePVWithoutA();
1079             final Vector3D fixedP   = new Vector3D(1, keplerianShifted.partialPV.getPosition(),
1080                     0.5 * dtS * dtS, nonKeplerianAcceleration);
1081             final double   fixedR2 = fixedP.getNormSq();
1082             final double   fixedR  = FastMath.sqrt(fixedR2);
1083             final Vector3D fixedV  = new Vector3D(1, keplerianShifted.partialPV.getVelocity(),
1084                     dtS, nonKeplerianAcceleration);
1085             final Vector3D fixedA  = new Vector3D(-getMu() / (fixedR2 * fixedR), keplerianShifted.partialPV.getPosition(),
1086                     1, nonKeplerianAcceleration);
1087 
1088             // build a new orbit, taking non-Keplerian acceleration into account
1089             return new KeplerianOrbit(new TimeStampedPVCoordinates(keplerianShifted.getDate(),
1090                     fixedP, fixedV, fixedA),
1091                     keplerianShifted.getFrame(), keplerianShifted.getMu());
1092 
1093         } else {
1094             // Keplerian-only motion is all we can do
1095             return keplerianShifted;
1096         }
1097 
1098     }
1099 
1100     /** {@inheritDoc} */
1101     @Override
1102     protected double[][] computeJacobianMeanWrtCartesian() {
1103         if (isElliptical()) {
1104             return computeJacobianMeanWrtCartesianElliptical();
1105         } else {
1106             return computeJacobianMeanWrtCartesianHyperbolic();
1107         }
1108     }
1109 
1110     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1111      * <p>
1112      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1113      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1114      * yDot for j=4, zDot for j=5).
1115      * </p>
1116      * @return 6x6 Jacobian matrix
1117      */
1118     private double[][] computeJacobianMeanWrtCartesianElliptical() {
1119 
1120         final double[][] jacobian = new double[6][6];
1121 
1122         // compute various intermediate parameters
1123         computePVWithoutA();
1124         final Vector3D position = partialPV.getPosition();
1125         final Vector3D velocity = partialPV.getVelocity();
1126         final Vector3D momentum = partialPV.getMomentum();
1127         final double v2         = velocity.getNormSq();
1128         final double r2         = position.getNormSq();
1129         final double r          = FastMath.sqrt(r2);
1130         final double r3         = r * r2;
1131 
1132         final double px         = position.getX();
1133         final double py         = position.getY();
1134         final double pz         = position.getZ();
1135         final double vx         = velocity.getX();
1136         final double vy         = velocity.getY();
1137         final double vz         = velocity.getZ();
1138         final double mx         = momentum.getX();
1139         final double my         = momentum.getY();
1140         final double mz         = momentum.getZ();
1141 
1142         final double mu         = getMu();
1143         final double sqrtMuA    = FastMath.sqrt(a * mu);
1144         final double sqrtAoMu   = FastMath.sqrt(a / mu);
1145         final double a2         = a * a;
1146         final double twoA       = 2 * a;
1147         final double rOnA       = r / a;
1148 
1149         final double oMe2       = 1 - e * e;
1150         final double epsilon    = FastMath.sqrt(oMe2);
1151         final double sqrtRec    = 1 / epsilon;
1152 
1153         final SinCos scI        = FastMath.sinCos(i);
1154         final SinCos scPA       = FastMath.sinCos(pa);
1155         final double cosI       = scI.cos();
1156         final double sinI       = scI.sin();
1157         final double cosPA      = scPA.cos();
1158         final double sinPA      = scPA.sin();
1159 
1160         final double pv         = Vector3D.dotProduct(position, velocity);
1161         final double cosE       = (a - r) / (a * e);
1162         final double sinE       = pv / (e * sqrtMuA);
1163 
1164         // da
1165         final Vector3D vectorAR = new Vector3D(2 * a2 / r3, position);
1166         final Vector3D vectorARDot = velocity.scalarMultiply(2 * a2 / mu);
1167         fillHalfRow(1, vectorAR,    jacobian[0], 0);
1168         fillHalfRow(1, vectorARDot, jacobian[0], 3);
1169 
1170         // de
1171         final double factorER3 = pv / twoA;
1172         final Vector3D vectorER   = new Vector3D(cosE * v2 / (r * mu), position,
1173                 sinE / sqrtMuA, velocity,
1174                 -factorER3 * sinE / sqrtMuA, vectorAR);
1175         final Vector3D vectorERDot = new Vector3D(sinE / sqrtMuA, position,
1176                 cosE * 2 * r / mu, velocity,
1177                 -factorER3 * sinE / sqrtMuA, vectorARDot);
1178         fillHalfRow(1, vectorER,    jacobian[1], 0);
1179         fillHalfRow(1, vectorERDot, jacobian[1], 3);
1180 
1181         // dE / dr (Eccentric anomaly)
1182         final double coefE = cosE / (e * sqrtMuA);
1183         final Vector3D  vectorEAnR =
1184                 new Vector3D(-sinE * v2 / (e * r * mu), position, coefE, velocity,
1185                         -factorER3 * coefE, vectorAR);
1186 
1187         // dE / drDot
1188         final Vector3D  vectorEAnRDot =
1189                 new Vector3D(-sinE * 2 * r / (e * mu), velocity, coefE, position,
1190                         -factorER3 * coefE, vectorARDot);
1191 
1192         // precomputing some more factors
1193         final double s1 = -sinE * pz / r - cosE * vz * sqrtAoMu;
1194         final double s2 = -cosE * pz / r3;
1195         final double s3 = -sinE * vz / (2 * sqrtMuA);
1196         final double t1 = sqrtRec * (cosE * pz / r - sinE * vz * sqrtAoMu);
1197         final double t2 = sqrtRec * (-sinE * pz / r3);
1198         final double t3 = sqrtRec * (cosE - e) * vz / (2 * sqrtMuA);
1199         final double t4 = sqrtRec * (e * sinI * cosPA * sqrtRec - vz * sqrtAoMu);
1200         final Vector3D s = new Vector3D(cosE / r, Vector3D.PLUS_K,
1201                 s1,       vectorEAnR,
1202                 s2,       position,
1203                 s3,       vectorAR);
1204         final Vector3D sDot = new Vector3D(-sinE * sqrtAoMu, Vector3D.PLUS_K,
1205                 s1,               vectorEAnRDot,
1206                 s3,               vectorARDot);
1207         final Vector3D t =
1208                 new Vector3D(sqrtRec * sinE / r, Vector3D.PLUS_K).add(new Vector3D(t1, vectorEAnR,
1209                         t2, position,
1210                         t3, vectorAR,
1211                         t4, vectorER));
1212         final Vector3D tDot = new Vector3D(sqrtRec * (cosE - e) * sqrtAoMu, Vector3D.PLUS_K,
1213                 t1,                              vectorEAnRDot,
1214                 t3,                              vectorARDot,
1215                 t4,                              vectorERDot);
1216 
1217         // di
1218         final double factorI1 = -sinI * sqrtRec / sqrtMuA;
1219         final double i1 =  factorI1;
1220         final double i2 = -factorI1 * mz / twoA;
1221         final double i3 =  factorI1 * mz * e / oMe2;
1222         final double i4 = cosI * sinPA;
1223         final double i5 = cosI * cosPA;
1224         fillHalfRow(i1, new Vector3D(vy, -vx, 0), i2, vectorAR, i3, vectorER, i4, s, i5, t,
1225                 jacobian[2], 0);
1226         fillHalfRow(i1, new Vector3D(-py, px, 0), i2, vectorARDot, i3, vectorERDot, i4, sDot, i5, tDot,
1227                 jacobian[2], 3);
1228 
1229         // dpa
1230         fillHalfRow(cosPA / sinI, s,    -sinPA / sinI, t,    jacobian[3], 0);
1231         fillHalfRow(cosPA / sinI, sDot, -sinPA / sinI, tDot, jacobian[3], 3);
1232 
1233         // dRaan
1234         final double factorRaanR = 1 / (mu * a * oMe2 * sinI * sinI);
1235         fillHalfRow(-factorRaanR * my, new Vector3D(  0, vz, -vy),
1236                 factorRaanR * mx, new Vector3D(-vz,  0,  vx),
1237                 jacobian[4], 0);
1238         fillHalfRow(-factorRaanR * my, new Vector3D( 0, -pz,  py),
1239                 factorRaanR * mx, new Vector3D(pz,   0, -px),
1240                 jacobian[4], 3);
1241 
1242         // dM
1243         fillHalfRow(rOnA, vectorEAnR,    -sinE, vectorER,    jacobian[5], 0);
1244         fillHalfRow(rOnA, vectorEAnRDot, -sinE, vectorERDot, jacobian[5], 3);
1245 
1246         return jacobian;
1247 
1248     }
1249 
1250     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1251      * <p>
1252      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1253      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1254      * yDot for j=4, zDot for j=5).
1255      * </p>
1256      * @return 6x6 Jacobian matrix
1257      */
1258     private double[][] computeJacobianMeanWrtCartesianHyperbolic() {
1259 
1260         final double[][] jacobian = new double[6][6];
1261 
1262         // compute various intermediate parameters
1263         computePVWithoutA();
1264         final Vector3D position = partialPV.getPosition();
1265         final Vector3D velocity = partialPV.getVelocity();
1266         final Vector3D momentum = partialPV.getMomentum();
1267         final double r2         = position.getNormSq();
1268         final double r          = FastMath.sqrt(r2);
1269         final double r3         = r * r2;
1270 
1271         final double x          = position.getX();
1272         final double y          = position.getY();
1273         final double z          = position.getZ();
1274         final double vx         = velocity.getX();
1275         final double vy         = velocity.getY();
1276         final double vz         = velocity.getZ();
1277         final double mx         = momentum.getX();
1278         final double my         = momentum.getY();
1279         final double mz         = momentum.getZ();
1280 
1281         final double mu         = getMu();
1282         final double absA       = -a;
1283         final double sqrtMuA    = FastMath.sqrt(absA * mu);
1284         final double a2         = a * a;
1285         final double rOa        = r / absA;
1286 
1287         final SinCos scI        = FastMath.sinCos(i);
1288         final double cosI       = scI.cos();
1289         final double sinI       = scI.sin();
1290 
1291         final double pv         = Vector3D.dotProduct(position, velocity);
1292 
1293         // da
1294         final Vector3D vectorAR = new Vector3D(-2 * a2 / r3, position);
1295         final Vector3D vectorARDot = velocity.scalarMultiply(-2 * a2 / mu);
1296         fillHalfRow(-1, vectorAR,    jacobian[0], 0);
1297         fillHalfRow(-1, vectorARDot, jacobian[0], 3);
1298 
1299         // differentials of the momentum
1300         final double m      = momentum.getNorm();
1301         final double oOm    = 1 / m;
1302         final Vector3D dcXP = new Vector3D(  0,  vz, -vy);
1303         final Vector3D dcYP = new Vector3D(-vz,   0,  vx);
1304         final Vector3D dcZP = new Vector3D( vy, -vx,   0);
1305         final Vector3D dcXV = new Vector3D(  0,  -z,   y);
1306         final Vector3D dcYV = new Vector3D(  z,   0,  -x);
1307         final Vector3D dcZV = new Vector3D( -y,   x,   0);
1308         final Vector3D dCP  = new Vector3D(mx * oOm, dcXP, my * oOm, dcYP, mz * oOm, dcZP);
1309         final Vector3D dCV  = new Vector3D(mx * oOm, dcXV, my * oOm, dcYV, mz * oOm, dcZV);
1310 
1311         // dp
1312         final double mOMu   = m / mu;
1313         final Vector3D dpP  = new Vector3D(2 * mOMu, dCP);
1314         final Vector3D dpV  = new Vector3D(2 * mOMu, dCV);
1315 
1316         // de
1317         final double p      = m * mOMu;
1318         final double moO2ae = 1 / (2 * absA * e);
1319         final double m2OaMu = -p / absA;
1320         fillHalfRow(moO2ae, dpP, m2OaMu * moO2ae, vectorAR,    jacobian[1], 0);
1321         fillHalfRow(moO2ae, dpV, m2OaMu * moO2ae, vectorARDot, jacobian[1], 3);
1322 
1323         // di
1324         final double cI1 = 1 / (m * sinI);
1325         final double cI2 = cosI * cI1;
1326         fillHalfRow(cI2, dCP, -cI1, dcZP, jacobian[2], 0);
1327         fillHalfRow(cI2, dCV, -cI1, dcZV, jacobian[2], 3);
1328 
1329         // dPA
1330         final double cP1     =  y * oOm;
1331         final double cP2     = -x * oOm;
1332         final double cP3     = -(mx * cP1 + my * cP2);
1333         final double cP4     = cP3 * oOm;
1334         final double cP5     = -1 / (r2 * sinI * sinI);
1335         final double cP6     = z  * cP5;
1336         final double cP7     = cP3 * cP5;
1337         final Vector3D dacP  = new Vector3D(cP1, dcXP, cP2, dcYP, cP4, dCP, oOm, new Vector3D(-my, mx, 0));
1338         final Vector3D dacV  = new Vector3D(cP1, dcXV, cP2, dcYV, cP4, dCV);
1339         final Vector3D dpoP  = new Vector3D(cP6, dacP, cP7, Vector3D.PLUS_K);
1340         final Vector3D dpoV  = new Vector3D(cP6, dacV);
1341 
1342         final double re2     = r2 * e * e;
1343         final double recOre2 = (p - r) / re2;
1344         final double resOre2 = (pv * mOMu) / re2;
1345         final Vector3D dreP  = new Vector3D(mOMu, velocity, pv / mu, dCP);
1346         final Vector3D dreV  = new Vector3D(mOMu, position, pv / mu, dCV);
1347         final Vector3D davP  = new Vector3D(-resOre2, dpP, recOre2, dreP, resOre2 / r, position);
1348         final Vector3D davV  = new Vector3D(-resOre2, dpV, recOre2, dreV);
1349         fillHalfRow(1, dpoP, -1, davP, jacobian[3], 0);
1350         fillHalfRow(1, dpoV, -1, davV, jacobian[3], 3);
1351 
1352         // dRAAN
1353         final double cO0 = cI1 * cI1;
1354         final double cO1 =  mx * cO0;
1355         final double cO2 = -my * cO0;
1356         fillHalfRow(cO1, dcYP, cO2, dcXP, jacobian[4], 0);
1357         fillHalfRow(cO1, dcYV, cO2, dcXV, jacobian[4], 3);
1358 
1359         // dM
1360         final double s2a    = pv / (2 * absA);
1361         final double oObux  = 1 / FastMath.sqrt(m * m + mu * absA);
1362         final double scasbu = pv * oObux;
1363         final Vector3D dauP = new Vector3D(1 / sqrtMuA, velocity, -s2a / sqrtMuA, vectorAR);
1364         final Vector3D dauV = new Vector3D(1 / sqrtMuA, position, -s2a / sqrtMuA, vectorARDot);
1365         final Vector3D dbuP = new Vector3D(oObux * mu / 2, vectorAR,    m * oObux, dCP);
1366         final Vector3D dbuV = new Vector3D(oObux * mu / 2, vectorARDot, m * oObux, dCV);
1367         final Vector3D dcuP = new Vector3D(oObux, velocity, -scasbu * oObux, dbuP);
1368         final Vector3D dcuV = new Vector3D(oObux, position, -scasbu * oObux, dbuV);
1369         fillHalfRow(1, dauP, -e / (1 + rOa), dcuP, jacobian[5], 0);
1370         fillHalfRow(1, dauV, -e / (1 + rOa), dcuV, jacobian[5], 3);
1371 
1372         return jacobian;
1373 
1374     }
1375 
1376     /** {@inheritDoc} */
1377     @Override
1378     protected double[][] computeJacobianEccentricWrtCartesian() {
1379         if (isElliptical()) {
1380             return computeJacobianEccentricWrtCartesianElliptical();
1381         } else {
1382             return computeJacobianEccentricWrtCartesianHyperbolic();
1383         }
1384     }
1385 
1386     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1387      * <p>
1388      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1389      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1390      * yDot for j=4, zDot for j=5).
1391      * </p>
1392      * @return 6x6 Jacobian matrix
1393      */
1394     private double[][] computeJacobianEccentricWrtCartesianElliptical() {
1395 
1396         // start by computing the Jacobian with mean angle
1397         final double[][] jacobian = computeJacobianMeanWrtCartesianElliptical();
1398 
1399         // Differentiating the Kepler equation M = E - e sin E leads to:
1400         // dM = (1 - e cos E) dE - sin E de
1401         // which is inverted and rewritten as:
1402         // dE = a/r dM + sin E a/r de
1403         final SinCos scE              = FastMath.sinCos(getEccentricAnomaly());
1404         final double aOr              = 1 / (1 - e * scE.cos());
1405 
1406         // update anomaly row
1407         final double[] eRow           = jacobian[1];
1408         final double[] anomalyRow     = jacobian[5];
1409         for (int j = 0; j < anomalyRow.length; ++j) {
1410             anomalyRow[j] = aOr * (anomalyRow[j] + scE.sin() * eRow[j]);
1411         }
1412 
1413         return jacobian;
1414 
1415     }
1416 
1417     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1418      * <p>
1419      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1420      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1421      * yDot for j=4, zDot for j=5).
1422      * </p>
1423      * @return 6x6 Jacobian matrix
1424      */
1425     private double[][] computeJacobianEccentricWrtCartesianHyperbolic() {
1426 
1427         // start by computing the Jacobian with mean angle
1428         final double[][] jacobian = computeJacobianMeanWrtCartesianHyperbolic();
1429 
1430         // Differentiating the Kepler equation M = e sinh H - H leads to:
1431         // dM = (e cosh H - 1) dH + sinh H de
1432         // which is inverted and rewritten as:
1433         // dH = 1 / (e cosh H - 1) dM - sinh H / (e cosh H - 1) de
1434         final double H      = getEccentricAnomaly();
1435         final double coshH  = FastMath.cosh(H);
1436         final double sinhH  = FastMath.sinh(H);
1437         final double absaOr = 1 / (e * coshH - 1);
1438 
1439         // update anomaly row
1440         final double[] eRow       = jacobian[1];
1441         final double[] anomalyRow = jacobian[5];
1442         for (int j = 0; j < anomalyRow.length; ++j) {
1443             anomalyRow[j] = absaOr * (anomalyRow[j] - sinhH * eRow[j]);
1444         }
1445 
1446         return jacobian;
1447 
1448     }
1449 
1450     /** {@inheritDoc} */
1451     @Override
1452     protected double[][] computeJacobianTrueWrtCartesian() {
1453         if (isElliptical()) {
1454             return computeJacobianTrueWrtCartesianElliptical();
1455         } else {
1456             return computeJacobianTrueWrtCartesianHyperbolic();
1457         }
1458     }
1459 
1460     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1461      * <p>
1462      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1463      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1464      * yDot for j=4, zDot for j=5).
1465      * </p>
1466      * @return 6x6 Jacobian matrix
1467      */
1468     private double[][] computeJacobianTrueWrtCartesianElliptical() {
1469 
1470         // start by computing the Jacobian with eccentric angle
1471         final double[][] jacobian = computeJacobianEccentricWrtCartesianElliptical();
1472 
1473         // Differentiating the eccentric anomaly equation sin E = sqrt(1-e^2) sin v / (1 + e cos v)
1474         // and using cos E = (e + cos v) / (1 + e cos v) to get rid of cos E leads to:
1475         // dE = [sqrt (1 - e^2) / (1 + e cos v)] dv - [sin E / (1 - e^2)] de
1476         // which is inverted and rewritten as:
1477         // dv = sqrt (1 - e^2) a/r dE + [sin E / sqrt (1 - e^2)] a/r de
1478         final double e2           = e * e;
1479         final double oMe2         = 1 - e2;
1480         final double epsilon      = FastMath.sqrt(oMe2);
1481         final SinCos scE          = FastMath.sinCos(getEccentricAnomaly());
1482         final double aOr          = 1 / (1 - e * scE.cos());
1483         final double aFactor      = epsilon * aOr;
1484         final double eFactor      = scE.sin() * aOr / epsilon;
1485 
1486         // update anomaly row
1487         final double[] eRow       = jacobian[1];
1488         final double[] anomalyRow = jacobian[5];
1489         for (int j = 0; j < anomalyRow.length; ++j) {
1490             anomalyRow[j] = aFactor * anomalyRow[j] + eFactor * eRow[j];
1491         }
1492 
1493         return jacobian;
1494 
1495     }
1496 
1497     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1498      * <p>
1499      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1500      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1501      * yDot for j=4, zDot for j=5).
1502      * </p>
1503      * @return 6x6 Jacobian matrix
1504      */
1505     private double[][] computeJacobianTrueWrtCartesianHyperbolic() {
1506 
1507         // start by computing the Jacobian with eccentric angle
1508         final double[][] jacobian = computeJacobianEccentricWrtCartesianHyperbolic();
1509 
1510         // Differentiating the eccentric anomaly equation sinh H = sqrt(e^2-1) sin v / (1 + e cos v)
1511         // and using cosh H = (e + cos v) / (1 + e cos v) to get rid of cosh H leads to:
1512         // dH = [sqrt (e^2 - 1) / (1 + e cos v)] dv + [sinh H / (e^2 - 1)] de
1513         // which is inverted and rewritten as:
1514         // dv = sqrt (1 - e^2) a/r dH - [sinh H / sqrt (e^2 - 1)] a/r de
1515         final double e2       = e * e;
1516         final double e2Mo     = e2 - 1;
1517         final double epsilon  = FastMath.sqrt(e2Mo);
1518         final double H        = getEccentricAnomaly();
1519         final double coshH    = FastMath.cosh(H);
1520         final double sinhH    = FastMath.sinh(H);
1521         final double aOr      = 1 / (e * coshH - 1);
1522         final double aFactor  = epsilon * aOr;
1523         final double eFactor  = sinhH * aOr / epsilon;
1524 
1525         // update anomaly row
1526         final double[] eRow           = jacobian[1];
1527         final double[] anomalyRow     = jacobian[5];
1528         for (int j = 0; j < anomalyRow.length; ++j) {
1529             anomalyRow[j] = aFactor * anomalyRow[j] - eFactor * eRow[j];
1530         }
1531 
1532         return jacobian;
1533 
1534     }
1535 
1536     /** {@inheritDoc} */
1537     @Override
1538     public void addKeplerContribution(final PositionAngleType type, final double gm,
1539                                       final double[] pDot) {
1540         pDot[5] += computeKeplerianAnomalyDot(type, a, e, gm, cachedAnomaly, cachedPositionAngleType);
1541     }
1542 
1543     /**
1544      * Compute rate of argument of latitude.
1545      * @param type position angle type of rate
1546      * @param a semi major axis
1547      * @param e eccentricity
1548      * @param mu mu
1549      * @param anomaly anomaly
1550      * @param cachedType position angle type of passed anomaly
1551      * @return first-order time derivative for anomaly
1552      * @since 12.2
1553      */
1554     private static double computeKeplerianAnomalyDot(final PositionAngleType type, final double a, final double e,
1555                                                      final double mu, final double anomaly, final PositionAngleType cachedType) {
1556         final double absA = FastMath.abs(a);
1557         final double n    = FastMath.sqrt(mu / absA) / absA;
1558         if (type == PositionAngleType.MEAN) {
1559             return n;
1560         }
1561         final double oMe2 = FastMath.abs(1 - e * e);
1562         final double ksi = 1 + e * FastMath.cos(KeplerianAnomalyUtility.convertAnomaly(cachedType, anomaly, e, PositionAngleType.TRUE));
1563         if (type == PositionAngleType.ECCENTRIC) {
1564             return n * ksi / oMe2;
1565         } else { // TRUE
1566             return n * ksi * ksi / (oMe2 * FastMath.sqrt(oMe2));
1567         }
1568     }
1569 
1570     /**  Returns a string representation of this Keplerian parameters object.
1571      * @return a string representation of this object
1572      */
1573     public String toString() {
1574         return new StringBuilder().append("Keplerian parameters: ").append('{').
1575                 append("a: ").append(a).
1576                 append("; e: ").append(e).
1577                 append("; i: ").append(FastMath.toDegrees(i)).
1578                 append("; pa: ").append(FastMath.toDegrees(pa)).
1579                 append("; raan: ").append(FastMath.toDegrees(raan)).
1580                 append("; v: ").append(FastMath.toDegrees(getTrueAnomaly())).
1581                 append(";}").toString();
1582     }
1583 
1584     /** {@inheritDoc} */
1585     @Override
1586     public PositionAngleType getCachedPositionAngleType() {
1587         return cachedPositionAngleType;
1588     }
1589 
1590     /** {@inheritDoc} */
1591     @Override
1592     public boolean hasNonKeplerianRates() {
1593         return hasNonKeplerianAcceleration();
1594     }
1595 
1596     /** {@inheritDoc} */
1597     @Override
1598     public KeplerianOrbit withKeplerianRates() {
1599         final PositionAngleType positionAngleType = getCachedPositionAngleType();
1600         return new KeplerianOrbit(a, e, i, pa, raan, cachedAnomaly, positionAngleType, positionAngleType,
1601                 getFrame(), getDate(), getMu());
1602     }
1603 
1604     /** Check if the given parameter is within an acceptable range.
1605      * The bounds are inclusive: an exception is raised when either of those conditions are met:
1606      * <ul>
1607      *     <li>The parameter is strictly greater than upperBound</li>
1608      *     <li>The parameter is strictly lower than lowerBound</li>
1609      * </ul>
1610      * <p>
1611      * In either of these cases, an OrekitException is raised.
1612      * </p>
1613      * @param parameterName name of the parameter
1614      * @param parameter value of the parameter
1615      * @param lowerBound lower bound of the acceptable range (inclusive)
1616      * @param upperBound upper bound of the acceptable range (inclusive)
1617      */
1618     private void checkParameterRangeInclusive(final String parameterName, final double parameter,
1619                                               final double lowerBound, final double upperBound) {
1620         if (parameter < lowerBound || parameter > upperBound) {
1621             throw new OrekitException(OrekitMessages.INVALID_PARAMETER_RANGE, parameterName,
1622                     parameter, lowerBound, upperBound);
1623         }
1624     }
1625 
1626 }