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.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.linear.DecompositionSolver;
21  import org.hipparchus.linear.MatrixUtils;
22  import org.hipparchus.linear.QRDecomposition;
23  import org.hipparchus.linear.RealMatrix;
24  import org.hipparchus.linear.RealVector;
25  import org.hipparchus.util.FastMath;
26  import org.hipparchus.util.MathArrays;
27  import org.orekit.errors.OrekitIllegalArgumentException;
28  import org.orekit.errors.OrekitMessages;
29  import org.orekit.frames.Frame;
30  import org.orekit.time.AbsoluteDate;
31  import org.orekit.time.TimeOffset;
32  import org.orekit.utils.PVCoordinates;
33  import org.orekit.utils.ShiftablePVCoordinatesHolder;
34  import org.orekit.utils.TimeStampedPVCoordinates;
35  
36  /**
37   * This class handles orbital parameters.
38  
39   * <p>
40   * For user convenience, both the Cartesian and the equinoctial elements
41   * are provided by this class, regardless of the canonical representation
42   * implemented in the derived class (which may be classical Keplerian
43   * elements for example).
44   * </p>
45   * <p>
46   * The parameters are defined in a frame specified by the user. It is important
47   * to make sure this frame is consistent: it probably is inertial and centered
48   * on the central body. This information is used for example by some
49   * force models.
50   * </p>
51   * <p>
52   * Instance of this class are guaranteed to be immutable.
53   * </p>
54   * @author Luc Maisonobe
55   * @author Guylaine Prat
56   * @author Fabien Maussion
57   * @author V&eacute;ronique Pommier-Maurussane
58   */
59  public abstract class Orbit
60      implements ShiftablePVCoordinatesHolder<Orbit>, OrbitalState {
61  
62      /** Absolute tolerance when checking if the rate of the position angle is Keplerian or not. */
63      protected static final double TOLERANCE_POSITION_ANGLE_RATE = 1e-15;
64  
65      /** Frame in which are defined the orbital parameters. */
66      private final Frame frame;
67  
68      /** Date of the orbital parameters. */
69      private final AbsoluteDate date;
70  
71      /** Value of mu used to compute position and velocity (m³/s²). */
72      private final double mu;
73  
74      /** Computed position.
75       * @since 12.0
76       */
77      private Vector3D position;
78  
79      /** Computed PVCoordinates. */
80      private TimeStampedPVCoordinates pvCoordinates;
81  
82      /** Jacobian of the orbital parameters with mean angle with respect to the Cartesian coordinates. */
83      private double[][] jacobianMeanWrtCartesian;
84  
85      /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with mean angle. */
86      private double[][] jacobianWrtParametersMean;
87  
88      /** Jacobian of the orbital parameters with eccentric angle with respect to the Cartesian coordinates. */
89      private double[][] jacobianEccentricWrtCartesian;
90  
91      /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with eccentric angle. */
92      private double[][] jacobianWrtParametersEccentric;
93  
94      /** Jacobian of the orbital parameters with true angle with respect to the Cartesian coordinates. */
95      private double[][] jacobianTrueWrtCartesian;
96  
97      /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with true angle. */
98      private double[][] jacobianWrtParametersTrue;
99  
100     /** Default constructor.
101      * Build a new instance with arbitrary default elements.
102      * @param frame the frame in which the parameters are defined
103      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
104      * @param date date of the orbital parameters
105      * @param mu central attraction coefficient (m^3/s^2)
106      * @exception IllegalArgumentException if frame is not a {@link
107      * Frame#isPseudoInertial pseudo-inertial frame}
108      */
109     protected Orbit(final Frame frame, final AbsoluteDate date, final double mu)
110         throws IllegalArgumentException {
111         ensurePseudoInertialFrame(frame);
112         this.date                      = date;
113         this.mu                        = mu;
114         this.pvCoordinates             = null;
115         this.frame                     = frame;
116         jacobianMeanWrtCartesian       = null;
117         jacobianWrtParametersMean      = null;
118         jacobianEccentricWrtCartesian  = null;
119         jacobianWrtParametersEccentric = null;
120         jacobianTrueWrtCartesian       = null;
121         jacobianWrtParametersTrue      = null;
122     }
123 
124     /** Set the orbit from Cartesian parameters.
125      *
126      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
127      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
128      * use {@code mu} and the position to compute the acceleration, including
129      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
130      *
131      * @param pvCoordinates the position and velocity in the inertial frame
132      * @param frame the frame in which the {@link TimeStampedPVCoordinates} are defined
133      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
134      * @param mu central attraction coefficient (m^3/s^2)
135      * @exception IllegalArgumentException if frame is not a {@link
136      * Frame#isPseudoInertial pseudo-inertial frame}
137      */
138     protected Orbit(final TimeStampedPVCoordinates pvCoordinates, final Frame frame, final double mu)
139         throws IllegalArgumentException {
140         ensurePseudoInertialFrame(frame);
141         this.date = pvCoordinates.getDate();
142         this.mu = mu;
143         if (pvCoordinates.getAcceleration().getNorm2Sq() == 0) {
144             // the acceleration was not provided,
145             // compute it from Newtonian attraction
146             final double r2 = pvCoordinates.getPosition().getNorm2Sq();
147             final double r3 = r2 * FastMath.sqrt(r2);
148             this.pvCoordinates = new TimeStampedPVCoordinates(pvCoordinates.getDate(),
149                                                               pvCoordinates.getPosition(),
150                                                               pvCoordinates.getVelocity(),
151                                                               new Vector3D(-mu / r3, pvCoordinates.getPosition()));
152         } else {
153             this.pvCoordinates = pvCoordinates;
154         }
155         this.frame = frame;
156     }
157 
158     /** Compute non-Keplerian part of the acceleration from first time derivatives.
159      * @return non-Keplerian part of the acceleration
160      * @since 13.1
161      */
162     protected Vector3D nonKeplerianAcceleration() {
163 
164         final double[][] dPdC = new double[6][6];
165         final PositionAngleType positionAngleType = PositionAngleType.MEAN;
166         getJacobianWrtCartesian(positionAngleType, dPdC);
167         final RealMatrix subMatrix = MatrixUtils.createRealMatrix(dPdC);
168 
169         final DecompositionSolver solver = getDecompositionSolver(subMatrix);
170 
171         final double[] derivatives = new double[6];
172         getType().mapOrbitToArray(this, positionAngleType, new double[6], derivatives);
173         derivatives[5] -= getKeplerianMeanMotion();
174 
175         final RealVector solution = solver.solve(MatrixUtils.createRealVector(derivatives));
176         return new Vector3D(solution.getEntry(3), solution.getEntry(4), solution.getEntry(5));
177 
178     }
179 
180     /** Check if Cartesian coordinates include non-Keplerian acceleration.
181      * @param pva Cartesian coordinates
182      * @param mu central attraction coefficient
183      * @return true if Cartesian coordinates include non-Keplerian acceleration
184      */
185     protected static boolean hasNonKeplerianAcceleration(final PVCoordinates pva, final double mu) {
186 
187         final Vector3D a = pva.getAcceleration();
188         if (a == null) {
189             return false;
190         }
191 
192         final Vector3D p = pva.getPosition();
193         final double r2 = p.getNorm2Sq();
194 
195         // Check if acceleration is relatively close to 0 compared to the Keplerian acceleration
196         final double tolerance = mu * 1e-9;
197         final Vector3D aTimesR2 = a.scalarMultiply(r2);
198         if (aTimesR2.getNorm() < tolerance) {
199             return false;
200         }
201 
202         if ((aTimesR2.add(p.normalize().scalarMultiply(mu))).getNorm() > tolerance) {
203             // we have a relevant acceleration, we can compute derivatives
204             return true;
205         } else {
206             // the provided acceleration is either too small to be reliable (probably even 0), or NaN
207             return false;
208         }
209     }
210 
211     /**
212      * Compute corrected shift from non-Keplerian part.
213      * @param keplerianShifted Keplerian shift
214      * @param dt time shift
215      * @return corrected position-velocity-acceleration vector
216      * @since 14.0
217      */
218     protected PVCoordinates shiftPVNonKeplerian(final PVCoordinates keplerianShifted, final double dt) {
219         // extract non-Keplerian acceleration from first time derivatives
220         final Vector3D nonKeplerianAcceleration = nonKeplerianAcceleration();
221 
222         // add second order effect of non-Keplerian acceleration to Keplerian-only shift
223         final Vector3D fixedV = nonKeplerianAcceleration.scalarMultiply(dt).add(keplerianShifted.getVelocity());
224         final Vector3D fixedP   = nonKeplerianAcceleration.scalarMultiply(dt * dt / 2.).add(keplerianShifted.getPosition());
225         final double   fixedR2 = fixedP.getNorm2Sq();
226         final double   fixedR  = FastMath.sqrt(fixedR2);
227         final Vector3D fixedA  = nonKeplerianAcceleration.add(new Vector3D(-getMu() / (fixedR2 * fixedR),
228                 keplerianShifted.getPosition()));
229         return new PVCoordinates(fixedP, fixedV, fixedA);
230     }
231 
232     /** Returns true if and only if the orbit is elliptical i.e. has a non-negative semi-major axis.
233      * @return true if getA() is strictly greater than 0
234      * @since 12.0
235      */
236     public boolean isElliptical() {
237         return getA() > 0.;
238     }
239 
240     /** Get the orbit type.
241      * @return orbit type
242      */
243     public abstract OrbitParamsType getType();
244 
245     /**
246      * Get a factory for the current orbit type.
247      *
248      * @param positionAngleType position angle type to use
249      * @param positionScale     position scale used to scale the orbital drivers
250      * @return factory building orbit instance of the correct type
251      * @since 14.0
252      */
253     public abstract AbstractOrbitFactory<? extends Orbit> factory(PositionAngleType positionAngleType,
254                                                                   double positionScale);
255 
256     /** Ensure the defining frame is a pseudo-inertial frame.
257      * @param frame frame to check
258      * @exception IllegalArgumentException if frame is not a {@link
259      * Frame#isPseudoInertial pseudo-inertial frame}
260      */
261     private static void ensurePseudoInertialFrame(final Frame frame)
262         throws IllegalArgumentException {
263         if (!frame.isPseudoInertial()) {
264             throw new OrekitIllegalArgumentException(OrekitMessages.NON_PSEUDO_INERTIAL_FRAME,
265                                                      frame.getName());
266         }
267     }
268 
269     /** Get the frame in which the orbital parameters are defined.
270      * @return frame in which the orbital parameters are defined
271      */
272     public Frame getFrame() {
273         return frame;
274     }
275 
276     /** Get the semi-major axis.
277      * <p>Note that the semi-major axis is considered negative for hyperbolic orbits.</p>
278      * @return semi-major axis (m)
279      */
280     public abstract double getA();
281 
282     /** Get the semi-major axis derivative.
283      * <p>Note that the semi-major axis is considered negative for hyperbolic orbits.</p>
284      * <p>
285      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
286      * </p>
287      * @return semi-major axis  derivative (m/s)
288      * @since 9.0
289      */
290     public abstract double getADot();
291 
292     /** Get the first component of the equinoctial eccentricity vector.
293      * @return first component of the equinoctial eccentricity vector
294      */
295     public abstract double getEquinoctialEx();
296 
297     /** Get the first component of the equinoctial eccentricity vector derivative.
298      * <p>
299      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
300      * </p>
301      * @return first component of the equinoctial eccentricity vector derivative
302      * @since 9.0
303      */
304     public abstract double getEquinoctialExDot();
305 
306     /** Get the second component of the equinoctial eccentricity vector.
307      * @return second component of the equinoctial eccentricity vector
308      */
309     public abstract double getEquinoctialEy();
310 
311     /** Get the second component of the equinoctial eccentricity vector derivative.
312      * <p>
313      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
314      * </p>
315      * @return second component of the equinoctial eccentricity vector derivative
316      * @since 9.0
317      */
318     public abstract double getEquinoctialEyDot();
319 
320     /** Get the first component of the inclination vector.
321      * @return first component of the inclination vector
322      */
323     public abstract double getHx();
324 
325     /** Get the first component of the inclination vector derivative.
326      * <p>
327      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
328      * </p>
329      * @return first component of the inclination vector derivative
330 
331      * @since 9.0
332      */
333     public abstract double getHxDot();
334 
335     /** Get the second component of the inclination vector.
336      * @return second component of the inclination vector
337      */
338     public abstract double getHy();
339 
340     /** Get the second component of the inclination vector derivative.
341      * <p>
342      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
343      * </p>
344      * @return second component of the inclination vector derivative
345      * @since 9.0
346      */
347     public abstract double getHyDot();
348 
349     /** Get the eccentric longitude argument.
350      * @return E + ω + Ω eccentric longitude argument (rad)
351      */
352     public abstract double getLE();
353 
354     /** Get the eccentric longitude argument derivative.
355      * <p>
356      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
357      * </p>
358      * @return d(E + ω + Ω)/dt eccentric longitude argument derivative (rad/s)
359      * @since 9.0
360      */
361     public abstract double getLEDot();
362 
363     /** Get the true longitude argument.
364      * @return v + ω + Ω true longitude argument (rad)
365      */
366     public abstract double getLv();
367 
368     /** Get the true longitude argument derivative.
369      * <p>
370      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
371      * </p>
372      * @return d(v + ω + Ω)/dt true longitude argument derivative (rad/s)
373      * @since 9.0
374      */
375     public abstract double getLvDot();
376 
377     /** Get the mean longitude argument.
378      * @return M + ω + Ω mean longitude argument (rad)
379      */
380     public abstract double getLM();
381 
382     /** Get the mean longitude argument derivative.
383      * <p>
384      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
385      * </p>
386      * @return d(M + ω + Ω)/dt mean longitude argument derivative (rad/s)
387      * @since 9.0
388      */
389     public abstract double getLMDot();
390 
391     // Additional orbital elements
392 
393     /** Get the eccentricity.
394      * @return eccentricity
395      */
396     public abstract double getE();
397 
398     /** Get the eccentricity derivative.
399      * <p>
400      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
401      * </p>
402      * @return eccentricity derivative
403      * @since 9.0
404      */
405     public abstract double getEDot();
406 
407     /** Get the inclination.
408      * @return inclination (rad)
409      */
410     public abstract double getI();
411 
412     /** Get the inclination derivative.
413      * <p>
414      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
415      * </p>
416      * @return inclination derivative (rad/s)
417      * @since 9.0
418      */
419     public abstract double getIDot();
420 
421     /** Check if orbit includes non-Keplerian rates.
422      * @return true if orbit includes non-Keplerian derivatives
423      * @see #getADot()
424      * @see #getEquinoctialExDot()
425      * @see #getEquinoctialEyDot()
426      * @see #getHxDot()
427      * @see #getHyDot()
428      * @see #getLEDot()
429      * @see #getLvDot()
430      * @see #getLMDot()
431      * @see #getEDot()
432      * @see #getIDot()
433      * @since 13.0
434      */
435     public boolean hasNonKeplerianAcceleration() {
436         return hasNonKeplerianAcceleration(getPVCoordinates(), getMu());
437     }
438 
439     /** Get the central acceleration constant.
440      * @return central acceleration constant
441      */
442     public double getMu() {
443         return mu;
444     }
445 
446     /** Get the Keplerian period.
447      * <p>The Keplerian period is computed directly from semi major axis
448      * and central acceleration constant.</p>
449      * @return Keplerian period in seconds, or positive infinity for hyperbolic orbits
450      */
451     public double getKeplerianPeriod() {
452         final double a = getA();
453         return isElliptical() ? 2.0 * FastMath.PI * a * FastMath.sqrt(a / mu) : Double.POSITIVE_INFINITY;
454     }
455 
456     /** Get the Keplerian mean motion.
457      * <p>The Keplerian mean motion is computed directly from semi major axis
458      * and central acceleration constant.</p>
459      * @return Keplerian mean motion in radians per second
460      */
461     public double getKeplerianMeanMotion() {
462         final double absA = FastMath.abs(getA());
463         return FastMath.sqrt(mu / absA) / absA;
464     }
465 
466     /** Get the derivative of the mean anomaly with respect to the semi major axis.
467      * @return derivative of the mean anomaly with respect to the semi major axis
468      */
469     public double getMeanAnomalyDotWrtA() {
470         return -1.5 * getKeplerianMeanMotion() / getA();
471     }
472 
473     /** Get the date of orbital parameters.
474      * @return date of the orbital parameters
475      */
476     public AbsoluteDate getDate() {
477         return date;
478     }
479 
480     @Override
481     public Vector3D getPosition(final AbsoluteDate otherDate, final Frame outputFrame) {
482         // use Keplerian-only motion
483         final double dt = otherDate.durationFrom(date);
484         final Orbit keplerianShifted = keplerianShiftedBy(dt);
485 
486         // Non-Keplerian acceleration shall be considered
487         if (hasNonKeplerianAcceleration()) {
488             // extract non-Keplerian acceleration from first time derivatives
489             final Vector3D nonKeplerianAcceleration = nonKeplerianAcceleration();
490             // add second order effect of non-Keplerian acceleration to Keplerian-only shift
491             final Vector3D shiftedPosition = nonKeplerianAcceleration.scalarMultiply(dt * dt / 2.)
492                     .add(keplerianShifted.getPosition());
493             if (outputFrame == getFrame()) {
494                 return shiftedPosition;
495             } else {
496                 return getFrame().getStaticTransformTo(outputFrame, otherDate).transformPosition(shiftedPosition);
497             }
498         }
499         // Keplerian-only motion is all we can do
500         else {
501             return keplerianShifted.getPosition(outputFrame);
502         }
503     }
504 
505     /** Get the position in definition frame.
506      * @return position in the definition frame
507      * @see #getPVCoordinates()
508      * @since 12.0
509      */
510     @Override
511     public Vector3D getPosition() {
512         if (position == null) {
513             position = initPosition();
514         }
515         return position;
516     }
517 
518     /** Get the {@link TimeStampedPVCoordinates} in definition frame.
519      * @return pvCoordinates in the definition frame
520      * @see #getPVCoordinates(Frame)
521      */
522     public TimeStampedPVCoordinates getPVCoordinates() {
523         if (pvCoordinates == null) {
524             pvCoordinates = initPVCoordinates();
525             position      = pvCoordinates.getPosition();
526         }
527         return pvCoordinates;
528     }
529 
530     /** Compute the position coordinates from the canonical parameters.
531      * @return computed position coordinates
532      * @since 12.0
533      */
534     protected abstract Vector3D initPosition();
535 
536     /** Compute the position/velocity coordinates from the canonical parameters.
537      * @return computed position/velocity coordinates
538      */
539     protected abstract TimeStampedPVCoordinates initPVCoordinates();
540 
541     /**
542      * Create a new object representing the same physical orbital state, but attached to a different reference frame.
543      * If the new frame is not inertial, an exception will be thrown.
544      *
545      * @param inertialFrame reference frame of output orbit
546      * @return orbit with different frame
547      * @since 13.0
548      */
549     public abstract Orbit inFrame(Frame inertialFrame);
550 
551     /** Get a time-shifted orbit.
552      * <p>
553      * The orbit can be slightly shifted to close dates. The shifting model is a
554      * Keplerian one if no derivatives are available in the orbit, or Keplerian
555      * plus quadratic effect of the non-Keplerian acceleration if derivatives are
556      * available. Shifting is <em>not</em> intended as a replacement for proper
557      * orbit propagation but should be sufficient for small time shifts or coarse
558      * accuracy.
559      * </p>
560      * @param dt time shift in seconds
561      * @return a new orbit, shifted with respect to the instance (which is immutable)
562      */
563     @Override
564     public abstract Orbit shiftedBy(double dt);
565 
566     /** Get a time-shifted orbit.
567      * <p>
568      * The orbit can be slightly shifted to close dates. The shifting model is a
569      * Keplerian one if no derivatives are available in the orbit, or Keplerian
570      * plus quadratic effect of the non-Keplerian acceleration if derivatives are
571      * available. Shifting is <em>not</em> intended as a replacement for proper
572      * orbit propagation but should be sufficient for small time shifts or coarse
573      * accuracy.
574      * </p>
575      * @param dt time shift
576      * @return a new orbit, shifted with respect to the instance (which is immutable)
577      */
578     @Override
579     public abstract Orbit shiftedBy(TimeOffset dt);
580 
581     /** Get a time-shifted orbit assuming pure Keplerian motion.
582      * @param dt time shift
583      * @return a new orbit, shifted with respect to the instance (which is immutable)
584      * @since 14.0
585      */
586     protected abstract Orbit keplerianShiftedBy(double dt);
587 
588     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
589      * <p>
590      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
591      * respect to Cartesian coordinate j. This means each row corresponds to one orbital parameter
592      * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
593      * </p>
594      * @param type type of the position angle to use
595      * @param jacobian placeholder 6x6 (or larger) matrix to be filled with the Jacobian, if matrix
596      * is larger than 6x6, only the 6x6 upper left corner will be modified
597      */
598     public void getJacobianWrtCartesian(final PositionAngleType type, final double[][] jacobian) {
599 
600         final double[][] cachedJacobian;
601         synchronized (this) {
602             cachedJacobian = switch (type) {
603                 case MEAN -> {
604                     if (jacobianMeanWrtCartesian == null) {
605                         // first call, we need to compute the Jacobian and cache it
606                         jacobianMeanWrtCartesian = computeJacobianMeanWrtCartesian();
607                     }
608                     yield jacobianMeanWrtCartesian;
609                 }
610                 case ECCENTRIC -> {
611                     if (jacobianEccentricWrtCartesian == null) {
612                         // first call, we need to compute the Jacobian and cache it
613                         jacobianEccentricWrtCartesian = computeJacobianEccentricWrtCartesian();
614                     }
615                     yield jacobianEccentricWrtCartesian;
616                 }
617                 case TRUE -> {
618                     if (jacobianTrueWrtCartesian == null) {
619                         // first call, we need to compute the Jacobian and cache it
620                         jacobianTrueWrtCartesian = computeJacobianTrueWrtCartesian();
621                     }
622                     yield jacobianTrueWrtCartesian;
623                 }
624             };
625         }
626 
627         // fill the user provided array
628         for (int i = 0; i < cachedJacobian.length; ++i) {
629             System.arraycopy(cachedJacobian[i], 0, jacobian[i], 0, cachedJacobian[i].length);
630         }
631 
632     }
633 
634     /** Compute the Jacobian of the Cartesian parameters with respect to the orbital parameters.
635      * <p>
636      * Element {@code jacobian[i][j]} is the derivative of Cartesian coordinate i of the orbit with
637      * respect to orbital parameter j. This means each row corresponds to one Cartesian coordinate
638      * x, y, z, xdot, ydot, zdot whereas columns 0 to 5 correspond to the orbital parameters.
639      * </p>
640      * @param type type of the position angle to use
641      * @param jacobian placeholder 6x6 (or larger) matrix to be filled with the Jacobian, if matrix
642      * is larger than 6x6, only the 6x6 upper left corner will be modified
643      */
644     public void getJacobianWrtParameters(final PositionAngleType type, final double[][] jacobian) {
645 
646         final double[][] cachedJacobian;
647         synchronized (this) {
648             cachedJacobian = switch (type) {
649                 case MEAN -> {
650                     if (jacobianWrtParametersMean == null) {
651                         // first call, we need to compute the Jacobian and cache it
652                         jacobianWrtParametersMean = createInverseJacobian(type);
653                     }
654                     yield jacobianWrtParametersMean;
655                 }
656                 case ECCENTRIC -> {
657                     if (jacobianWrtParametersEccentric == null) {
658                         // first call, we need to compute the Jacobian and cache it
659                         jacobianWrtParametersEccentric = createInverseJacobian(type);
660                     }
661                     yield jacobianWrtParametersEccentric;
662                 }
663                 case TRUE -> {
664                     if (jacobianWrtParametersTrue == null) {
665                         // first call, we need to compute the Jacobian and cache it
666                         jacobianWrtParametersTrue = createInverseJacobian(type);
667                     }
668                     yield jacobianWrtParametersTrue;
669                 }
670             };
671         }
672 
673         // fill the user-provided array
674         for (int i = 0; i < cachedJacobian.length; ++i) {
675             System.arraycopy(cachedJacobian[i], 0, jacobian[i], 0, cachedJacobian[i].length);
676         }
677 
678     }
679 
680     /** Create an inverse Jacobian.
681      * @param type type of the position angle to use
682      * @return inverse Jacobian
683      */
684     private double[][] createInverseJacobian(final PositionAngleType type) {
685 
686         // get the direct Jacobian
687         final double[][] directJacobian = new double[6][6];
688         getJacobianWrtCartesian(type, directJacobian);
689 
690         // invert the direct Jacobian
691         final RealMatrix matrix = MatrixUtils.createRealMatrix(directJacobian);
692         final DecompositionSolver solver = getDecompositionSolver(matrix);
693         return solver.getInverse().getData();
694 
695     }
696 
697     /**
698      * Method to build a matrix decomposition solver.
699      * @param realMatrix matrix
700      * @return solver
701      * @since 13.1
702      */
703     protected DecompositionSolver getDecompositionSolver(final RealMatrix realMatrix) {
704         return new QRDecomposition(realMatrix).getSolver();
705     }
706 
707     /** Compute the Jacobian of the orbital parameters with mean angle with respect to the Cartesian parameters.
708      * <p>
709      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
710      * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter
711      * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
712      * </p>
713      * <p>
714      * The array returned by this method will not be modified.
715      * </p>
716      * @return 6x6 Jacobian matrix
717      * @see #computeJacobianEccentricWrtCartesian()
718      * @see #computeJacobianTrueWrtCartesian()
719      */
720     protected abstract double[][] computeJacobianMeanWrtCartesian();
721 
722     /** Compute the Jacobian of the orbital parameters with eccentric angle with respect to the Cartesian parameters.
723      * <p>
724      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
725      * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter
726      * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
727      * </p>
728      * <p>
729      * The array returned by this method will not be modified.
730      * </p>
731      * @return 6x6 Jacobian matrix
732      * @see #computeJacobianMeanWrtCartesian()
733      * @see #computeJacobianTrueWrtCartesian()
734      */
735     protected abstract double[][] computeJacobianEccentricWrtCartesian();
736 
737     /** Compute the Jacobian of the orbital parameters with true angle with respect to the Cartesian parameters.
738      * <p>
739      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
740      * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter
741      * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
742      * </p>
743      * <p>
744      * The array returned by this method will not be modified.
745      * </p>
746      * @return 6x6 Jacobian matrix
747      * @see #computeJacobianMeanWrtCartesian()
748      * @see #computeJacobianEccentricWrtCartesian()
749      */
750     protected abstract double[][] computeJacobianTrueWrtCartesian();
751 
752     /** Add the contribution of the Keplerian motion to parameters derivatives.
753      * <p>
754      * This method is used by integration-based propagators to evaluate the part of Keplerian
755      * motion to evolution of the orbital state.
756      * </p>
757      * @param type type of the position angle in the state
758      * @param gm attraction coefficient to use
759      * @param pDot array containing orbital state derivatives to update (the Keplerian
760      * part must be <em>added</em> to the array components, as the array may already
761      * contain some non-zero elements corresponding to non-Keplerian parts)
762      */
763     public abstract void addKeplerContribution(PositionAngleType type, double gm, double[] pDot);
764 
765         /** Fill a Jacobian half row with a single vector.
766      * @param a coefficient of the vector
767      * @param v vector
768      * @param row Jacobian matrix row
769      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
770      */
771     protected static void fillHalfRow(final double a, final Vector3D v, final double[] row, final int j) {
772         row[j]     = a * v.getX();
773         row[j + 1] = a * v.getY();
774         row[j + 2] = a * v.getZ();
775     }
776 
777     /** Fill a Jacobian half row with a linear combination of vectors.
778      * @param a1 coefficient of the first vector
779      * @param v1 first vector
780      * @param a2 coefficient of the second vector
781      * @param v2 second vector
782      * @param row Jacobian matrix row
783      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
784      */
785     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
786                                       final double[] row, final int j) {
787         row[j]     = MathArrays.linearCombination(a1, v1.getX(), a2, v2.getX());
788         row[j + 1] = MathArrays.linearCombination(a1, v1.getY(), a2, v2.getY());
789         row[j + 2] = MathArrays.linearCombination(a1, v1.getZ(), a2, v2.getZ());
790     }
791 
792     /** Fill a Jacobian half row with a linear combination of vectors.
793      * @param a1 coefficient of the first vector
794      * @param v1 first vector
795      * @param a2 coefficient of the second vector
796      * @param v2 second vector
797      * @param a3 coefficient of the third vector
798      * @param v3 third vector
799      * @param row Jacobian matrix row
800      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
801      */
802     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
803                                       final double a3, final Vector3D v3,
804                                       final double[] row, final int j) {
805         row[j]     = MathArrays.linearCombination(a1, v1.getX(), a2, v2.getX(), a3, v3.getX());
806         row[j + 1] = MathArrays.linearCombination(a1, v1.getY(), a2, v2.getY(), a3, v3.getY());
807         row[j + 2] = MathArrays.linearCombination(a1, v1.getZ(), a2, v2.getZ(), a3, v3.getZ());
808     }
809 
810     /** Fill a Jacobian half row with a linear combination of vectors.
811      * @param a1 coefficient of the first vector
812      * @param v1 first vector
813      * @param a2 coefficient of the second vector
814      * @param v2 second vector
815      * @param a3 coefficient of the third vector
816      * @param v3 third vector
817      * @param a4 coefficient of the fourth vector
818      * @param v4 fourth vector
819      * @param row Jacobian matrix row
820      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
821      */
822     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
823                                       final double a3, final Vector3D v3, final double a4, final Vector3D v4,
824                                       final double[] row, final int j) {
825         row[j]     = MathArrays.linearCombination(a1, v1.getX(), a2, v2.getX(), a3, v3.getX(), a4, v4.getX());
826         row[j + 1] = MathArrays.linearCombination(a1, v1.getY(), a2, v2.getY(), a3, v3.getY(), a4, v4.getY());
827         row[j + 2] = MathArrays.linearCombination(a1, v1.getZ(), a2, v2.getZ(), a3, v3.getZ(), a4, v4.getZ());
828     }
829 
830     /** Fill a Jacobian half row with a linear combination of vectors.
831      * @param a1 coefficient of the first vector
832      * @param v1 first vector
833      * @param a2 coefficient of the second vector
834      * @param v2 second vector
835      * @param a3 coefficient of the third vector
836      * @param v3 third vector
837      * @param a4 coefficient of the fourth vector
838      * @param v4 fourth vector
839      * @param a5 coefficient of the fifth vector
840      * @param v5 fifth vector
841      * @param row Jacobian matrix row
842      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
843      */
844     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
845                                       final double a3, final Vector3D v3, final double a4, final Vector3D v4,
846                                       final double a5, final Vector3D v5,
847                                       final double[] row, final int j) {
848         final double[] a = new double[] {
849             a1, a2, a3, a4, a5
850         };
851         row[j]     = MathArrays.linearCombination(a, new double[] {
852             v1.getX(), v2.getX(), v3.getX(), v4.getX(), v5.getX()
853         });
854         row[j + 1] = MathArrays.linearCombination(a, new double[] {
855             v1.getY(), v2.getY(), v3.getY(), v4.getY(), v5.getY()
856         });
857         row[j + 2] = MathArrays.linearCombination(a, new double[] {
858             v1.getZ(), v2.getZ(), v3.getZ(), v4.getZ(), v5.getZ()
859         });
860     }
861 
862     /** Fill a Jacobian half row with a linear combination of vectors.
863      * @param a1 coefficient of the first vector
864      * @param v1 first vector
865      * @param a2 coefficient of the second vector
866      * @param v2 second vector
867      * @param a3 coefficient of the third vector
868      * @param v3 third vector
869      * @param a4 coefficient of the fourth vector
870      * @param v4 fourth vector
871      * @param a5 coefficient of the fifth vector
872      * @param v5 fifth vector
873      * @param a6 coefficient of the sixth vector
874      * @param v6 sixth vector
875      * @param row Jacobian matrix row
876      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
877      */
878     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
879                                       final double a3, final Vector3D v3, final double a4, final Vector3D v4,
880                                       final double a5, final Vector3D v5, final double a6, final Vector3D v6,
881                                       final double[] row, final int j) {
882         final double[] a = new double[] {
883             a1, a2, a3, a4, a5, a6
884         };
885         row[j]     = MathArrays.linearCombination(a, new double[] {
886             v1.getX(), v2.getX(), v3.getX(), v4.getX(), v5.getX(), v6.getX()
887         });
888         row[j + 1] = MathArrays.linearCombination(a, new double[] {
889             v1.getY(), v2.getY(), v3.getY(), v4.getY(), v5.getY(), v6.getY()
890         });
891         row[j + 2] = MathArrays.linearCombination(a, new double[] {
892             v1.getZ(), v2.getZ(), v3.getZ(), v4.getZ(), v5.getZ(), v6.getZ()
893         });
894     }
895 
896 }