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