1   /* Copyright 2002-2025 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.propagation;
18  
19  import org.hipparchus.analysis.differentiation.Gradient;
20  import org.hipparchus.analysis.differentiation.GradientField;
21  import org.hipparchus.linear.Array2DRowRealMatrix;
22  import org.hipparchus.linear.MatrixUtils;
23  import org.hipparchus.linear.RealMatrix;
24  import org.hipparchus.util.MathArrays;
25  import org.orekit.errors.OrekitException;
26  import org.orekit.errors.OrekitMessages;
27  import org.orekit.frames.Frame;
28  import org.orekit.frames.KinematicTransform;
29  import org.orekit.frames.LOF;
30  import org.orekit.orbits.FieldOrbit;
31  import org.orekit.orbits.Orbit;
32  import org.orekit.orbits.OrbitType;
33  import org.orekit.orbits.PositionAngleType;
34  import org.orekit.time.AbsoluteDate;
35  import org.orekit.time.FieldAbsoluteDate;
36  import org.orekit.time.TimeStamped;
37  import org.orekit.utils.CartesianCovarianceUtils;
38  
39  /** This class is the representation of a covariance matrix at a given date.
40   * <p>
41   * Currently, the covariance only represents the orbital elements.
42   * <p>
43   * It is possible to change the covariance frame by using the
44   * {@link #changeCovarianceFrame(Orbit, Frame)} or {@link #changeCovarianceFrame(Orbit, LOF)} method.
45   * These methods are based on Equations (18) and (20) of <i>Covariance Transformations for Satellite
46   * Flight Dynamics Operations</i> by David A. SVallado.
47   * <p>
48   * Finally, covariance orbit type can be changed using the
49   * {@link #changeCovarianceType(Orbit, OrbitType, PositionAngleType)} method.
50   *
51   * @author Bryan Cazabonne
52   * @author Vincent Cucchietti
53   * @since 11.3
54   */
55  public class StateCovariance implements TimeStamped {
56  
57      /** State dimension. */
58      public static final int STATE_DIMENSION = 6;
59  
60      /** Default position angle for covariance expressed in Cartesian elements. */
61      private static final PositionAngleType DEFAULT_POSITION_ANGLE = PositionAngleType.TRUE;
62  
63      /** Orbital covariance [6x6]. */
64      private final RealMatrix orbitalCovariance;
65  
66      /** Covariance frame (can be null if LOF is defined). */
67      private final Frame frame;
68  
69      /** Covariance LOF type (can be null if frame is defined). */
70      private final LOF lof;
71  
72      /** Covariance epoch. */
73      private final AbsoluteDate epoch;
74  
75      /** Covariance orbit type. */
76      private final OrbitType orbitType;
77  
78      /** Covariance position angle type (not used if orbitType is CARTESIAN). */
79      private final PositionAngleType angleType;
80  
81      /**
82       * Constructor.
83       * @param orbitalCovariance 6x6 orbital parameters covariance
84       * @param epoch epoch of the covariance
85       * @param lof covariance LOF type
86       */
87      public StateCovariance(final RealMatrix orbitalCovariance, final AbsoluteDate epoch, final LOF lof) {
88          this(orbitalCovariance, epoch, null, lof, OrbitType.CARTESIAN, DEFAULT_POSITION_ANGLE);
89      }
90  
91      /**
92       * Constructor.
93       * @param orbitalCovariance 6x6 orbital parameters covariance
94       * @param epoch epoch of the covariance
95       * @param covarianceFrame covariance frame (inertial or Earth fixed)
96       * @param orbitType orbit type of the covariance (CARTESIAN if covarianceFrame is not pseudo-inertial)
97       * @param angleType position angle type of the covariance (not used if orbitType is CARTESIAN)
98       */
99      public StateCovariance(final RealMatrix orbitalCovariance, final AbsoluteDate epoch,
100                            final Frame covarianceFrame,
101                            final OrbitType orbitType, final PositionAngleType angleType) {
102         this(orbitalCovariance, epoch, covarianceFrame, null, orbitType, angleType);
103     }
104 
105     /**
106      * Private constructor.
107      * @param orbitalCovariance 6x6 orbital parameters covariance
108      * @param epoch epoch of the covariance
109      * @param covarianceFrame covariance frame (inertial or Earth fixed)
110      * @param lof covariance LOF type
111      * @param orbitType orbit type of the covariance
112      * @param angleType position angle type of the covariance (not used if orbitType is CARTESIAN)
113      */
114     private StateCovariance(final RealMatrix orbitalCovariance, final AbsoluteDate epoch,
115                             final Frame covarianceFrame, final LOF lof,
116                             final OrbitType orbitType, final PositionAngleType angleType) {
117 
118         checkFrameAndOrbitTypeConsistency(covarianceFrame, orbitType);
119 
120         this.orbitalCovariance = orbitalCovariance;
121         this.epoch = epoch;
122         this.frame     = covarianceFrame;
123         this.lof       = lof;
124         this.orbitType = orbitType;
125         this.angleType = angleType;
126 
127     }
128 
129     /**
130      * Check constructor's inputs consistency.
131      *
132      * @param covarianceFrame covariance frame (inertial or Earth fixed)
133      * @param inputType orbit type of the covariance
134      *
135      * @throws OrekitException if input frame is not pseudo-inertial AND the orbit type is not Cartesian
136      */
137     public static void checkFrameAndOrbitTypeConsistency(final Frame covarianceFrame, final OrbitType inputType) {
138 
139         // State covariance expressed in a celestial body frame
140         if (covarianceFrame != null) {
141 
142             // Input frame is not pseudo-inertial
143             if (!covarianceFrame.isPseudoInertial() && inputType != OrbitType.CARTESIAN) {
144                 throw new OrekitException(OrekitMessages.WRONG_ORBIT_PARAMETERS_TYPE,
145                                           inputType.name(),
146                                           OrbitType.CARTESIAN.name());
147             }
148         }
149     }
150 
151     /**
152      * Checks if input/output orbit and angle types are identical.
153      *
154      * @param inOrbitType input orbit type
155      * @param inAngleType input angle type
156      * @param outOrbitType output orbit type
157      * @param outAngleType output angle type
158      * @return flag defining if input/output orbit and angle types are identical
159      */
160     public static boolean inputAndOutputAreIdentical(final OrbitType inOrbitType, final PositionAngleType inAngleType,
161                                                      final OrbitType outOrbitType, final PositionAngleType outAngleType) {
162         return inOrbitType == outOrbitType && inAngleType == outAngleType;
163     }
164 
165     /**
166      * Checks if input and output orbit types are both {@code OrbitType.CARTESIAN}.
167      *
168      * @param inOrbitType input orbit type
169      * @param outOrbitType output orbit type
170      * @return flag defining if input and output orbit types are both {@code OrbitType.CARTESIAN}
171      */
172     public static boolean inputAndOutputOrbitTypesAreCartesian(final OrbitType inOrbitType, final OrbitType outOrbitType) {
173         return inOrbitType == OrbitType.CARTESIAN && outOrbitType == OrbitType.CARTESIAN;
174     }
175 
176     /** {@inheritDoc}. */
177     @Override
178     public AbsoluteDate getDate() {
179         return epoch;
180     }
181 
182     /**
183      * Get the covariance matrix.
184      * @return the covariance matrix
185      */
186     public RealMatrix getMatrix() {
187         return orbitalCovariance;
188     }
189 
190     /**
191      * Get the covariance orbit type.
192      * @return the covariance orbit type
193      */
194     public OrbitType getOrbitType() {
195         return orbitType;
196     }
197 
198     /**
199      * Get the covariance angle type.
200      * @return the covariance angle type
201      */
202     public PositionAngleType getPositionAngleType() {
203         return angleType;
204     }
205 
206     /**
207      * Get the covariance frame.
208      * @return the covariance frame (can be null)
209      * @see #getLOF()
210      */
211     public Frame getFrame() {
212         return frame;
213     }
214 
215     /**
216      * Get the covariance LOF type.
217      * @return the covariance LOF type (can be null)
218      * @see #getFrame()
219      */
220     public LOF getLOF() {
221         return lof;
222     }
223 
224     /**
225      * Get the covariance matrix in another orbit type.
226      * <p>
227      * The covariance orbit type <b>cannot</b> be changed if the covariance
228      * matrix is expressed in a {@link LOF local orbital frame} or a
229      * non-pseudo inertial frame.
230      * <p>
231      * As this type change uses the jacobian matrix of the transformation, it introduces a linear approximation.
232      * Hence, the current covariance matrix <b>will not exactly match</b> the new linearized case and the
233      * distribution will not follow a generalized Gaussian distribution anymore.
234      * <p>
235      * This is based on equation (1) to (6) from "Vallado, D. A. (2004). Covariance transformations for satellite flight
236      * dynamics operations."
237      *
238      * @param orbit orbit to which the covariance matrix should correspond
239      * @param outOrbitType target orbit type of the state covariance matrix
240      * @param outAngleType target position angle type of the state covariance matrix
241      * @return a new covariance state, expressed in the target orbit type with the target position angle
242      * @see #changeCovarianceFrame(Orbit, Frame)
243      */
244     public StateCovariance changeCovarianceType(final Orbit orbit, final OrbitType outOrbitType,
245                                                 final PositionAngleType outAngleType) {
246 
247         // Handle case where the covariance is already expressed in the output type
248         if (outOrbitType == orbitType && (outOrbitType == OrbitType.CARTESIAN || outAngleType == angleType)) {
249             if (lof == null) {
250                 return new StateCovariance(orbitalCovariance, epoch, frame, orbitType, angleType);
251             }
252             else {
253                 return new StateCovariance(orbitalCovariance, epoch, lof);
254             }
255         }
256 
257         // Check if the covariance is expressed in a celestial body frame
258         if (frame != null) {
259 
260             // Check if the covariance is defined in an inertial frame
261             if (frame.isPseudoInertial()) {
262                 return changeTypeAndCreate(orbit, epoch, frame, orbitType, angleType, outOrbitType, outAngleType,
263                                            orbitalCovariance);
264             }
265 
266             // The covariance is not defined in an inertial frame. The orbit type cannot be changed
267             throw new OrekitException(OrekitMessages.CANNOT_CHANGE_COVARIANCE_TYPE_IF_DEFINED_IN_NON_INERTIAL_FRAME);
268 
269         }
270 
271         // The covariance is not expressed in a celestial body frame. The orbit type cannot be changed
272         throw new OrekitException(OrekitMessages.CANNOT_CHANGE_COVARIANCE_TYPE_IF_DEFINED_IN_LOF);
273 
274     }
275 
276     /**
277      * Get the covariance in a given local orbital frame.
278      * <p>
279      * Changing the covariance frame is a linear process, this method does not introduce approximation unless a change
280      * in covariance orbit type is required.
281      * <p>
282      * This is based on equation (18) to (20) "from Vallado, D. A. (2004). Covariance transformations for satellite
283      * flight dynamics operations."
284      *
285      * @param orbit orbit to which the covariance matrix should correspond
286      * @param lofOut output local orbital frame
287      * @return a new covariance state, expressed in the output local orbital frame
288      */
289     public StateCovariance changeCovarianceFrame(final Orbit orbit, final LOF lofOut) {
290 
291         // Verify current covariance frame
292         if (lof != null) {
293 
294             // Check specific case where output covariance will be the same
295             if (lofOut == lof) {
296                 return new StateCovariance(orbitalCovariance, epoch, lof);
297             }
298 
299             // Change the covariance local orbital frame
300             return changeFrameAndCreate(orbit, epoch, lof, lofOut, orbitalCovariance);
301 
302         } else {
303 
304             // Covariance is expressed in celestial body frame
305             return changeFrameAndCreate(orbit, epoch, frame, lofOut, orbitalCovariance, orbitType, angleType);
306 
307         }
308 
309     }
310 
311     /**
312      * Get the covariance in the output frame.
313      * <p>
314      * Changing the covariance frame is a linear process, this method does not introduce approximation unless a change
315      * in covariance orbit type is required.
316      * <p>
317      * This is based on equation (18) to (20) "from Vallado, D. A. (2004). Covariance transformations for satellite
318      * flight dynamics operations."
319      *
320      * @param orbit orbit to which the covariance matrix should correspond
321      * @param frameOut output frame
322      * @return a new covariance state, expressed in the output frame
323      */
324     public StateCovariance changeCovarianceFrame(final Orbit orbit, final Frame frameOut) {
325 
326         // Verify current covariance frame
327         if (lof != null) {
328 
329             // Covariance is expressed in local orbital frame
330             return changeFrameAndCreate(orbit, epoch, lof, frameOut, orbitalCovariance);
331 
332         } else {
333 
334             // Check specific case where output covariance will be the same
335             if (frame == frameOut) {
336                 return new StateCovariance(orbitalCovariance, epoch, frame, orbitType, angleType);
337             }
338 
339             // Change covariance frame
340             return changeFrameAndCreate(orbit, epoch, frame, frameOut, orbitalCovariance, orbitType, angleType);
341 
342         }
343 
344     }
345 
346     /**
347      * Get a time-shifted covariance matrix.
348      * <p>
349      * The shifting model is a linearized, Keplerian one. In other words, it is based on a state transition matrix that
350      * is computed assuming Keplerian motion.
351      * <p>
352      * Shifting is <em>not</em> intended as a replacement for proper covariance propagation, but should be sufficient
353      * for small time shifts or coarse accuracy.
354      *
355      * @param orbit orbit to which the covariance matrix should correspond
356      * @param dt time shift in seconds
357      * @return a new covariance state, shifted with respect to the instance
358      */
359     public StateCovariance shiftedBy(final Orbit orbit, final double dt) {
360 
361         // Shifted orbit
362         final Orbit shifted = orbit.shiftedBy(dt);
363 
364         // State covariance expressed in celestial body frame
365         if (frame != null) {
366 
367             // State covariance expressed in a pseudo-inertial frame
368             if (frame.isPseudoInertial()) {
369 
370                 // Shift covariance by applying the STM
371                 final RealMatrix stm = getKeplerianStm(orbit, dt);
372                 return new StateCovariance(stm.multiply(getMatrix().multiplyTransposed(stm)), getDate().shiftedBy(dt),
373                         frame, orbitType, getPositionAngleType());
374 
375             }
376 
377             // State covariance expressed in a non pseudo-inertial frame
378             else {
379 
380                 // Convert state covariance to orbit pseudo-inertial frame
381                 final StateCovariance inOrbitFrame = changeCovarianceFrame(orbit, orbit.getFrame());
382 
383                 // Shift the state covariance
384                 final StateCovariance shiftedCovariance = inOrbitFrame.shiftedBy(orbit, dt);
385 
386                 // Restore the initial covariance frame
387                 return shiftedCovariance.changeCovarianceFrame(shifted, frame);
388             }
389         }
390 
391         // State covariance expressed in a commonly used local orbital frame (LOF)
392         else {
393 
394             // Convert state covariance to orbit pseudo-inertial frame
395             final StateCovariance inOrbitFrame = changeCovarianceFrame(orbit, orbit.getFrame());
396 
397             // Shift the state covariance
398             final StateCovariance shiftedCovariance = inOrbitFrame.shiftedBy(orbit, dt);
399 
400             // Restore the initial covariance frame
401             return shiftedCovariance.changeCovarianceFrame(shifted, lof);
402         }
403 
404     }
405 
406     /**
407      * Create a covariance matrix in another orbit type.
408      * <p>
409      * As this type change uses the jacobian matrix of the transformation, it introduces a linear approximation.
410      * Hence, the input covariance matrix <b>will not exactly match</b> the new linearized case and the
411      * distribution will not follow a generalized Gaussian distribution anymore.
412      * <p>
413      * This is based on equation (1) to (6) from "Vallado, D. A. (2004). Covariance transformations for satellite flight
414      * dynamics operations."
415      *
416      * @param orbit orbit to which the covariance matrix should correspond
417      * @param date covariance epoch
418      * @param covFrame covariance frame
419      * @param inOrbitType initial orbit type of the state covariance matrix
420      * @param inAngleType initial position angle type of the state covariance matrix
421      * @param outOrbitType target orbit type of the state covariance matrix
422      * @param outAngleType target position angle type of the state covariance matrix
423      * @param inputCov input covariance
424      * @return the covariance expressed in the target orbit type with the target position angle
425      */
426     private static StateCovariance changeTypeAndCreate(final Orbit orbit,
427                                                        final AbsoluteDate date,
428                                                        final Frame covFrame,
429                                                        final OrbitType inOrbitType, final PositionAngleType inAngleType,
430                                                        final OrbitType outOrbitType, final PositionAngleType outAngleType,
431                                                        final RealMatrix inputCov) {
432 
433         // Check if type change is really necessary, if not then return input covariance
434         if (inputAndOutputOrbitTypesAreCartesian(inOrbitType, outOrbitType) ||
435             inputAndOutputAreIdentical(inOrbitType, inAngleType, outOrbitType, outAngleType)) {
436             return new StateCovariance(inputCov, date, covFrame, inOrbitType, inAngleType);
437         }
438 
439         // Notations:
440         // I: Input orbit type
441         // O: Output orbit type
442         // C: Cartesian parameters
443 
444         // Compute dOutputdCartesian
445         final double[][] aOC               = new double[STATE_DIMENSION][STATE_DIMENSION];
446         final Orbit      orbitInOutputType = outOrbitType.convertType(orbit);
447         orbitInOutputType.getJacobianWrtCartesian(outAngleType, aOC);
448         final RealMatrix dOdC = new Array2DRowRealMatrix(aOC, false);
449 
450         // Compute dCartesiandInput
451         final double[][] aCI              = new double[STATE_DIMENSION][STATE_DIMENSION];
452         final Orbit      orbitInInputType = inOrbitType.convertType(orbit);
453         orbitInInputType.getJacobianWrtParameters(inAngleType, aCI);
454         final RealMatrix dCdI = new Array2DRowRealMatrix(aCI, false);
455 
456         // Compute dOutputdInput
457         final RealMatrix dOdI = dOdC.multiply(dCdI);
458 
459         // Conversion of the state covariance matrix in target orbit type
460         final RealMatrix outputCov = dOdI.multiply(inputCov.multiplyTransposed(dOdI));
461 
462         // Return the converted covariance
463         return new StateCovariance(outputCov, date, covFrame, outOrbitType, outAngleType);
464 
465     }
466 
467     /**
468      * Create a covariance matrix from a {@link LOF local orbital frame} to another
469      * {@link LOF local orbital frame}.
470      * <p>
471      * Changing the covariance frame is a linear process, this method does not introduce approximation.
472      * <p>
473      * The transformation is based on Equation (18) to (20) of "Covariance Transformations for Satellite Flight Dynamics
474      * Operations" by David A. Vallado.
475      *
476      * @param orbit orbit to which the covariance matrix should correspond
477      * @param date covariance epoch
478      * @param lofIn the local orbital frame in which the input covariance matrix is expressed
479      * @param lofOut the target local orbital frame
480      * @param inputCartesianCov input covariance {@code CARTESIAN})
481      * @return the covariance matrix expressed in the target commonly used local orbital frame in Cartesian elements
482      */
483     private static StateCovariance changeFrameAndCreate(final Orbit orbit, final AbsoluteDate date,
484                                                         final LOF lofIn, final LOF lofOut,
485                                                         final RealMatrix inputCartesianCov) {
486 
487         // Builds the matrix to perform covariance transformation
488         final RealMatrix jacobianFromLofInToLofOut =
489                 getJacobian(LOF.transformFromLOFInToLOFOut(lofIn, lofOut, date, orbit.getPVCoordinates()));
490 
491         // Get the Cartesian covariance matrix converted to frameOut
492         final RealMatrix cartesianCovarianceOut =
493                 jacobianFromLofInToLofOut.multiply(inputCartesianCov.multiplyTransposed(jacobianFromLofInToLofOut));
494 
495         // Output converted covariance
496         return new StateCovariance(cartesianCovarianceOut, date, lofOut);
497 
498     }
499 
500     /**
501      * Convert the covariance matrix from a {@link Frame frame} to a {@link LOF local orbital frame}.
502      * <p>
503      * Changing the covariance frame is a linear process, this method does not introduce approximation unless a change
504      * in covariance orbit type is required.
505      * <p>
506      * The transformation is based on Equation (18) to (20) of "Covariance Transformations for Satellite Flight Dynamics
507      * Operations" by David A. Vallado.
508      * <p>
509      * As the frame transformation must be performed with the covariance expressed in Cartesian elements, both the orbit
510      * and position angle types of the input covariance must be provided.
511      * <p>
512      * <b>The output covariance matrix will provided in Cartesian orbit type and not converted back to
513      * its original expression (if input different from Cartesian elements).</b>
514      *
515      * @param orbit orbit to which the covariance matrix should correspond
516      * @param date covariance epoch
517      * @param frameIn the frame in which the input covariance matrix is expressed. In case the frame is <b>not</b>
518      * pseudo-inertial, the input covariance matrix is expected to be expressed in <b>Cartesian elements</b>.
519      * @param lofOut the target local orbital frame
520      * @param inputCov input covariance
521      * @param covOrbitType orbit type of the covariance matrix (used if frameIn is pseudo-inertial)
522      * @param covAngleType position angle type of the covariance matrix (used if frameIn is pseudo-inertial) (not used
523      * if covOrbitType equals {@code CARTESIAN})
524      * @return the covariance matrix expressed in the target local orbital frame in Cartesian elements
525      * @throws OrekitException if input frame is <b>not</b> pseudo-inertial <b>and</b> the input covariance is
526      * <b>not</b> expressed in Cartesian elements.
527      */
528     private static StateCovariance changeFrameAndCreate(final Orbit orbit, final AbsoluteDate date,
529                                                         final Frame frameIn, final LOF lofOut,
530                                                         final RealMatrix inputCov,
531                                                         final OrbitType covOrbitType,
532                                                         final PositionAngleType covAngleType) {
533 
534         // Input frame is inertial
535         if (frameIn.isPseudoInertial()) {
536 
537             // Convert input matrix to Cartesian parameters in input frame
538             final RealMatrix cartesianCovarianceIn =
539                     changeTypeAndCreate(orbit, date, frameIn, covOrbitType, covAngleType,
540                                         OrbitType.CARTESIAN, PositionAngleType.MEAN,
541                                         inputCov).getMatrix();
542 
543             // Builds the matrix to perform covariance transformation
544             final RealMatrix jacobianFromFrameInToLofOut =
545                             getJacobian(lofOut.transformFromInertial(date, orbit.getPVCoordinates(frameIn)));
546 
547             // Get the Cartesian covariance matrix converted to frameOut
548             final RealMatrix cartesianCovarianceOut =
549                     jacobianFromFrameInToLofOut.multiply(cartesianCovarianceIn.multiplyTransposed(jacobianFromFrameInToLofOut));
550 
551             // Return converted covariance matrix expressed in Cartesian elements
552             return new StateCovariance(cartesianCovarianceOut, date, lofOut);
553 
554         }
555 
556         // Input frame is not inertial so the covariance matrix is expected to be in Cartesian elements
557         else {
558 
559             // Method checkInputConsistency already verify that input covariance is defined in CARTESIAN type
560             final Frame orbitInertialFrame = orbit.getFrame();
561 
562             // Compute rotation matrix from frameIn to orbit inertial frame
563             final RealMatrix cartesianCovarianceInOrbitFrame =
564                    changeFrameAndCreate(orbit, date, frameIn, orbitInertialFrame, inputCov,
565                                          OrbitType.CARTESIAN, PositionAngleType.MEAN).getMatrix();
566 
567             // Convert from orbit inertial frame to lofOut
568             return changeFrameAndCreate(orbit, date, orbitInertialFrame, lofOut, cartesianCovarianceInOrbitFrame,
569                                         OrbitType.CARTESIAN, PositionAngleType.MEAN);
570 
571         }
572 
573     }
574 
575     /**
576      * Convert the covariance matrix from a {@link LOF  local orbital frame} to a {@link Frame frame}.
577      * <p>
578      * Changing the covariance frame is a linear process, this method does not introduce approximation.
579      * <p>
580      * The transformation is based on Equation (18) to (20) of "Covariance Transformations for Satellite Flight Dynamics
581      * Operations" by David A. Vallado.
582      * <p>
583      * The <u>input</u> covariance matrix is necessarily provided in <b>Cartesian orbit type</b>.
584      * <p>
585      * The <u>output</u> covariance matrix will be expressed in <b>Cartesian elements</b>.
586      *
587      * @param orbit orbit to which the covariance matrix should correspond
588      * @param date covariance epoch
589      * @param lofIn the local orbital frame in which the input covariance matrix is expressed
590      * @param frameOut the target frame
591      * @param inputCartesianCov input covariance ({@code CARTESIAN})
592      * @return the covariance matrix expressed in the target frame in Cartesian elements
593      */
594     private static StateCovariance changeFrameAndCreate(final Orbit orbit, final AbsoluteDate date,
595                                                         final LOF lofIn, final Frame frameOut,
596                                                         final RealMatrix inputCartesianCov) {
597 
598         // Output frame is pseudo-inertial
599         if (frameOut.isPseudoInertial()) {
600 
601             // Builds the matrix to perform covariance transformation
602             final RealMatrix jacobianFromLofInToFrameOut =
603                     getJacobian(lofIn.transformFromInertial(date, orbit.getPVCoordinates(frameOut)).getInverse());
604 
605             // Transform covariance
606             final RealMatrix transformedCovariance =
607                     jacobianFromLofInToFrameOut.multiply(inputCartesianCov.multiplyTransposed(jacobianFromLofInToFrameOut));
608 
609             // Get the Cartesian covariance matrix converted to frameOut
610             return new StateCovariance(transformedCovariance, date, frameOut, OrbitType.CARTESIAN,
611                                        DEFAULT_POSITION_ANGLE);
612 
613         }
614 
615         // Output frame is not pseudo-inertial
616         else {
617 
618             // Builds the matrix to perform covariance transformation
619             final RealMatrix jacobianFromLofInToOrbitFrame =
620                     getJacobian(lofIn.transformFromInertial(date, orbit.getPVCoordinates()).getInverse());
621 
622             // Get the Cartesian covariance matrix converted to orbit inertial frame
623             final RealMatrix cartesianCovarianceInOrbitFrame = jacobianFromLofInToOrbitFrame.multiply(
624                     inputCartesianCov.multiplyTransposed(jacobianFromLofInToOrbitFrame));
625 
626             // Get the Cartesian covariance matrix converted to frameOut
627             return changeFrameAndCreate(orbit, date, orbit.getFrame(), frameOut, cartesianCovarianceInOrbitFrame,
628                                         OrbitType.CARTESIAN, PositionAngleType.MEAN);
629         }
630 
631     }
632 
633     /**
634      * Get the covariance matrix in another frame.
635      * <p>
636      * The transformation is based on Equation (18) of "Covariance Transformations for Satellite Flight Dynamics
637      * Operations" by David A. Vallado.
638      * <p>
639      * Changing the covariance frame is a linear process, this method does not introduce approximation unless a change
640      * in covariance orbit type is required.
641      * <p>
642      * As the frame transformation must be performed with the covariance expressed in Cartesian elements, both the orbit
643      * and position angle types of the input covariance must be provided.
644      * <p>
645      * In case the <u>input</u> frame is <b>not</b> pseudo-inertial, the <u>input</u> covariance matrix is necessarily
646      * expressed in <b>Cartesian elements</b>.
647      * <p>
648      * In case the <u>output</u> frame is <b>not</b> pseudo-inertial, the <u>output</u> covariance matrix will be
649      * expressed in <b>Cartesian elements</b>.
650      *
651      * @param orbit orbit to which the covariance matrix should correspond
652      * @param date covariance epoch
653      * @param frameIn the frame in which the input covariance matrix is expressed
654      * @param frameOut the target frame
655      * @param inputCov input covariance
656      * @param covOrbitType orbit type of the covariance matrix (used if frameIn is pseudo-inertial)
657      * @param covAngleType position angle type of the covariance matrix (used if frameIn is pseudo-inertial) (<b>not</b>
658      * used if covOrbitType equals {@code CARTESIAN})
659      * @return the covariance matrix expressed in the target frame
660      * @throws OrekitException if input frame is <b>not</b> pseudo-inertial <b>and</b> the input covariance is
661      * <b>not</b> expressed in Cartesian elements.
662      */
663     private static StateCovariance changeFrameAndCreate(final Orbit orbit, final AbsoluteDate date,
664                                                         final Frame frameIn, final Frame frameOut,
665                                                         final RealMatrix inputCov,
666                                                         final OrbitType covOrbitType,
667                                                         final PositionAngleType covAngleType) {
668 
669         // Input frame pseudo-inertial
670         if (frameIn.isPseudoInertial()) {
671 
672             // Convert input matrix to Cartesian parameters in input frame
673             final RealMatrix cartesianCovarianceIn =
674                     changeTypeAndCreate(orbit, date, frameIn, covOrbitType, covAngleType,
675                                         OrbitType.CARTESIAN, PositionAngleType.MEAN,
676                                         inputCov).getMatrix();
677 
678             // Get the Cartesian covariance matrix converted to frameOut
679             final RealMatrix cartesianCovarianceOut = CartesianCovarianceUtils.changeReferenceFrame(frameIn,
680                     cartesianCovarianceIn, date, frameOut);
681 
682             // Output frame is pseudo-inertial
683             if (frameOut.isPseudoInertial()) {
684 
685                 // Convert output Cartesian matrix to initial orbit type and position angle
686                 return changeTypeAndCreate(orbit, date, frameOut, OrbitType.CARTESIAN, PositionAngleType.MEAN,
687                                            covOrbitType, covAngleType, cartesianCovarianceOut);
688 
689             }
690 
691             // Output frame is not pseudo-inertial
692             else {
693 
694                 // Output Cartesian matrix
695                 return new StateCovariance(cartesianCovarianceOut, date, frameOut, OrbitType.CARTESIAN,
696                                            DEFAULT_POSITION_ANGLE);
697 
698             }
699         }
700 
701         // Input frame is not pseudo-inertial
702         else {
703 
704             // Method checkInputConsistency already verify that input covariance is defined in CARTESIAN type
705 
706             // Convert covariance matrix to frameOut
707             final RealMatrix covInFrameOut = CartesianCovarianceUtils.changeReferenceFrame(frameIn,
708                     inputCov, date, frameOut);
709 
710             // Output the Cartesian covariance matrix converted to frameOut
711             return new StateCovariance(covInFrameOut, date, frameOut, OrbitType.CARTESIAN, DEFAULT_POSITION_ANGLE);
712 
713         }
714 
715     }
716 
717     /**
718      * Builds the matrix to perform covariance frame transformation.
719      * @param transform input transformation
720      * @return the matrix to perform the covariance frame transformation
721      */
722     private static RealMatrix getJacobian(final KinematicTransform transform) {
723         return MatrixUtils.createRealMatrix(transform.getPVJacobian());
724     }
725 
726     /**
727      * Get the state transition matrix considering Keplerian contribution only and assuming equinoctial elements with mean anomaly.
728      *
729      * @param initialOrbit orbit to which the initial covariance matrix should correspond
730      * @param dt time difference between the two orbits
731      * @return the state transition matrix used to shift the covariance matrix
732      * @deprecated since 13.1. If you must, do:
733      * final RealMatrix stm = MatrixUtils.createRealIdentityMatrix(STATE_DIMENSION);
734      * double contribution = initialOrbit.getMeanAnomalyDotWrtA() * dt;
735      * stm.setEntry(5, 0, contribution);
736      */
737     @Deprecated
738     public static RealMatrix getStm(final Orbit initialOrbit, final double dt) {
739         // initialize the STM
740         final RealMatrix stm = MatrixUtils.createRealIdentityMatrix(STATE_DIMENSION);
741 
742         // State transition matrix using Keplerian contribution only
743         final double contribution = initialOrbit.getMeanAnomalyDotWrtA() * dt;
744         stm.setEntry(5, 0, contribution);
745 
746         // Return
747         return stm;
748     }
749 
750     /**
751      * Get the state transition matrix of Keplerian motion.
752      * The coordinates used are those of the covariance itself.
753      *
754      * @param initialOrbit orbit to which the initial covariance matrix should correspond
755      * @param dt time difference between the two orbits
756      * @return the state transition matrix used to shift the covariance matrix
757      * @since 13.1
758      */
759     RealMatrix getKeplerianStm(final Orbit initialOrbit, final double dt) {
760 
761         // compute derivatives of Keplerian motion
762         final GradientField field = GradientField.getField(STATE_DIMENSION);
763         final double[] stateVector = new double[STATE_DIMENSION];
764         orbitType.mapOrbitToArray(initialOrbit, getPositionAngleType(), stateVector, null);
765         final Gradient[] fieldGradientStateVector = MathArrays.buildArray(field, STATE_DIMENSION);
766         for (int i = 0; i < STATE_DIMENSION; i++) {
767             fieldGradientStateVector[i] = Gradient.variable(STATE_DIMENSION, i, stateVector[i]);
768         }
769         final FieldAbsoluteDate<Gradient> fieldGradientDate = new FieldAbsoluteDate<>(field, initialOrbit.getDate());
770         final FieldOrbit<Gradient> fieldOrbit = orbitType.mapArrayToOrbit(fieldGradientStateVector, null, getPositionAngleType(),
771                 fieldGradientDate, field.getOne().newInstance(initialOrbit.getMu()), initialOrbit.getFrame());
772         final FieldOrbit<Gradient> shiftedOrbit = fieldOrbit.shiftedBy(dt);  // automatic differentiation
773         final Gradient[] gradient = MathArrays.buildArray(field, STATE_DIMENSION);
774         orbitType.mapOrbitToArray(shiftedOrbit, getPositionAngleType(), gradient, null);
775 
776         // Return state transition matrix
777         final RealMatrix stm = MatrixUtils.createRealIdentityMatrix(STATE_DIMENSION);
778         for (int i = 0; i < gradient.length; i++) {
779             stm.setRow(i, gradient[i].getGradient());
780         }
781         return stm;
782 
783     }
784 
785 }