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