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.OrekitIllegalArgumentException;
24  import org.orekit.errors.OrekitInternalError;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.frames.Frame;
27  import org.orekit.frames.KinematicTransform;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.time.TimeOffset;
30  import org.orekit.utils.PVCoordinates;
31  import org.orekit.utils.TimeStampedPVCoordinates;
32  
33  
34  /**
35   * This class handles circular orbital parameters.
36  
37   * <p>
38   * The parameters used internally are the circular elements which can be
39   * related to Keplerian elements as follows:
40   *   <ul>
41   *     <li>a</li>
42   *     <li>e<sub>x</sub> = e cos(ω)</li>
43   *     <li>e<sub>y</sub> = e sin(ω)</li>
44   *     <li>i</li>
45   *     <li>Ω</li>
46   *     <li>α<sub>v</sub> = v + ω</li>
47   *   </ul>
48   * where Ω stands for the Right Ascension of the Ascending Node and
49   * α<sub>v</sub> stands for the true latitude argument
50   *
51   * <p>
52   * The conversion equations from and to Keplerian elements given above hold only
53   * when both sides are unambiguously defined, i.e. when orbit is neither equatorial
54   * nor circular. When orbit is circular (but not equatorial), the circular
55   * parameters are still unambiguously defined whereas some Keplerian elements
56   * (more precisely ω and Ω) become ambiguous. When orbit is equatorial,
57   * neither the Keplerian nor the circular parameters can be defined unambiguously.
58   * {@link EquinoctialOrbit equinoctial orbits} is the recommended way to represent
59   * orbits.
60   * </p>
61   * <p>
62   * The instance <code>CircularOrbit</code> is guaranteed to be immutable.
63   * </p>
64   * @see    Orbit
65   * @see    KeplerianOrbit
66   * @see    CartesianOrbit
67   * @see    EquinoctialOrbit
68   * @author Luc Maisonobe
69   * @author Fabien Maussion
70   * @author V&eacute;ronique Pommier-Maurussane
71   */
72  
73  public class CircularOrbit extends Orbit implements PositionAngleBased<CircularOrbit> {
74  
75      /** Semi-major axis (m). */
76      private final double a;
77  
78      /** First component of the circular eccentricity vector. */
79      private final double ex;
80  
81      /** Second component of the circular eccentricity vector. */
82      private final double ey;
83  
84      /** Inclination (rad). */
85      private final double i;
86  
87      /** Right Ascension of Ascending Node (rad). */
88      private final double raan;
89  
90      /** Cached latitude argument (rad). */
91      private final double cachedAlpha;
92  
93      /** Type of cached position angle (latitude argument). */
94      private final PositionAngleType cachedPositionAngleType;
95  
96      /** Semi-major axis derivative (m/s). */
97      private final double aDot;
98  
99      /** First component of the circular eccentricity vector derivative. */
100     private final double exDot;
101 
102     /** Second component of the circular eccentricity vector derivative. */
103     private final double eyDot;
104 
105     /** Inclination derivative (rad/s). */
106     private final double iDot;
107 
108     /** Right Ascension of Ascending Node derivative (rad/s). */
109     private final double raanDot;
110 
111     /** True latitude argument derivative (rad/s). */
112     private final double cachedAlphaDot;
113 
114     /** Partial Cartesian coordinates (position and velocity are valid, acceleration may be missing). */
115     private PVCoordinates partialPV;
116 
117     /** Creates a new instance.
118      * @param a  semi-major axis (m)
119      * @param ex e cos(ω), first component of circular eccentricity vector
120      * @param ey e sin(ω), second component of circular eccentricity vector
121      * @param i inclination (rad)
122      * @param raan right ascension of ascending node (Ω, rad)
123      * @param alpha  an + ω, mean, eccentric or true latitude argument (rad)
124      * @param type type of latitude argument
125      * @param cachedPositionAngleType type of cached latitude argument
126      * @param frame the frame in which are defined the parameters
127      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
128      * @param date date of the orbital parameters
129      * @param mu central attraction coefficient (m³/s²)
130      * @exception IllegalArgumentException if eccentricity is equal to 1 or larger or
131      * if frame is not a {@link Frame#isPseudoInertial pseudo-inertial frame}
132      * @since 12.1
133      */
134     public CircularOrbit(final double a, final double ex, final double ey,
135                          final double i, final double raan, final double alpha,
136                          final PositionAngleType type, final PositionAngleType cachedPositionAngleType,
137                          final Frame frame, final AbsoluteDate date, final double mu)
138         throws IllegalArgumentException {
139         this(a, ex, ey, i, raan, alpha, 0., 0., 0., 0., 0.,
140             computeKeplerianAlphaDot(type, a, ex, ey, mu, alpha, type),
141             type, cachedPositionAngleType, frame, date, mu);
142     }
143 
144     /** Creates a new instance without derivatives and with cached position angle same as value inputted.
145      * @param a  semi-major axis (m)
146      * @param ex e cos(ω), first component of circular eccentricity vector
147      * @param ey e sin(ω), second component of circular eccentricity vector
148      * @param i inclination (rad)
149      * @param raan right ascension of ascending node (Ω, rad)
150      * @param alpha  an + ω, mean, eccentric or true latitude argument (rad)
151      * @param type type of latitude argument
152      * @param frame the frame in which are defined the parameters
153      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
154      * @param date date of the orbital parameters
155      * @param mu central attraction coefficient (m³/s²)
156      * @exception IllegalArgumentException if eccentricity is equal to 1 or larger or
157      * if frame is not a {@link Frame#isPseudoInertial pseudo-inertial frame}
158      */
159     public CircularOrbit(final double a, final double ex, final double ey,
160                          final double i, final double raan, final double alpha,
161                          final PositionAngleType type,
162                          final Frame frame, final AbsoluteDate date, final double mu)
163             throws IllegalArgumentException {
164         this(a, ex, ey, i, raan, alpha, type, type, frame, date, mu);
165     }
166 
167     /** Creates a new instance.
168      * @param a  semi-major axis (m)
169      * @param ex e cos(ω), first component of circular eccentricity vector
170      * @param ey e sin(ω), second component of circular eccentricity vector
171      * @param i inclination (rad)
172      * @param raan right ascension of ascending node (Ω, rad)
173      * @param alpha  an + ω, mean, eccentric or true latitude argument (rad)
174      * @param aDot  semi-major axis derivative (m/s)
175      * @param exDot d(e cos(ω))/dt, first component of circular eccentricity vector derivative
176      * @param eyDot d(e sin(ω))/dt, second component of circular eccentricity vector derivative
177      * @param iDot inclination  derivative(rad/s)
178      * @param raanDot right ascension of ascending node derivative (rad/s)
179      * @param alphaDot  d(an + ω), mean, eccentric or true latitude argument derivative (rad/s)
180      * @param type type of latitude argument
181      * @param cachedPositionAngleType type of cached latitude argument
182      * @param frame the frame in which are defined the parameters
183      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
184      * @param date date of the orbital parameters
185      * @param mu central attraction coefficient (m³/s²)
186      * @exception IllegalArgumentException if eccentricity is equal to 1 or larger or
187      * if frame is not a {@link Frame#isPseudoInertial pseudo-inertial frame}
188      * @since 12.1
189      */
190     public CircularOrbit(final double a, final double ex, final double ey,
191                          final double i, final double raan, final double alpha,
192                          final double aDot, final double exDot, final double eyDot,
193                          final double iDot, final double raanDot, final double alphaDot,
194                          final PositionAngleType type, final PositionAngleType cachedPositionAngleType,
195                          final Frame frame, final AbsoluteDate date, final double mu)
196         throws IllegalArgumentException {
197         super(frame, date, mu);
198         if (ex * ex + ey * ey >= 1.0) {
199             throw new OrekitIllegalArgumentException(OrekitMessages.HYPERBOLIC_ORBIT_NOT_HANDLED_AS,
200                                                      getClass().getName());
201         }
202         this.a       =  a;
203         this.aDot    =  aDot;
204         this.ex      = ex;
205         this.exDot   = exDot;
206         this.ey      = ey;
207         this.eyDot   = eyDot;
208         this.i       = i;
209         this.iDot    = iDot;
210         this.raan    = raan;
211         this.raanDot = raanDot;
212         this.cachedPositionAngleType = cachedPositionAngleType;
213 
214         final UnivariateDerivative1 alphaUD = initializeCachedAlpha(alpha, alphaDot, type);
215         this.cachedAlpha = alphaUD.getValue();
216         this.cachedAlphaDot = alphaUD.getFirstDerivative();
217 
218         partialPV   = null;
219 
220     }
221 
222     /** Creates a new instance with derivatives and with cached position angle same as value inputted.
223      * @param a  semi-major axis (m)
224      * @param ex e cos(ω), first component of circular eccentricity vector
225      * @param ey e sin(ω), second component of circular eccentricity vector
226      * @param i inclination (rad)
227      * @param raan right ascension of ascending node (Ω, rad)
228      * @param alpha  an + ω, mean, eccentric or true latitude argument (rad)
229      * @param aDot  semi-major axis derivative (m/s)
230      * @param exDot d(e cos(ω))/dt, first component of circular eccentricity vector derivative
231      * @param eyDot d(e sin(ω))/dt, second component of circular eccentricity vector derivative
232      * @param iDot inclination  derivative(rad/s)
233      * @param raanDot right ascension of ascending node derivative (rad/s)
234      * @param alphaDot  d(an + ω), mean, eccentric or true latitude argument derivative (rad/s)
235      * @param type type of latitude argument
236      * @param frame the frame in which are defined the parameters
237      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
238      * @param date date of the orbital parameters
239      * @param mu central attraction coefficient (m³/s²)
240      * @exception IllegalArgumentException if eccentricity is equal to 1 or larger or
241      * if frame is not a {@link Frame#isPseudoInertial pseudo-inertial frame}
242      */
243     public CircularOrbit(final double a, final double ex, final double ey,
244                          final double i, final double raan, final double alpha,
245                          final double aDot, final double exDot, final double eyDot,
246                          final double iDot, final double raanDot, final double alphaDot,
247                          final PositionAngleType type,
248                          final Frame frame, final AbsoluteDate date, final double mu)
249             throws IllegalArgumentException {
250         this(a, ex, ey, i, raan, alpha, aDot, exDot, eyDot, iDot, raanDot, alphaDot, type, type,
251                 frame, date, mu);
252     }
253 
254     /** Constructor from Cartesian parameters.
255      *
256      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
257      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
258      * use {@code mu} and the position to compute the acceleration, including
259      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
260      *
261      * @param pvCoordinates the {@link PVCoordinates} in inertial frame
262      * @param frame the frame in which are defined the {@link PVCoordinates}
263      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
264      * @param mu central attraction coefficient (m³/s²)
265      * @exception IllegalArgumentException if frame is not a {@link
266      * Frame#isPseudoInertial pseudo-inertial frame}
267      */
268     public CircularOrbit(final TimeStampedPVCoordinates pvCoordinates, final Frame frame, final double mu)
269         throws IllegalArgumentException {
270         super(pvCoordinates, frame, mu);
271         this.cachedPositionAngleType = PositionAngleType.TRUE;
272 
273         // compute semi-major axis
274         final Vector3D pvP = pvCoordinates.getPosition();
275         final Vector3D pvV = pvCoordinates.getVelocity();
276         final Vector3D pvA = pvCoordinates.getAcceleration();
277         final double r2 = pvP.getNorm2Sq();
278         final double r  = FastMath.sqrt(r2);
279         final double V2 = pvV.getNorm2Sq();
280         final double rV2OnMu = r * V2 / mu;
281         a = r / (2 - rV2OnMu);
282 
283         if (!isElliptical()) {
284             throw new OrekitIllegalArgumentException(OrekitMessages.HYPERBOLIC_ORBIT_NOT_HANDLED_AS,
285                                                      getClass().getName());
286         }
287 
288         // compute inclination
289         final Vector3D momentum = pvCoordinates.getMomentum();
290         i = Vector3D.angle(momentum, Vector3D.PLUS_K);
291 
292         // compute right ascension of ascending node
293         final Vector3D node  = Vector3D.crossProduct(Vector3D.PLUS_K, momentum);
294         raan = FastMath.atan2(node.getY(), node.getX());
295 
296         // 2D-coordinates in the canonical frame
297         final SinCos scRaan = FastMath.sinCos(raan);
298         final SinCos scI    = FastMath.sinCos(i);
299         final double xP     = pvP.getX();
300         final double yP     = pvP.getY();
301         final double zP     = pvP.getZ();
302         final double x2     = (xP * scRaan.cos() + yP * scRaan.sin()) / a;
303         final double y2     = ((yP * scRaan.cos() - xP * scRaan.sin()) * scI.cos() + zP * scI.sin()) / a;
304 
305         // compute eccentricity vector
306         final double eSE    = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(mu * a);
307         final double eCE    = rV2OnMu - 1;
308         final double e2     = eCE * eCE + eSE * eSE;
309         final double f      = eCE - e2;
310         final double g      = FastMath.sqrt(1 - e2) * eSE;
311         final double aOnR   = a / r;
312         final double a2OnR2 = aOnR * aOnR;
313         ex = a2OnR2 * (f * x2 + g * y2);
314         ey = a2OnR2 * (f * y2 - g * x2);
315 
316         // compute latitude argument
317         final double beta = 1 / (1 + FastMath.sqrt(1 - ex * ex - ey * ey));
318         cachedAlpha = CircularLatitudeArgumentUtility.eccentricToTrue(ex, ey, FastMath.atan2(y2 + ey + eSE * beta * ex, x2 + ex - eSE * beta * ey));
319 
320         partialPV   = pvCoordinates;
321 
322         if (hasNonKeplerianAcceleration(pvCoordinates, mu)) {
323             // we have a relevant acceleration, we can compute derivatives
324 
325             final double[][] jacobian = new double[6][6];
326             getJacobianWrtCartesian(PositionAngleType.MEAN, jacobian);
327 
328             final Vector3D keplerianAcceleration    = new Vector3D(-mu / (r * r2), pvP);
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             exDot   = jacobian[1][3] * aX + jacobian[1][4] * aY + jacobian[1][5] * aZ;
335             eyDot   = jacobian[2][3] * aX + jacobian[2][4] * aY + jacobian[2][5] * aZ;
336             iDot    = 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 latitude argument derivative, we must compute
340             // mean latitude argument derivative including Keplerian motion and convert to true latitude argument
341             final double alphaMDot = getKeplerianMeanMotion() +
342                                      jacobian[5][3] * aX + jacobian[5][4] * aY + jacobian[5][5] * aZ;
343             final UnivariateDerivative1 exUD     = new UnivariateDerivative1(ex, exDot);
344             final UnivariateDerivative1 eyUD     = new UnivariateDerivative1(ey, eyDot);
345             final UnivariateDerivative1 alphaMUD = new UnivariateDerivative1(getAlphaM(), alphaMDot);
346             final UnivariateDerivative1 alphavUD = FieldCircularLatitudeArgumentUtility.meanToTrue(exUD, eyUD, alphaMUD);
347             cachedAlphaDot = alphavUD.getFirstDerivative();
348 
349         } else {
350             // acceleration is either almost zero or NaN,
351             // we assume acceleration was not known
352             // we don't set up derivatives
353             aDot      = 0.;
354             exDot     = 0.;
355             eyDot     = 0.;
356             iDot      = 0.;
357             raanDot   = 0.;
358             cachedAlphaDot = computeKeplerianAlphaDot(cachedPositionAngleType, a, ex, ey, mu, cachedAlpha, 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 {@link PVCoordinates} in inertial frame
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 CircularOrbit(final PVCoordinates pvCoordinates, final Frame frame,
379                          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 CircularOrbit(final Orbit op) {
388 
389         super(op.getFrame(), op.getDate(), op.getMu());
390 
391         a    = op.getA();
392         i    = op.getI();
393         final double hx = op.getHx();
394         final double hy = op.getHy();
395         final double h2 = hx * hx + hy * hy;
396         final double h  = FastMath.sqrt(h2);
397         raan = FastMath.atan2(hy, hx);
398         final SinCos scRaan  = FastMath.sinCos(raan);
399         final double cosRaan = h == 0 ? scRaan.cos() : hx / h;
400         final double sinRaan = h == 0 ? scRaan.sin() : hy / h;
401         final double equiEx = op.getEquinoctialEx();
402         final double equiEy = op.getEquinoctialEy();
403         ex     = equiEx * cosRaan + equiEy * sinRaan;
404         ey     = equiEy * cosRaan - equiEx * sinRaan;
405         cachedPositionAngleType = PositionAngleType.TRUE;
406         cachedAlpha = op.getLv() - raan;
407 
408         if (op.hasNonKeplerianAcceleration()) {
409             aDot    = op.getADot();
410             final double hxDot = op.getHxDot();
411             final double hyDot = op.getHyDot();
412             iDot    = 2 * (cosRaan * hxDot + sinRaan * hyDot) / (1 + h2);
413             raanDot = (hx * hyDot - hy * hxDot) / h2;
414             final double equiExDot = op.getEquinoctialExDot();
415             final double equiEyDot = op.getEquinoctialEyDot();
416             exDot   = (equiExDot + equiEy * raanDot) * cosRaan +
417                       (equiEyDot - equiEx * raanDot) * sinRaan;
418             eyDot   = (equiEyDot - equiEx * raanDot) * cosRaan -
419                       (equiExDot + equiEy * raanDot) * sinRaan;
420             cachedAlphaDot = op.getLvDot() - raanDot;
421         } else {
422             aDot      = 0.;
423             exDot     = 0.;
424             eyDot     = 0.;
425             iDot      = 0.;
426             raanDot   = 0.;
427             cachedAlphaDot = computeKeplerianAlphaDot(cachedPositionAngleType, a, ex, ey, getMu(), cachedAlpha, cachedPositionAngleType);
428         }
429 
430         partialPV   = null;
431 
432     }
433 
434     /** {@inheritDoc} */
435     @Override
436     public boolean hasNonKeplerianAcceleration() {
437         return aDot != 0. || exDot != 0. || eyDot != 0. || iDot != 0. || raanDot != 0. ||
438                 FastMath.abs(cachedAlphaDot - computeKeplerianAlphaDot(cachedPositionAngleType, a, ex, ey, getMu(), cachedAlpha, cachedPositionAngleType)) > TOLERANCE_POSITION_ANGLE_RATE;
439     }
440 
441     /** {@inheritDoc} */
442     @Override
443     public OrbitType getType() {
444         return OrbitType.CIRCULAR;
445     }
446 
447     /** {@inheritDoc} */
448     @Override
449     public AbstractOrbitFactory<CircularOrbit> factory(final PositionAngleType positionAngleType,
450                                                        final double positionScale) {
451         return new CircularOrbitFactory(this, positionScale, positionAngleType);
452     }
453 
454     /** {@inheritDoc} */
455     @Override
456     public double getA() {
457         return a;
458     }
459 
460     /** {@inheritDoc} */
461     @Override
462     public double getADot() {
463         return aDot;
464     }
465 
466     /** {@inheritDoc} */
467     @Override
468     public double getEquinoctialEx() {
469         final SinCos sc = FastMath.sinCos(raan);
470         return ex * sc.cos() - ey * sc.sin();
471     }
472 
473     /** {@inheritDoc} */
474     @Override
475     public double getEquinoctialExDot() {
476         if (!hasNonKeplerianAcceleration()) {
477             return 0.;
478         }
479         final SinCos sc = FastMath.sinCos(raan);
480         return (exDot - ey * raanDot) * sc.cos() - (eyDot + ex * raanDot) * sc.sin();
481     }
482 
483     /** {@inheritDoc} */
484     @Override
485     public double getEquinoctialEy() {
486         final SinCos sc = FastMath.sinCos(raan);
487         return ey * sc.cos() + ex * sc.sin();
488     }
489 
490     /** {@inheritDoc} */
491     @Override
492     public double getEquinoctialEyDot() {
493         if (!hasNonKeplerianAcceleration()) {
494             return 0.;
495         }
496         final SinCos sc = FastMath.sinCos(raan);
497         return (eyDot + ex * raanDot) * sc.cos() + (exDot - ey * raanDot) * sc.sin();
498     }
499 
500     /** Get the first component of the circular eccentricity vector.
501      * @return ex = e cos(ω), first component of the circular eccentricity vector
502      */
503     public double getCircularEx() {
504         return ex;
505     }
506 
507     /** Get the first component of the circular eccentricity vector derivative.
508      * @return ex = e cos(ω), first component of the circular eccentricity vector derivative
509      * @since 9.0
510      */
511     public double getCircularExDot() {
512         return exDot;
513     }
514 
515     /** Get the second component of the circular eccentricity vector.
516      * @return ey = e sin(ω), second component of the circular eccentricity vector
517      */
518     public double getCircularEy() {
519         return ey;
520     }
521 
522     /** Get the second component of the circular eccentricity vector derivative.
523      * @return ey = e sin(ω), second component of the circular eccentricity vector derivative
524      */
525     public double getCircularEyDot() {
526         return eyDot;
527     }
528 
529     /** {@inheritDoc} */
530     @Override
531     public double getHx() {
532         // Check for equatorial retrograde orbit
533         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
534             return Double.NaN;
535         }
536         return FastMath.cos(raan) * FastMath.tan(i / 2);
537     }
538 
539     /** {@inheritDoc} */
540     @Override
541     public double getHxDot() {
542         // Check for equatorial retrograde orbit
543         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
544             return Double.NaN;
545         }
546         if (!hasNonKeplerianAcceleration()) {
547             return 0.;
548         }
549         final SinCos sc  = FastMath.sinCos(raan);
550         final double tan = FastMath.tan(0.5 * i);
551         return 0.5 * sc.cos() * (1 + tan * tan) * iDot - sc.sin() * tan * raanDot;
552     }
553 
554     /** {@inheritDoc} */
555     @Override
556     public double getHy() {
557         // Check for equatorial retrograde orbit
558         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
559             return Double.NaN;
560         }
561         return FastMath.sin(raan) * FastMath.tan(i / 2);
562     }
563 
564     /** {@inheritDoc} */
565     @Override
566     public double getHyDot() {
567         // Check for equatorial retrograde orbit
568         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
569             return Double.NaN;
570         }
571         if (!hasNonKeplerianAcceleration()) {
572             return 0.;
573         }
574         final SinCos sc  = FastMath.sinCos(raan);
575         final double tan = FastMath.tan(0.5 * i);
576         return 0.5 * sc.sin() * (1 + tan * tan) * iDot + sc.cos() * tan * raanDot;
577     }
578 
579     /** Get the true latitude argument.
580      * @return v + ω true latitude argument (rad)
581      */
582     public double getAlphaV() {
583         return switch (cachedPositionAngleType) {
584             case TRUE -> cachedAlpha;
585             case ECCENTRIC -> CircularLatitudeArgumentUtility.eccentricToTrue(ex, ey, cachedAlpha);
586             case MEAN -> CircularLatitudeArgumentUtility.meanToTrue(ex, ey, cachedAlpha);
587         };
588     }
589 
590     /** Get the true latitude argument derivative.
591      * <p>
592      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
593      * </p>
594      * @return v + ω true latitude argument derivative (rad/s)
595      * @since 9.0
596      */
597     public double getAlphaVDot() {
598         switch (cachedPositionAngleType) {
599             case ECCENTRIC:
600                 final UnivariateDerivative1 alphaEUD = new UnivariateDerivative1(cachedAlpha, cachedAlphaDot);
601                 final UnivariateDerivative1 exUD     = new UnivariateDerivative1(ex,     exDot);
602                 final UnivariateDerivative1 eyUD     = new UnivariateDerivative1(ey,     eyDot);
603                 final UnivariateDerivative1 alphaVUD = FieldCircularLatitudeArgumentUtility.eccentricToTrue(exUD, eyUD,
604                         alphaEUD);
605                 return alphaVUD.getFirstDerivative();
606 
607             case TRUE:
608                 return cachedAlphaDot;
609 
610             case MEAN:
611                 final UnivariateDerivative1 alphaMUD = new UnivariateDerivative1(cachedAlpha, cachedAlphaDot);
612                 final UnivariateDerivative1 exUD2    = new UnivariateDerivative1(ex,     exDot);
613                 final UnivariateDerivative1 eyUD2    = new UnivariateDerivative1(ey,     eyDot);
614                 final UnivariateDerivative1 alphaVUD2 = FieldCircularLatitudeArgumentUtility.meanToTrue(exUD2,
615                         eyUD2, alphaMUD);
616                 return alphaVUD2.getFirstDerivative();
617 
618             default:
619                 throw new OrekitInternalError(null);
620         }
621     }
622 
623     /** Get the eccentric latitude argument.
624      * @return E + ω eccentric latitude argument (rad)
625      */
626     public double getAlphaE() {
627         return switch (cachedPositionAngleType) {
628             case TRUE -> CircularLatitudeArgumentUtility.trueToEccentric(ex, ey, cachedAlpha);
629             case ECCENTRIC -> cachedAlpha;
630             case MEAN -> CircularLatitudeArgumentUtility.meanToEccentric(ex, ey, cachedAlpha);
631         };
632     }
633 
634     /** Get the eccentric latitude argument derivative.
635      * <p>
636      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
637      * </p>
638      * @return d(E + ω)/dt eccentric latitude argument derivative (rad/s)
639      * @since 9.0
640      */
641     public double getAlphaEDot() {
642         switch (cachedPositionAngleType) {
643             case TRUE:
644                 final UnivariateDerivative1 alphaVUD = new UnivariateDerivative1(cachedAlpha, cachedAlphaDot);
645                 final UnivariateDerivative1 exUD     = new UnivariateDerivative1(ex,     exDot);
646                 final UnivariateDerivative1 eyUD     = new UnivariateDerivative1(ey,     eyDot);
647                 final UnivariateDerivative1 alphaEUD = FieldCircularLatitudeArgumentUtility.trueToEccentric(exUD, eyUD,
648                         alphaVUD);
649                 return alphaEUD.getFirstDerivative();
650 
651             case ECCENTRIC:
652                 return cachedAlphaDot;
653 
654             case MEAN:
655                 final UnivariateDerivative1 alphaMUD = new UnivariateDerivative1(cachedAlpha, cachedAlphaDot);
656                 final UnivariateDerivative1 exUD2    = new UnivariateDerivative1(ex,     exDot);
657                 final UnivariateDerivative1 eyUD2    = new UnivariateDerivative1(ey,     eyDot);
658                 final UnivariateDerivative1 alphaVUD2 = FieldCircularLatitudeArgumentUtility.meanToEccentric(exUD2,
659                         eyUD2, alphaMUD);
660                 return alphaVUD2.getFirstDerivative();
661 
662             default:
663                 throw new OrekitInternalError(null);
664         }
665     }
666 
667     /** Get the mean latitude argument.
668      * @return M + ω mean latitude argument (rad)
669      */
670     public double getAlphaM() {
671         return switch (cachedPositionAngleType) {
672             case TRUE -> CircularLatitudeArgumentUtility.trueToMean(ex, ey, cachedAlpha);
673             case MEAN -> cachedAlpha;
674             case ECCENTRIC -> CircularLatitudeArgumentUtility.eccentricToMean(ex, ey, cachedAlpha);
675         };
676     }
677 
678     /** Get the mean latitude argument derivative.
679      * <p>
680      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
681      * </p>
682      * @return d(M + ω)/dt mean latitude argument derivative (rad/s)
683      * @since 9.0
684      */
685     public double getAlphaMDot() {
686         switch (cachedPositionAngleType) {
687             case TRUE:
688                 final UnivariateDerivative1 alphaVUD = new UnivariateDerivative1(cachedAlpha, cachedAlphaDot);
689                 final UnivariateDerivative1 exUD     = new UnivariateDerivative1(ex,     exDot);
690                 final UnivariateDerivative1 eyUD     = new UnivariateDerivative1(ey,     eyDot);
691                 final UnivariateDerivative1 alphaMUD = FieldCircularLatitudeArgumentUtility.trueToMean(exUD, eyUD,
692                         alphaVUD);
693                 return alphaMUD.getFirstDerivative();
694 
695             case MEAN:
696                 return cachedAlphaDot;
697 
698             case ECCENTRIC:
699                 final UnivariateDerivative1 alphaEUD = new UnivariateDerivative1(cachedAlpha, cachedAlphaDot);
700                 final UnivariateDerivative1 exUD2    = new UnivariateDerivative1(ex,     exDot);
701                 final UnivariateDerivative1 eyUD2    = new UnivariateDerivative1(ey,     eyDot);
702                 final UnivariateDerivative1 alphaMUD2 = FieldCircularLatitudeArgumentUtility.eccentricToMean(exUD2,
703                         eyUD2, alphaEUD);
704                 return alphaMUD2.getFirstDerivative();
705 
706             default:
707                 throw new OrekitInternalError(null);
708         }
709     }
710 
711     /** Get the latitude argument.
712      * @param type type of the angle
713      * @return latitude argument (rad)
714      */
715     public double getAlpha(final PositionAngleType type) {
716         return (type == PositionAngleType.MEAN) ? getAlphaM() :
717                                               ((type == PositionAngleType.ECCENTRIC) ? getAlphaE() :
718                                                                                    getAlphaV());
719     }
720 
721     /** Get the latitude argument derivative.
722      * <p>
723      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
724      * </p>
725      * @param type type of the angle
726      * @return latitude argument derivative (rad/s)
727      * @since 9.0
728      */
729     public double getAlphaDot(final PositionAngleType type) {
730         return (type == PositionAngleType.MEAN) ? getAlphaMDot() :
731                                               ((type == PositionAngleType.ECCENTRIC) ? getAlphaEDot() :
732                                                                                    getAlphaVDot());
733     }
734 
735     /** {@inheritDoc} */
736     @Override
737     public double getE() {
738         return FastMath.sqrt(ex * ex + ey * ey);
739     }
740 
741     /** {@inheritDoc} */
742     @Override
743     public double getEDot() {
744         if (!hasNonKeplerianAcceleration()) {
745             return 0.;
746         }
747         return (ex * exDot + ey * eyDot) / getE();
748     }
749 
750     /** {@inheritDoc} */
751     @Override
752     public double getI() {
753         return i;
754     }
755 
756     /** {@inheritDoc} */
757     @Override
758     public double getIDot() {
759         return iDot;
760     }
761 
762     /** Get the right ascension of the ascending node.
763      * @return right ascension of the ascending node (rad)
764      */
765     public double getRightAscensionOfAscendingNode() {
766         return raan;
767     }
768 
769     /** Get the right ascension of the ascending node derivative.
770      * <p>
771      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
772      * </p>
773      * @return right ascension of the ascending node derivative (rad/s)
774      * @since 9.0
775      */
776     public double getRightAscensionOfAscendingNodeDot() {
777         return raanDot;
778     }
779 
780     /** {@inheritDoc} */
781     @Override
782     public double getLv() {
783         return getAlphaV() + raan;
784     }
785 
786     /** {@inheritDoc} */
787     @Override
788     public double getLvDot() {
789         return getAlphaVDot() + raanDot;
790     }
791 
792     /** {@inheritDoc} */
793     @Override
794     public double getLE() {
795         return getAlphaE() + raan;
796     }
797 
798     /** {@inheritDoc} */
799     @Override
800     public double getLEDot() {
801         return getAlphaEDot() + raanDot;
802     }
803 
804     /** {@inheritDoc} */
805     @Override
806     public double getLM() {
807         return getAlphaM() + raan;
808     }
809 
810     /** {@inheritDoc} */
811     @Override
812     public double getLMDot() {
813         return getAlphaMDot() + raanDot;
814     }
815 
816     /** Compute position and velocity but not acceleration.
817      */
818     private void computePVWithoutA() {
819 
820         if (partialPV != null) {
821             // already computed
822             return;
823         }
824 
825         // get equinoctial parameters
826         final double equEx = getEquinoctialEx();
827         final double equEy = getEquinoctialEy();
828         final double hx = getHx();
829         final double hy = getHy();
830         final double lE = getLE();
831 
832         // inclination-related intermediate parameters
833         final double hx2   = hx * hx;
834         final double hy2   = hy * hy;
835         final double factH = 1. / (1 + hx2 + hy2);
836 
837         // reference axes defining the orbital plane
838         final double ux = (1 + hx2 - hy2) * factH;
839         final double uy =  2 * hx * hy * factH;
840         final double uz = -2 * hy * factH;
841 
842         final double vx = uy;
843         final double vy = (1 - hx2 + hy2) * factH;
844         final double vz =  2 * hx * factH;
845 
846         // eccentricity-related intermediate parameters
847         final double exey = equEx * equEy;
848         final double ex2  = equEx * equEx;
849         final double ey2  = equEy * equEy;
850         final double e2   = ex2 + ey2;
851         final double eta  = 1 + FastMath.sqrt(1 - e2);
852         final double beta = 1. / eta;
853 
854         // eccentric latitude argument
855         final SinCos scLe   = FastMath.sinCos(lE);
856         final double cLe    = scLe.cos();
857         final double sLe    = scLe.sin();
858         final double exCeyS = equEx * cLe + equEy * sLe;
859 
860         // coordinates of position and velocity in the orbital plane
861         final double x      = a * ((1 - beta * ey2) * cLe + beta * exey * sLe - equEx);
862         final double y      = a * ((1 - beta * ex2) * sLe + beta * exey * cLe - equEy);
863 
864         final double factor = FastMath.sqrt(getMu() / a) / (1 - exCeyS);
865         final double xdot   = factor * (-sLe + beta * equEy * exCeyS);
866         final double ydot   = factor * ( cLe - beta * equEx * exCeyS);
867 
868         final Vector3D position =
869                         new Vector3D(x * ux + y * vx, x * uy + y * vy, x * uz + y * vz);
870         final Vector3D velocity =
871                         new Vector3D(xdot * ux + ydot * vx, xdot * uy + ydot * vy, xdot * uz + ydot * vz);
872 
873         partialPV = new PVCoordinates(position, velocity);
874 
875     }
876 
877     /** Initialize cached alpha with rate.
878      * @param alpha input alpha
879      * @param alphaDot rate of input alpha
880      * @param inputType position angle type passed as input
881      * @return alpha to cache with rate
882      * @since 12.1
883      */
884     private UnivariateDerivative1 initializeCachedAlpha(final double alpha, final double alphaDot,
885                                                         final PositionAngleType inputType) {
886         if (cachedPositionAngleType == inputType) {
887             return new UnivariateDerivative1(alpha, alphaDot);
888 
889         } else {
890             final UnivariateDerivative1 exUD = new UnivariateDerivative1(ex, exDot);
891             final UnivariateDerivative1 eyUD = new UnivariateDerivative1(ey, eyDot);
892             final UnivariateDerivative1 alphaUD = new UnivariateDerivative1(alpha, alphaDot);
893 
894             switch (cachedPositionAngleType) {
895 
896                 case ECCENTRIC:
897                     if (inputType == PositionAngleType.MEAN) {
898                         return FieldCircularLatitudeArgumentUtility.meanToEccentric(exUD, eyUD, alphaUD);
899                     } else {
900                         return FieldCircularLatitudeArgumentUtility.trueToEccentric(exUD, eyUD, alphaUD);
901                     }
902 
903                 case TRUE:
904                     if (inputType == PositionAngleType.MEAN) {
905                         return FieldCircularLatitudeArgumentUtility.meanToTrue(exUD, eyUD, alphaUD);
906                     } else {
907                         return FieldCircularLatitudeArgumentUtility.eccentricToTrue(exUD, eyUD, alphaUD);
908                     }
909 
910                 case MEAN:
911                     if (inputType == PositionAngleType.TRUE) {
912                         return FieldCircularLatitudeArgumentUtility.trueToMean(exUD, eyUD, alphaUD);
913                     } else {
914                         return FieldCircularLatitudeArgumentUtility.eccentricToMean(exUD, eyUD, alphaUD);
915                     }
916 
917                 default:
918                     throw new OrekitInternalError(null);
919 
920             }
921 
922         }
923 
924     }
925 
926     /** {@inheritDoc} */
927     @Override
928     protected Vector3D initPosition() {
929 
930         // get equinoctial parameters
931         final double equEx = getEquinoctialEx();
932         final double equEy = getEquinoctialEy();
933         final double hx = getHx();
934         final double hy = getHy();
935         final double lE = getLE();
936 
937         // inclination-related intermediate parameters
938         final double hx2   = hx * hx;
939         final double hy2   = hy * hy;
940         final double factH = 1. / (1 + hx2 + hy2);
941 
942         // reference axes defining the orbital plane
943         final double ux = (1 + hx2 - hy2) * factH;
944         final double uy =  2 * hx * hy * factH;
945         final double uz = -2 * hy * factH;
946 
947         final double vx = uy;
948         final double vy = (1 - hx2 + hy2) * factH;
949         final double vz =  2 * hx * factH;
950 
951         // eccentricity-related intermediate parameters
952         final double exey = equEx * equEy;
953         final double ex2  = equEx * equEx;
954         final double ey2  = equEy * equEy;
955         final double e2   = ex2 + ey2;
956         final double eta  = 1 + FastMath.sqrt(1 - e2);
957         final double beta = 1. / eta;
958 
959         // eccentric latitude argument
960         final SinCos scLe   = FastMath.sinCos(lE);
961         final double cLe    = scLe.cos();
962         final double sLe    = scLe.sin();
963 
964         // coordinates of position and velocity in the orbital plane
965         final double x      = a * ((1 - beta * ey2) * cLe + beta * exey * sLe - equEx);
966         final double y      = a * ((1 - beta * ex2) * sLe + beta * exey * cLe - equEy);
967 
968         return new Vector3D(x * ux + y * vx, x * uy + y * vy, x * uz + y * vz);
969 
970     }
971 
972     /** {@inheritDoc} */
973     @Override
974     protected TimeStampedPVCoordinates initPVCoordinates() {
975 
976         // position and velocity
977         computePVWithoutA();
978 
979         // acceleration
980         final double r2 = partialPV.getPosition().getNorm2Sq();
981         final Vector3D keplerianAcceleration = new Vector3D(-getMu() / (r2 * FastMath.sqrt(r2)), partialPV.getPosition());
982         final Vector3D acceleration = hasNonKeplerianRates() ?
983                                       keplerianAcceleration.add(nonKeplerianAcceleration()) :
984                                       keplerianAcceleration;
985 
986         return new TimeStampedPVCoordinates(getDate(), partialPV.getPosition(), partialPV.getVelocity(), acceleration);
987 
988     }
989 
990     /** {@inheritDoc} */
991     @Override
992     public CircularOrbit inFrame(final Frame inertialFrame) {
993         final PVCoordinates pvCoordinates;
994         if (hasNonKeplerianAcceleration()) {
995             pvCoordinates = getPVCoordinates(inertialFrame);
996         } else {
997             final KinematicTransform transform = getFrame().getKinematicTransformTo(inertialFrame, getDate());
998             pvCoordinates = transform.transformOnlyPV(getPVCoordinates());
999         }
1000         final CircularOrbit circularOrbit = new CircularOrbit(pvCoordinates, inertialFrame, getDate(), getMu());
1001         if (circularOrbit.getCachedPositionAngleType() == getCachedPositionAngleType()) {
1002             return circularOrbit;
1003         } else {
1004             return circularOrbit.withCachedPositionAngleType(getCachedPositionAngleType());
1005         }
1006     }
1007 
1008     /** {@inheritDoc} */
1009     @Override
1010     public CircularOrbit withCachedPositionAngleType(final PositionAngleType positionAngleType) {
1011         return new CircularOrbit(a, ex, ey, i, raan, getAlpha(positionAngleType), aDot, exDot, eyDot, iDot, raanDot,
1012                 getAlphaDot(positionAngleType), positionAngleType, getFrame(), getDate(), getMu());
1013     }
1014 
1015     /** {@inheritDoc} */
1016     @Override
1017     public CircularOrbit shiftedBy(final double dt) {
1018         return shiftedBy(new TimeOffset(dt));
1019     }
1020 
1021     /** {@inheritDoc} */
1022     @Override
1023     public CircularOrbit shiftedBy(final TimeOffset dt) {
1024 
1025         final double dtS = dt.toDouble();
1026 
1027         // use Keplerian-only motion
1028         final CircularOrbit keplerianShifted = new CircularOrbit(a, ex, ey, i, raan,
1029                                                                  getAlphaM() + getKeplerianMeanMotion() * dtS,
1030                                                                  PositionAngleType.MEAN, cachedPositionAngleType,
1031                                                                  getFrame(), getDate().shiftedBy(dt), getMu());
1032 
1033         if (dtS != 0. && hasNonKeplerianRates()) {
1034             final PVCoordinates pvCoordinates = shiftNonKeplerian(keplerianShifted.getPVCoordinates(), dtS);
1035 
1036             // build a new orbit, taking non-Keplerian acceleration into account
1037             return new CircularOrbit(new TimeStampedPVCoordinates(keplerianShifted.getDate(), pvCoordinates),
1038                                      keplerianShifted.getFrame(), keplerianShifted.getMu());
1039 
1040         } else {
1041             // Keplerian-only motion is all we can do
1042             return keplerianShifted;
1043         }
1044 
1045     }
1046 
1047     /** {@inheritDoc} */
1048     @Override
1049     protected double[][] computeJacobianMeanWrtCartesian() {
1050 
1051 
1052         final double[][] jacobian = new double[6][6];
1053 
1054         computePVWithoutA();
1055         final Vector3D position = partialPV.getPosition();
1056         final Vector3D velocity = partialPV.getVelocity();
1057         final double x          = position.getX();
1058         final double y          = position.getY();
1059         final double z          = position.getZ();
1060         final double vx         = velocity.getX();
1061         final double vy         = velocity.getY();
1062         final double vz         = velocity.getZ();
1063         final double pv         = Vector3D.dotProduct(position, velocity);
1064         final double r2         = position.getNorm2Sq();
1065         final double r          = FastMath.sqrt(r2);
1066         final double v2         = velocity.getNorm2Sq();
1067 
1068         final double mu         = getMu();
1069         final double oOsqrtMuA  = 1 / FastMath.sqrt(mu * a);
1070         final double rOa        = r / a;
1071         final double aOr        = a / r;
1072         final double aOr2       = a / r2;
1073         final double a2         = a * a;
1074 
1075         final double ex2        = ex * ex;
1076         final double ey2        = ey * ey;
1077         final double e2         = ex2 + ey2;
1078         final double epsilon    = FastMath.sqrt(1 - e2);
1079         final double beta       = 1 / (1 + epsilon);
1080 
1081         final double eCosE      = 1 - rOa;
1082         final double eSinE      = pv * oOsqrtMuA;
1083 
1084         final SinCos scI    = FastMath.sinCos(i);
1085         final SinCos scRaan = FastMath.sinCos(raan);
1086         final double cosI       = scI.cos();
1087         final double sinI       = scI.sin();
1088         final double cosRaan    = scRaan.cos();
1089         final double sinRaan    = scRaan.sin();
1090 
1091         // da
1092         fillHalfRow(2 * aOr * aOr2, position, jacobian[0], 0);
1093         fillHalfRow(2 * a2 / mu, velocity, jacobian[0], 3);
1094 
1095         // differentials of the normalized momentum
1096         final Vector3D danP = new Vector3D(v2, position, -pv, velocity);
1097         final Vector3D danV = new Vector3D(r2, velocity, -pv, position);
1098         final double recip  = 1 / partialPV.getMomentum().getNorm();
1099         final double recip2 = recip * recip;
1100         final Vector3D dwXP = new Vector3D(recip, new Vector3D(  0,  vz, -vy), -recip2 * sinRaan * sinI, danP);
1101         final Vector3D dwYP = new Vector3D(recip, new Vector3D(-vz,   0,  vx),  recip2 * cosRaan * sinI, danP);
1102         final Vector3D dwZP = new Vector3D(recip, new Vector3D( vy, -vx,   0), -recip2 * cosI,           danP);
1103         final Vector3D dwXV = new Vector3D(recip, new Vector3D(  0,  -z,   y), -recip2 * sinRaan * sinI, danV);
1104         final Vector3D dwYV = new Vector3D(recip, new Vector3D(  z,   0,  -x),  recip2 * cosRaan * sinI, danV);
1105         final Vector3D dwZV = new Vector3D(recip, new Vector3D( -y,   x,   0), -recip2 * cosI,           danV);
1106 
1107         // di
1108         fillHalfRow(sinRaan * cosI, dwXP, -cosRaan * cosI, dwYP, -sinI, dwZP, jacobian[3], 0);
1109         fillHalfRow(sinRaan * cosI, dwXV, -cosRaan * cosI, dwYV, -sinI, dwZV, jacobian[3], 3);
1110 
1111         // dRaan
1112         fillHalfRow(sinRaan / sinI, dwYP, cosRaan / sinI, dwXP, jacobian[4], 0);
1113         fillHalfRow(sinRaan / sinI, dwYV, cosRaan / sinI, dwXV, jacobian[4], 3);
1114 
1115         // orbital frame: (p, q, w) p along ascending node, w along momentum
1116         // the coordinates of the spacecraft in this frame are: (u, v, 0)
1117         final double u     =  x * cosRaan + y * sinRaan;
1118         final double cv    = -x * sinRaan + y * cosRaan;
1119         final double v     = cv * cosI + z * sinI;
1120 
1121         // du
1122         final Vector3D duP = new Vector3D(cv * cosRaan / sinI, dwXP,
1123                                           cv * sinRaan / sinI, dwYP,
1124                                           1, new Vector3D(cosRaan, sinRaan, 0));
1125         final Vector3D duV = new Vector3D(cv * cosRaan / sinI, dwXV,
1126                                           cv * sinRaan / sinI, dwYV);
1127 
1128         // dv
1129         final Vector3D dvP = new Vector3D(-u * cosRaan * cosI / sinI + sinRaan * z, dwXP,
1130                                           -u * sinRaan * cosI / sinI - cosRaan * z, dwYP,
1131                                           cv, dwZP,
1132                                           1, new Vector3D(-sinRaan * cosI, cosRaan * cosI, sinI));
1133         final Vector3D dvV = new Vector3D(-u * cosRaan * cosI / sinI + sinRaan * z, dwXV,
1134                                           -u * sinRaan * cosI / sinI - cosRaan * z, dwYV,
1135                                           cv, dwZV);
1136 
1137         final Vector3D dc1P = new Vector3D(aOr2 * (2 * eSinE * eSinE + 1 - eCosE) / r2, position,
1138                                             -2 * aOr2 * eSinE * oOsqrtMuA, velocity);
1139         final Vector3D dc1V = new Vector3D(-2 * aOr2 * eSinE * oOsqrtMuA, position,
1140                                             2 / mu, velocity);
1141         final Vector3D dc2P = new Vector3D(aOr2 * eSinE * (eSinE * eSinE - (1 - e2)) / (r2 * epsilon), position,
1142                                             aOr2 * (1 - e2 - eSinE * eSinE) * oOsqrtMuA / epsilon, velocity);
1143         final Vector3D dc2V = new Vector3D(aOr2 * (1 - e2 - eSinE * eSinE) * oOsqrtMuA / epsilon, position,
1144                                             eSinE / (mu * epsilon), velocity);
1145 
1146         final double cof1   = aOr2 * (eCosE - e2);
1147         final double cof2   = aOr2 * epsilon * eSinE;
1148         final Vector3D dexP = new Vector3D(u, dc1P,  v, dc2P, cof1, duP,  cof2, dvP);
1149         final Vector3D dexV = new Vector3D(u, dc1V,  v, dc2V, cof1, duV,  cof2, dvV);
1150         final Vector3D deyP = new Vector3D(v, dc1P, -u, dc2P, cof1, dvP, -cof2, duP);
1151         final Vector3D deyV = new Vector3D(v, dc1V, -u, dc2V, cof1, dvV, -cof2, duV);
1152         fillHalfRow(1, dexP, jacobian[1], 0);
1153         fillHalfRow(1, dexV, jacobian[1], 3);
1154         fillHalfRow(1, deyP, jacobian[2], 0);
1155         fillHalfRow(1, deyV, jacobian[2], 3);
1156 
1157         final double cle = u / a + ex - eSinE * beta * ey;
1158         final double sle = v / a + ey + eSinE * beta * ex;
1159         final double m1  = beta * eCosE;
1160         final double m2  = 1 - m1 * eCosE;
1161         final double m3  = (u * ey - v * ex) + eSinE * beta * (u * ex + v * ey);
1162         final double m4  = -sle + cle * eSinE * beta;
1163         final double m5  = cle + sle * eSinE * beta;
1164         fillHalfRow((2 * m3 / r + aOr * eSinE + m1 * eSinE * (1 + m1 - (1 + aOr) * m2) / epsilon) / r2, position,
1165                     (m1 * m2 / epsilon - 1) * oOsqrtMuA, velocity,
1166                     m4, dexP, m5, deyP, -sle / a, duP, cle / a, dvP,
1167                     jacobian[5], 0);
1168         fillHalfRow((m1 * m2 / epsilon - 1) * oOsqrtMuA, position,
1169                     (2 * m3 + eSinE * a + m1 * eSinE * r * (eCosE * beta * 2 - aOr * m2) / epsilon) / mu, velocity,
1170                     m4, dexV, m5, deyV, -sle / a, duV, cle / a, dvV,
1171                     jacobian[5], 3);
1172 
1173         return jacobian;
1174 
1175     }
1176 
1177     /** {@inheritDoc} */
1178     @Override
1179     protected double[][] computeJacobianEccentricWrtCartesian() {
1180 
1181         // start by computing the Jacobian with mean angle
1182         final double[][] jacobian = computeJacobianMeanWrtCartesian();
1183 
1184         // Differentiating the Kepler equation aM = aE - ex sin aE + ey cos aE leads to:
1185         // daM = (1 - ex cos aE - ey sin aE) daE - sin aE dex + cos aE dey
1186         // which is inverted and rewritten as:
1187         // daE = a/r daM + sin aE a/r dex - cos aE a/r dey
1188         final double alphaE = getAlphaE();
1189         final SinCos scAe   = FastMath.sinCos(alphaE);
1190         final double cosAe  = scAe.cos();
1191         final double sinAe  = scAe.sin();
1192         final double aOr    = 1 / (1 - ex * cosAe - ey * sinAe);
1193 
1194         // update longitude row
1195         final double[] rowEx = jacobian[1];
1196         final double[] rowEy = jacobian[2];
1197         final double[] rowL  = jacobian[5];
1198         for (int j = 0; j < 6; ++j) {
1199             rowL[j] = aOr * (rowL[j] + sinAe * rowEx[j] - cosAe * rowEy[j]);
1200         }
1201 
1202         return jacobian;
1203 
1204     }
1205 
1206     /** {@inheritDoc} */
1207     @Override
1208     protected double[][] computeJacobianTrueWrtCartesian() {
1209 
1210         // start by computing the Jacobian with eccentric angle
1211         final double[][] jacobian = computeJacobianEccentricWrtCartesian();
1212 
1213         // Differentiating the eccentric latitude equation
1214         // tan((aV - aE)/2) = [ex sin aE - ey cos aE] / [sqrt(1-ex^2-ey^2) + 1 - ex cos aE - ey sin aE]
1215         // leads to
1216         // cT (daV - daE) = cE daE + cX dex + cY dey
1217         // with
1218         // cT = [d^2 + (ex sin aE - ey cos aE)^2] / 2
1219         // d  = 1 + sqrt(1-ex^2-ey^2) - ex cos aE - ey sin aE
1220         // cE = (ex cos aE + ey sin aE) (sqrt(1-ex^2-ey^2) + 1) - ex^2 - ey^2
1221         // cX =  sin aE (sqrt(1-ex^2-ey^2) + 1) - ey + ex (ex sin aE - ey cos aE) / sqrt(1-ex^2-ey^2)
1222         // cY = -cos aE (sqrt(1-ex^2-ey^2) + 1) + ex + ey (ex sin aE - ey cos aE) / sqrt(1-ex^2-ey^2)
1223         // which can be solved to find the differential of the true latitude
1224         // daV = (cT + cE) / cT daE + cX / cT deX + cY / cT deX
1225         final double alphaE    = getAlphaE();
1226         final SinCos scAe      = FastMath.sinCos(alphaE);
1227         final double cosAe     = scAe.cos();
1228         final double sinAe     = scAe.sin();
1229         final double eSinE     = ex * sinAe - ey * cosAe;
1230         final double ecosE     = ex * cosAe + ey * sinAe;
1231         final double e2        = ex * ex + ey * ey;
1232         final double epsilon   = FastMath.sqrt(1 - e2);
1233         final double onePeps   = 1 + epsilon;
1234         final double d         = onePeps - ecosE;
1235         final double cT        = (d * d + eSinE * eSinE) / 2;
1236         final double cE        = ecosE * onePeps - e2;
1237         final double cX        = ex * eSinE / epsilon - ey + sinAe * onePeps;
1238         final double cY        = ey * eSinE / epsilon + ex - cosAe * onePeps;
1239         final double factorLe  = (cT + cE) / cT;
1240         final double factorEx  = cX / cT;
1241         final double factorEy  = cY / cT;
1242 
1243         // update latitude row
1244         final double[] rowEx = jacobian[1];
1245         final double[] rowEy = jacobian[2];
1246         final double[] rowA = jacobian[5];
1247         for (int j = 0; j < 6; ++j) {
1248             rowA[j] = factorLe * rowA[j] + factorEx * rowEx[j] + factorEy * rowEy[j];
1249         }
1250 
1251         return jacobian;
1252 
1253     }
1254 
1255     /** {@inheritDoc} */
1256     @Override
1257     public void addKeplerContribution(final PositionAngleType type, final double gm,
1258                                       final double[] pDot) {
1259         pDot[5] += computeKeplerianAlphaDot(type, a, ex, ey, gm, cachedAlpha, cachedPositionAngleType);
1260     }
1261 
1262     /**
1263      * Compute rate of argument of latitude.
1264      * @param type position angle type of rate
1265      * @param a semi major axis
1266      * @param ex ex
1267      * @param ey ey
1268      * @param mu mu
1269      * @param alpha argument of latitude
1270      * @param cachedType position angle type of passed alpha
1271      * @return first-order time derivative for alpha
1272      * @since 12.2
1273      */
1274     private static double computeKeplerianAlphaDot(final PositionAngleType type, final double a, final double ex,
1275                                                    final double ey, final double mu,
1276                                                    final double alpha, final PositionAngleType cachedType) {
1277         final double n  = FastMath.sqrt(mu / a) / a;
1278         if (type == PositionAngleType.MEAN) {
1279             return n;
1280         }
1281         final double ksi;
1282         final SinCos sc;
1283         if (type == PositionAngleType.ECCENTRIC) {
1284             sc = FastMath.sinCos(CircularLatitudeArgumentUtility.convertAlpha(cachedType, alpha, ex, ey, type));
1285             ksi   = 1. / (1 - ex * sc.cos() - ey * sc.sin());
1286             return n * ksi;
1287         } else { // TRUE
1288             sc = FastMath.sinCos(CircularLatitudeArgumentUtility.convertAlpha(cachedType, alpha, ex, ey, type));
1289             final double oMe2  = 1 - ex * ex - ey * ey;
1290             ksi   = 1 + ex * sc.cos() + ey * sc.sin();
1291             return n * ksi * ksi / (oMe2 * FastMath.sqrt(oMe2));
1292         }
1293     }
1294 
1295     /**  Returns a string representation of this Orbit object.
1296      * @return a string representation of this object
1297      */
1298     public String toString() {
1299         return new StringBuilder().append("circular parameters: ").append('{').
1300                                   append("a: ").append(a).
1301                                   append(", ex: ").append(ex).append(", ey: ").append(ey).
1302                                   append(", i: ").append(FastMath.toDegrees(i)).
1303                                   append(", raan: ").append(FastMath.toDegrees(raan)).
1304                                   append(", alphaV: ").append(FastMath.toDegrees(getAlphaV())).
1305                                   append(";}").toString();
1306     }
1307 
1308     /** {@inheritDoc} */
1309     @Override
1310     public PositionAngleType getCachedPositionAngleType() {
1311         return cachedPositionAngleType;
1312     }
1313 
1314     /** {@inheritDoc} */
1315     @Override
1316     public boolean hasNonKeplerianRates() {
1317         return hasNonKeplerianAcceleration();
1318     }
1319 
1320     /** {@inheritDoc} */
1321     @Override
1322     public CircularOrbit withKeplerianRates() {
1323         final PositionAngleType positionAngleType = getCachedPositionAngleType();
1324         return new CircularOrbit(a, ex, ey, i, raan, cachedAlpha, positionAngleType, positionAngleType,
1325                 getFrame(), getDate(), getMu());
1326     }
1327 
1328 }