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