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 OrbitParamsType getType() {
401         return OrbitParamsType.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             // build a new orbit, taking non-Keplerian acceleration into account
926             return new KeplerianOrbit(new TimeStampedPVCoordinates(keplerianShifted.getDate(),
927                     shiftPVNonKeplerian(keplerianShifted.getPVCoordinates(), dt.toDouble())),
928                     keplerianShifted.getFrame(), keplerianShifted.getMu());
929 
930         } else {
931             // Keplerian-only motion is all we can do
932             return keplerianShifted;
933         }
934 
935     }
936 
937     /** {@inheritDoc} */
938     @Override
939     protected KeplerianOrbit keplerianShiftedBy(final double dt) {
940         return new KeplerianOrbit(a, e, i, pa, raan, getMeanAnomaly() + dt * getKeplerianMeanMotion(),
941                 PositionAngleType.MEAN, getFrame(), getDate().shiftedBy(dt), getMu());
942     }
943 
944     /** {@inheritDoc} */
945     @Override
946     protected double[][] computeJacobianMeanWrtCartesian() {
947         if (isElliptical()) {
948             return computeJacobianMeanWrtCartesianElliptical();
949         } else {
950             return computeJacobianMeanWrtCartesianHyperbolic();
951         }
952     }
953 
954     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
955      * <p>
956      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
957      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
958      * yDot for j=4, zDot for j=5).
959      * </p>
960      * @return 6x6 Jacobian matrix
961      */
962     private double[][] computeJacobianMeanWrtCartesianElliptical() {
963 
964         final double[][] jacobian = new double[6][6];
965 
966         // compute various intermediate parameters
967         computePVWithoutA();
968         final Vector3D position = partialPV.getPosition();
969         final Vector3D velocity = partialPV.getVelocity();
970         final Vector3D momentum = partialPV.getMomentum();
971         final double v2         = velocity.getNorm2Sq();
972         final double r2         = position.getNorm2Sq();
973         final double r          = FastMath.sqrt(r2);
974         final double r3         = r * r2;
975 
976         final double px         = position.getX();
977         final double py         = position.getY();
978         final double pz         = position.getZ();
979         final double vx         = velocity.getX();
980         final double vy         = velocity.getY();
981         final double vz         = velocity.getZ();
982         final double mx         = momentum.getX();
983         final double my         = momentum.getY();
984         final double mz         = momentum.getZ();
985 
986         final double mu         = getMu();
987         final double sqrtMuA    = FastMath.sqrt(a * mu);
988         final double sqrtAoMu   = FastMath.sqrt(a / mu);
989         final double a2         = a * a;
990         final double twoA       = 2 * a;
991         final double rOnA       = r / a;
992 
993         final double oMe2       = 1 - e * e;
994         final double epsilon    = FastMath.sqrt(oMe2);
995         final double sqrtRec    = 1 / epsilon;
996 
997         final SinCos scI        = FastMath.sinCos(i);
998         final SinCos scPA       = FastMath.sinCos(pa);
999         final double cosI       = scI.cos();
1000         final double sinI       = scI.sin();
1001         final double cosPA      = scPA.cos();
1002         final double sinPA      = scPA.sin();
1003 
1004         final double pv         = Vector3D.dotProduct(position, velocity);
1005         final double cosE       = (a - r) / (a * e);
1006         final double sinE       = pv / (e * sqrtMuA);
1007 
1008         // da
1009         final Vector3D vectorAR = new Vector3D(2 * a2 / r3, position);
1010         final Vector3D vectorARDot = velocity.scalarMultiply(2 * a2 / mu);
1011         fillHalfRow(1, vectorAR,    jacobian[0], 0);
1012         fillHalfRow(1, vectorARDot, jacobian[0], 3);
1013 
1014         // de
1015         final double factorER3 = pv / twoA;
1016         final Vector3D vectorER   = new Vector3D(cosE * v2 / (r * mu), position,
1017                 sinE / sqrtMuA, velocity,
1018                 -factorER3 * sinE / sqrtMuA, vectorAR);
1019         final Vector3D vectorERDot = new Vector3D(sinE / sqrtMuA, position,
1020                 cosE * 2 * r / mu, velocity,
1021                 -factorER3 * sinE / sqrtMuA, vectorARDot);
1022         fillHalfRow(1, vectorER,    jacobian[1], 0);
1023         fillHalfRow(1, vectorERDot, jacobian[1], 3);
1024 
1025         // dE / dr (Eccentric anomaly)
1026         final double coefE = cosE / (e * sqrtMuA);
1027         final Vector3D  vectorEAnR =
1028                 new Vector3D(-sinE * v2 / (e * r * mu), position, coefE, velocity,
1029                         -factorER3 * coefE, vectorAR);
1030 
1031         // dE / drDot
1032         final Vector3D  vectorEAnRDot =
1033                 new Vector3D(-sinE * 2 * r / (e * mu), velocity, coefE, position,
1034                         -factorER3 * coefE, vectorARDot);
1035 
1036         // precomputing some more factors
1037         final double s1 = -sinE * pz / r - cosE * vz * sqrtAoMu;
1038         final double s2 = -cosE * pz / r3;
1039         final double s3 = -sinE * vz / (2 * sqrtMuA);
1040         final double t1 = sqrtRec * (cosE * pz / r - sinE * vz * sqrtAoMu);
1041         final double t2 = sqrtRec * (-sinE * pz / r3);
1042         final double t3 = sqrtRec * (cosE - e) * vz / (2 * sqrtMuA);
1043         final double t4 = sqrtRec * (e * sinI * cosPA * sqrtRec - vz * sqrtAoMu);
1044         final Vector3D s = new Vector3D(cosE / r, Vector3D.PLUS_K,
1045                 s1,       vectorEAnR,
1046                 s2,       position,
1047                 s3,       vectorAR);
1048         final Vector3D sDot = new Vector3D(-sinE * sqrtAoMu, Vector3D.PLUS_K,
1049                 s1,               vectorEAnRDot,
1050                 s3,               vectorARDot);
1051         final Vector3D t =
1052                 new Vector3D(sqrtRec * sinE / r, Vector3D.PLUS_K).add(new Vector3D(t1, vectorEAnR,
1053                         t2, position,
1054                         t3, vectorAR,
1055                         t4, vectorER));
1056         final Vector3D tDot = new Vector3D(sqrtRec * (cosE - e) * sqrtAoMu, Vector3D.PLUS_K,
1057                 t1,                              vectorEAnRDot,
1058                 t3,                              vectorARDot,
1059                 t4,                              vectorERDot);
1060 
1061         // di
1062         final double factorI1 = -sinI * sqrtRec / sqrtMuA;
1063         final double i1 =  factorI1;
1064         final double i2 = -factorI1 * mz / twoA;
1065         final double i3 =  factorI1 * mz * e / oMe2;
1066         final double i4 = cosI * sinPA;
1067         final double i5 = cosI * cosPA;
1068         fillHalfRow(i1, new Vector3D(vy, -vx, 0), i2, vectorAR, i3, vectorER, i4, s, i5, t,
1069                 jacobian[2], 0);
1070         fillHalfRow(i1, new Vector3D(-py, px, 0), i2, vectorARDot, i3, vectorERDot, i4, sDot, i5, tDot,
1071                 jacobian[2], 3);
1072 
1073         // dpa
1074         fillHalfRow(cosPA / sinI, s,    -sinPA / sinI, t,    jacobian[3], 0);
1075         fillHalfRow(cosPA / sinI, sDot, -sinPA / sinI, tDot, jacobian[3], 3);
1076 
1077         // dRaan
1078         final double factorRaanR = 1 / (mu * a * oMe2 * sinI * sinI);
1079         fillHalfRow(-factorRaanR * my, new Vector3D(  0, vz, -vy),
1080                 factorRaanR * mx, new Vector3D(-vz,  0,  vx),
1081                 jacobian[4], 0);
1082         fillHalfRow(-factorRaanR * my, new Vector3D( 0, -pz,  py),
1083                 factorRaanR * mx, new Vector3D(pz,   0, -px),
1084                 jacobian[4], 3);
1085 
1086         // dM
1087         fillHalfRow(rOnA, vectorEAnR,    -sinE, vectorER,    jacobian[5], 0);
1088         fillHalfRow(rOnA, vectorEAnRDot, -sinE, vectorERDot, jacobian[5], 3);
1089 
1090         return jacobian;
1091 
1092     }
1093 
1094     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1095      * <p>
1096      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1097      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1098      * yDot for j=4, zDot for j=5).
1099      * </p>
1100      * @return 6x6 Jacobian matrix
1101      */
1102     private double[][] computeJacobianMeanWrtCartesianHyperbolic() {
1103 
1104         final double[][] jacobian = new double[6][6];
1105 
1106         // compute various intermediate parameters
1107         computePVWithoutA();
1108         final Vector3D position = partialPV.getPosition();
1109         final Vector3D velocity = partialPV.getVelocity();
1110         final Vector3D momentum = partialPV.getMomentum();
1111         final double r2         = position.getNorm2Sq();
1112         final double r          = FastMath.sqrt(r2);
1113         final double r3         = r * r2;
1114 
1115         final double x          = position.getX();
1116         final double y          = position.getY();
1117         final double z          = position.getZ();
1118         final double vx         = velocity.getX();
1119         final double vy         = velocity.getY();
1120         final double vz         = velocity.getZ();
1121         final double mx         = momentum.getX();
1122         final double my         = momentum.getY();
1123         final double mz         = momentum.getZ();
1124 
1125         final double mu         = getMu();
1126         final double absA       = -a;
1127         final double sqrtMuA    = FastMath.sqrt(absA * mu);
1128         final double a2         = a * a;
1129         final double rOa        = r / absA;
1130 
1131         final SinCos scI        = FastMath.sinCos(i);
1132         final double cosI       = scI.cos();
1133         final double sinI       = scI.sin();
1134 
1135         final double pv         = Vector3D.dotProduct(position, velocity);
1136 
1137         // da
1138         final Vector3D vectorAR = new Vector3D(-2 * a2 / r3, position);
1139         final Vector3D vectorARDot = velocity.scalarMultiply(-2 * a2 / mu);
1140         fillHalfRow(-1, vectorAR,    jacobian[0], 0);
1141         fillHalfRow(-1, vectorARDot, jacobian[0], 3);
1142 
1143         // differentials of the momentum
1144         final double m      = momentum.getNorm();
1145         final double oOm    = 1 / m;
1146         final Vector3D dcXP = new Vector3D(  0,  vz, -vy);
1147         final Vector3D dcYP = new Vector3D(-vz,   0,  vx);
1148         final Vector3D dcZP = new Vector3D( vy, -vx,   0);
1149         final Vector3D dcXV = new Vector3D(  0,  -z,   y);
1150         final Vector3D dcYV = new Vector3D(  z,   0,  -x);
1151         final Vector3D dcZV = new Vector3D( -y,   x,   0);
1152         final Vector3D dCP  = new Vector3D(mx * oOm, dcXP, my * oOm, dcYP, mz * oOm, dcZP);
1153         final Vector3D dCV  = new Vector3D(mx * oOm, dcXV, my * oOm, dcYV, mz * oOm, dcZV);
1154 
1155         // dp
1156         final double mOMu   = m / mu;
1157         final Vector3D dpP  = new Vector3D(2 * mOMu, dCP);
1158         final Vector3D dpV  = new Vector3D(2 * mOMu, dCV);
1159 
1160         // de
1161         final double p      = m * mOMu;
1162         final double moO2ae = 1 / (2 * absA * e);
1163         final double m2OaMu = -p / absA;
1164         fillHalfRow(moO2ae, dpP, m2OaMu * moO2ae, vectorAR,    jacobian[1], 0);
1165         fillHalfRow(moO2ae, dpV, m2OaMu * moO2ae, vectorARDot, jacobian[1], 3);
1166 
1167         // di
1168         final double cI1 = 1 / (m * sinI);
1169         final double cI2 = cosI * cI1;
1170         fillHalfRow(cI2, dCP, -cI1, dcZP, jacobian[2], 0);
1171         fillHalfRow(cI2, dCV, -cI1, dcZV, jacobian[2], 3);
1172 
1173         // dPA
1174         final double cP1     =  y * oOm;
1175         final double cP2     = -x * oOm;
1176         final double cP3     = -(mx * cP1 + my * cP2);
1177         final double cP4     = cP3 * oOm;
1178         final double cP5     = -1 / (r2 * sinI * sinI);
1179         final double cP6     = z  * cP5;
1180         final double cP7     = cP3 * cP5;
1181         final Vector3D dacP  = new Vector3D(cP1, dcXP, cP2, dcYP, cP4, dCP, oOm, new Vector3D(-my, mx, 0));
1182         final Vector3D dacV  = new Vector3D(cP1, dcXV, cP2, dcYV, cP4, dCV);
1183         final Vector3D dpoP  = new Vector3D(cP6, dacP, cP7, Vector3D.PLUS_K);
1184         final Vector3D dpoV  = new Vector3D(cP6, dacV);
1185 
1186         final double re2     = r2 * e * e;
1187         final double recOre2 = (p - r) / re2;
1188         final double resOre2 = (pv * mOMu) / re2;
1189         final Vector3D dreP  = new Vector3D(mOMu, velocity, pv / mu, dCP);
1190         final Vector3D dreV  = new Vector3D(mOMu, position, pv / mu, dCV);
1191         final Vector3D davP  = new Vector3D(-resOre2, dpP, recOre2, dreP, resOre2 / r, position);
1192         final Vector3D davV  = new Vector3D(-resOre2, dpV, recOre2, dreV);
1193         fillHalfRow(1, dpoP, -1, davP, jacobian[3], 0);
1194         fillHalfRow(1, dpoV, -1, davV, jacobian[3], 3);
1195 
1196         // dRAAN
1197         final double cO0 = cI1 * cI1;
1198         final double cO1 =  mx * cO0;
1199         final double cO2 = -my * cO0;
1200         fillHalfRow(cO1, dcYP, cO2, dcXP, jacobian[4], 0);
1201         fillHalfRow(cO1, dcYV, cO2, dcXV, jacobian[4], 3);
1202 
1203         // dM
1204         final double s2a    = pv / (2 * absA);
1205         final double oObux  = 1 / FastMath.sqrt(m * m + mu * absA);
1206         final double scasbu = pv * oObux;
1207         final Vector3D dauP = new Vector3D(1 / sqrtMuA, velocity, -s2a / sqrtMuA, vectorAR);
1208         final Vector3D dauV = new Vector3D(1 / sqrtMuA, position, -s2a / sqrtMuA, vectorARDot);
1209         final Vector3D dbuP = new Vector3D(oObux * mu / 2, vectorAR,    m * oObux, dCP);
1210         final Vector3D dbuV = new Vector3D(oObux * mu / 2, vectorARDot, m * oObux, dCV);
1211         final Vector3D dcuP = new Vector3D(oObux, velocity, -scasbu * oObux, dbuP);
1212         final Vector3D dcuV = new Vector3D(oObux, position, -scasbu * oObux, dbuV);
1213         fillHalfRow(1, dauP, -e / (1 + rOa), dcuP, jacobian[5], 0);
1214         fillHalfRow(1, dauV, -e / (1 + rOa), dcuV, jacobian[5], 3);
1215 
1216         return jacobian;
1217 
1218     }
1219 
1220     /** {@inheritDoc} */
1221     @Override
1222     protected double[][] computeJacobianEccentricWrtCartesian() {
1223         if (isElliptical()) {
1224             return computeJacobianEccentricWrtCartesianElliptical();
1225         } else {
1226             return computeJacobianEccentricWrtCartesianHyperbolic();
1227         }
1228     }
1229 
1230     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1231      * <p>
1232      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1233      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1234      * yDot for j=4, zDot for j=5).
1235      * </p>
1236      * @return 6x6 Jacobian matrix
1237      */
1238     private double[][] computeJacobianEccentricWrtCartesianElliptical() {
1239 
1240         // start by computing the Jacobian with mean angle
1241         final double[][] jacobian = computeJacobianMeanWrtCartesianElliptical();
1242 
1243         // Differentiating the Kepler equation M = E - e sin E leads to:
1244         // dM = (1 - e cos E) dE - sin E de
1245         // which is inverted and rewritten as:
1246         // dE = a/r dM + sin E a/r de
1247         final SinCos scE              = FastMath.sinCos(getEccentricAnomaly());
1248         final double aOr              = 1 / (1 - e * scE.cos());
1249 
1250         // update anomaly row
1251         final double[] eRow           = jacobian[1];
1252         final double[] anomalyRow     = jacobian[5];
1253         for (int j = 0; j < anomalyRow.length; ++j) {
1254             anomalyRow[j] = aOr * (anomalyRow[j] + scE.sin() * eRow[j]);
1255         }
1256 
1257         return jacobian;
1258 
1259     }
1260 
1261     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1262      * <p>
1263      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1264      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1265      * yDot for j=4, zDot for j=5).
1266      * </p>
1267      * @return 6x6 Jacobian matrix
1268      */
1269     private double[][] computeJacobianEccentricWrtCartesianHyperbolic() {
1270 
1271         // start by computing the Jacobian with mean angle
1272         final double[][] jacobian = computeJacobianMeanWrtCartesianHyperbolic();
1273 
1274         // Differentiating the Kepler equation M = e sinh H - H leads to:
1275         // dM = (e cosh H - 1) dH + sinh H de
1276         // which is inverted and rewritten as:
1277         // dH = 1 / (e cosh H - 1) dM - sinh H / (e cosh H - 1) de
1278         final double H      = getEccentricAnomaly();
1279         final double coshH  = FastMath.cosh(H);
1280         final double sinhH  = FastMath.sinh(H);
1281         final double absaOr = 1 / (e * coshH - 1);
1282 
1283         // update anomaly row
1284         final double[] eRow       = jacobian[1];
1285         final double[] anomalyRow = jacobian[5];
1286         for (int j = 0; j < anomalyRow.length; ++j) {
1287             anomalyRow[j] = absaOr * (anomalyRow[j] - sinhH * eRow[j]);
1288         }
1289 
1290         return jacobian;
1291 
1292     }
1293 
1294     /** {@inheritDoc} */
1295     @Override
1296     protected double[][] computeJacobianTrueWrtCartesian() {
1297         if (isElliptical()) {
1298             return computeJacobianTrueWrtCartesianElliptical();
1299         } else {
1300             return computeJacobianTrueWrtCartesianHyperbolic();
1301         }
1302     }
1303 
1304     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1305      * <p>
1306      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1307      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1308      * yDot for j=4, zDot for j=5).
1309      * </p>
1310      * @return 6x6 Jacobian matrix
1311      */
1312     private double[][] computeJacobianTrueWrtCartesianElliptical() {
1313 
1314         // start by computing the Jacobian with eccentric angle
1315         final double[][] jacobian = computeJacobianEccentricWrtCartesianElliptical();
1316 
1317         // Differentiating the eccentric anomaly equation sin E = sqrt(1-e^2) sin v / (1 + e cos v)
1318         // and using cos E = (e + cos v) / (1 + e cos v) to get rid of cos E leads to:
1319         // dE = [sqrt (1 - e^2) / (1 + e cos v)] dv - [sin E / (1 - e^2)] de
1320         // which is inverted and rewritten as:
1321         // dv = sqrt (1 - e^2) a/r dE + [sin E / sqrt (1 - e^2)] a/r de
1322         final double e2           = e * e;
1323         final double oMe2         = 1 - e2;
1324         final double epsilon      = FastMath.sqrt(oMe2);
1325         final SinCos scE          = FastMath.sinCos(getEccentricAnomaly());
1326         final double aOr          = 1 / (1 - e * scE.cos());
1327         final double aFactor      = epsilon * aOr;
1328         final double eFactor      = scE.sin() * aOr / epsilon;
1329 
1330         // update anomaly row
1331         final double[] eRow       = jacobian[1];
1332         final double[] anomalyRow = jacobian[5];
1333         for (int j = 0; j < anomalyRow.length; ++j) {
1334             anomalyRow[j] = aFactor * anomalyRow[j] + eFactor * eRow[j];
1335         }
1336 
1337         return jacobian;
1338 
1339     }
1340 
1341     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
1342      * <p>
1343      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1344      * respect to Cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1345      * yDot for j=4, zDot for j=5).
1346      * </p>
1347      * @return 6x6 Jacobian matrix
1348      */
1349     private double[][] computeJacobianTrueWrtCartesianHyperbolic() {
1350 
1351         // start by computing the Jacobian with eccentric angle
1352         final double[][] jacobian = computeJacobianEccentricWrtCartesianHyperbolic();
1353 
1354         // Differentiating the eccentric anomaly equation sinh H = sqrt(e^2-1) sin v / (1 + e cos v)
1355         // and using cosh H = (e + cos v) / (1 + e cos v) to get rid of cosh H leads to:
1356         // dH = [sqrt (e^2 - 1) / (1 + e cos v)] dv + [sinh H / (e^2 - 1)] de
1357         // which is inverted and rewritten as:
1358         // dv = sqrt (1 - e^2) a/r dH - [sinh H / sqrt (e^2 - 1)] a/r de
1359         final double e2       = e * e;
1360         final double e2Mo     = e2 - 1;
1361         final double epsilon  = FastMath.sqrt(e2Mo);
1362         final double H        = getEccentricAnomaly();
1363         final double coshH    = FastMath.cosh(H);
1364         final double sinhH    = FastMath.sinh(H);
1365         final double aOr      = 1 / (e * coshH - 1);
1366         final double aFactor  = epsilon * aOr;
1367         final double eFactor  = sinhH * aOr / epsilon;
1368 
1369         // update anomaly row
1370         final double[] eRow           = jacobian[1];
1371         final double[] anomalyRow     = jacobian[5];
1372         for (int j = 0; j < anomalyRow.length; ++j) {
1373             anomalyRow[j] = aFactor * anomalyRow[j] - eFactor * eRow[j];
1374         }
1375 
1376         return jacobian;
1377 
1378     }
1379 
1380     /** {@inheritDoc} */
1381     @Override
1382     public void addKeplerContribution(final PositionAngleType type, final double gm,
1383                                       final double[] pDot) {
1384         pDot[5] += computeKeplerianAnomalyDot(type, a, e, gm, cachedAnomaly, cachedPositionAngleType);
1385     }
1386 
1387     /**
1388      * Compute rate of argument of latitude.
1389      * @param type position angle type of output rate
1390      * @param a semi major axis
1391      * @param e eccentricity
1392      * @param mu mu
1393      * @param anomaly anomaly
1394      * @param cachedType position angle type of input anomaly
1395      * @return first-order time derivative for anomaly
1396      * @since 12.2
1397      */
1398     private static double computeKeplerianAnomalyDot(final PositionAngleType type, final double a, final double e,
1399                                                      final double mu, final double anomaly, final PositionAngleType cachedType) {
1400         final double absA = FastMath.abs(a);
1401         final double n    = FastMath.sqrt(mu / absA) / absA;
1402         if (type == PositionAngleType.MEAN) {
1403             return n;
1404         }
1405         final double oMe2 = FastMath.abs(1 - e * e);
1406         final double ksi = 1 + e * FastMath.cos(KeplerianAnomalyUtility.convertAnomaly(cachedType, anomaly, e, PositionAngleType.TRUE));
1407         if (type == PositionAngleType.ECCENTRIC) {
1408             return n * ksi / oMe2;
1409         } else { // TRUE
1410             return n * ksi * ksi / (oMe2 * FastMath.sqrt(oMe2));
1411         }
1412     }
1413 
1414     /**  Returns a string representation of this Keplerian parameters object.
1415      * @return a string representation of this object
1416      */
1417     public String toString() {
1418         return "Keplerian parameters: " + '{' +
1419                 "a: " + a +
1420                 "; e: " + e +
1421                 "; i: " + FastMath.toDegrees(i) +
1422                 "; pa: " + FastMath.toDegrees(pa) +
1423                 "; raan: " + FastMath.toDegrees(raan) +
1424                 "; v: " + FastMath.toDegrees(getTrueAnomaly()) +
1425                 ";}";
1426     }
1427 
1428     /** {@inheritDoc} */
1429     @Override
1430     public PositionAngleType getCachedPositionAngleType() {
1431         return cachedPositionAngleType;
1432     }
1433 
1434     /** {@inheritDoc} */
1435     @Override
1436     public boolean hasNonKeplerianRates() {
1437         return hasNonKeplerianAcceleration();
1438     }
1439 
1440     /** {@inheritDoc} */
1441     @Override
1442     public KeplerianOrbit withKeplerianRates() {
1443         return new KeplerianOrbit(getKeplerianParameters(), getFrame(), getDate(), getMu());
1444     }
1445 
1446     /** Check if the given parameter is within an acceptable range.
1447      * The bounds are inclusive: an exception is raised when either of those conditions are met:
1448      * <ul>
1449      *     <li>The parameter is strictly greater than upperBound</li>
1450      *     <li>The parameter is strictly lower than lowerBound</li>
1451      * </ul>
1452      * <p>
1453      * In either of these cases, an OrekitException is raised.
1454      * </p>
1455      * @param parameterName name of the parameter
1456      * @param parameter value of the parameter
1457      * @param lowerBound lower bound of the acceptable range (inclusive)
1458      * @param upperBound upper bound of the acceptable range (inclusive)
1459      */
1460     private void checkParameterRangeInclusive(final String parameterName, final double parameter,
1461                                               final double lowerBound, final double upperBound) {
1462         if (parameter < lowerBound || parameter > upperBound) {
1463             throw new OrekitException(OrekitMessages.INVALID_PARAMETER_RANGE, parameterName,
1464                     parameter, lowerBound, upperBound);
1465         }
1466     }
1467 
1468 }