Orbit.java

  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.orbits;

  18. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  19. import org.hipparchus.linear.DecompositionSolver;
  20. import org.hipparchus.linear.MatrixUtils;
  21. import org.hipparchus.linear.QRDecomposition;
  22. import org.hipparchus.linear.RealMatrix;
  23. import org.hipparchus.linear.RealVector;
  24. import org.hipparchus.util.FastMath;
  25. import org.hipparchus.util.MathArrays;
  26. import org.orekit.errors.OrekitIllegalArgumentException;
  27. import org.orekit.errors.OrekitInternalError;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.frames.Frame;
  30. import org.orekit.frames.StaticTransform;
  31. import org.orekit.frames.Transform;
  32. import org.orekit.time.AbsoluteDate;
  33. import org.orekit.time.TimeOffset;
  34. import org.orekit.time.TimeShiftable;
  35. import org.orekit.time.TimeStamped;
  36. import org.orekit.utils.PVCoordinates;
  37. import org.orekit.utils.PVCoordinatesProvider;
  38. import org.orekit.utils.TimeStampedPVCoordinates;

  39. /**
  40.  * This class handles orbital parameters.

  41.  * <p>
  42.  * For user convenience, both the Cartesian and the equinoctial elements
  43.  * are provided by this class, regardless of the canonical representation
  44.  * implemented in the derived class (which may be classical Keplerian
  45.  * elements for example).
  46.  * </p>
  47.  * <p>
  48.  * The parameters are defined in a frame specified by the user. It is important
  49.  * to make sure this frame is consistent: it probably is inertial and centered
  50.  * on the central body. This information is used for example by some
  51.  * force models.
  52.  * </p>
  53.  * <p>
  54.  * Instance of this class are guaranteed to be immutable.
  55.  * </p>
  56.  * @author Luc Maisonobe
  57.  * @author Guylaine Prat
  58.  * @author Fabien Maussion
  59.  * @author V&eacute;ronique Pommier-Maurussane
  60.  */
  61. public abstract class Orbit
  62.     implements TimeStamped, TimeShiftable<Orbit>, PVCoordinatesProvider {

  63.     /** Absolute tolerance when checking if the rate of the position angle is Keplerian or not. */
  64.     protected static final double TOLERANCE_POSITION_ANGLE_RATE = 1e-15;

  65.     /** Frame in which are defined the orbital parameters. */
  66.     private final Frame frame;

  67.     /** Date of the orbital parameters. */
  68.     private final AbsoluteDate date;

  69.     /** Value of mu used to compute position and velocity (m³/s²). */
  70.     private final double mu;

  71.     /** Computed position.
  72.      * @since 12.0
  73.      */
  74.     private Vector3D position;

  75.     /** Computed PVCoordinates. */
  76.     private TimeStampedPVCoordinates pvCoordinates;

  77.     /** Jacobian of the orbital parameters with mean angle with respect to the Cartesian coordinates. */
  78.     private double[][] jacobianMeanWrtCartesian;

  79.     /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with mean angle. */
  80.     private double[][] jacobianWrtParametersMean;

  81.     /** Jacobian of the orbital parameters with eccentric angle with respect to the Cartesian coordinates. */
  82.     private double[][] jacobianEccentricWrtCartesian;

  83.     /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with eccentric angle. */
  84.     private double[][] jacobianWrtParametersEccentric;

  85.     /** Jacobian of the orbital parameters with true angle with respect to the Cartesian coordinates. */
  86.     private double[][] jacobianTrueWrtCartesian;

  87.     /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with true angle. */
  88.     private double[][] jacobianWrtParametersTrue;

  89.     /** Default constructor.
  90.      * Build a new instance with arbitrary default elements.
  91.      * @param frame the frame in which the parameters are defined
  92.      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
  93.      * @param date date of the orbital parameters
  94.      * @param mu central attraction coefficient (m^3/s^2)
  95.      * @exception IllegalArgumentException if frame is not a {@link
  96.      * Frame#isPseudoInertial pseudo-inertial frame}
  97.      */
  98.     protected Orbit(final Frame frame, final AbsoluteDate date, final double mu)
  99.         throws IllegalArgumentException {
  100.         ensurePseudoInertialFrame(frame);
  101.         this.date                      = date;
  102.         this.mu                        = mu;
  103.         this.pvCoordinates             = null;
  104.         this.frame                     = frame;
  105.         jacobianMeanWrtCartesian       = null;
  106.         jacobianWrtParametersMean      = null;
  107.         jacobianEccentricWrtCartesian  = null;
  108.         jacobianWrtParametersEccentric = null;
  109.         jacobianTrueWrtCartesian       = null;
  110.         jacobianWrtParametersTrue      = null;
  111.     }

  112.     /** Set the orbit from Cartesian parameters.
  113.      *
  114.      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
  115.      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
  116.      * use {@code mu} and the position to compute the acceleration, including
  117.      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
  118.      *
  119.      * @param pvCoordinates the position and velocity in the inertial frame
  120.      * @param frame the frame in which the {@link TimeStampedPVCoordinates} are defined
  121.      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
  122.      * @param mu central attraction coefficient (m^3/s^2)
  123.      * @exception IllegalArgumentException if frame is not a {@link
  124.      * Frame#isPseudoInertial pseudo-inertial frame}
  125.      */
  126.     protected Orbit(final TimeStampedPVCoordinates pvCoordinates, final Frame frame, final double mu)
  127.         throws IllegalArgumentException {
  128.         ensurePseudoInertialFrame(frame);
  129.         this.date = pvCoordinates.getDate();
  130.         this.mu = mu;
  131.         if (pvCoordinates.getAcceleration().getNormSq() == 0) {
  132.             // the acceleration was not provided,
  133.             // compute it from Newtonian attraction
  134.             final double r2 = pvCoordinates.getPosition().getNormSq();
  135.             final double r3 = r2 * FastMath.sqrt(r2);
  136.             this.pvCoordinates = new TimeStampedPVCoordinates(pvCoordinates.getDate(),
  137.                                                               pvCoordinates.getPosition(),
  138.                                                               pvCoordinates.getVelocity(),
  139.                                                               new Vector3D(-mu / r3, pvCoordinates.getPosition()));
  140.         } else {
  141.             this.pvCoordinates = pvCoordinates;
  142.         }
  143.         this.frame = frame;
  144.     }

  145.     /** Compute non-Keplerian part of the acceleration from first time derivatives.
  146.      * @return non-Keplerian part of the acceleration
  147.      * @since 13.1
  148.      */
  149.     protected Vector3D nonKeplerianAcceleration() {

  150.         final double[][] dPdC = new double[6][6];
  151.         final PositionAngleType positionAngleType = PositionAngleType.MEAN;
  152.         getJacobianWrtCartesian(positionAngleType, dPdC);
  153.         final RealMatrix subMatrix = MatrixUtils.createRealMatrix(dPdC);

  154.         final DecompositionSolver solver = getDecompositionSolver(subMatrix);

  155.         final double[] derivatives = new double[6];
  156.         getType().mapOrbitToArray(this, positionAngleType, new double[6], derivatives);
  157.         derivatives[5] -= getKeplerianMeanMotion();

  158.         final RealVector solution = solver.solve(MatrixUtils.createRealVector(derivatives));
  159.         return new Vector3D(solution.getEntry(3), solution.getEntry(4), solution.getEntry(5));

  160.     }

  161.     /** Check if Cartesian coordinates include non-Keplerian acceleration.
  162.      * @param pva Cartesian coordinates
  163.      * @param mu central attraction coefficient
  164.      * @return true if Cartesian coordinates include non-Keplerian acceleration
  165.      */
  166.     protected static boolean hasNonKeplerianAcceleration(final PVCoordinates pva, final double mu) {

  167.         final Vector3D a = pva.getAcceleration();
  168.         if (a == null) {
  169.             return false;
  170.         }

  171.         final Vector3D p = pva.getPosition();
  172.         final double r2 = p.getNormSq();

  173.         // Check if acceleration is relatively close to 0 compared to the Keplerian acceleration
  174.         final double tolerance = mu * 1e-9;
  175.         final Vector3D aTimesR2 = a.scalarMultiply(r2);
  176.         if (aTimesR2.getNorm() < tolerance) {
  177.             return false;
  178.         }

  179.         if ((aTimesR2.add(p.normalize().scalarMultiply(mu))).getNorm() > tolerance) {
  180.             // we have a relevant acceleration, we can compute derivatives
  181.             return true;
  182.         } else {
  183.             // the provided acceleration is either too small to be reliable (probably even 0), or NaN
  184.             return false;
  185.         }
  186.     }

  187.     /** Returns true if and only if the orbit is elliptical i.e. has a non-negative semi-major axis.
  188.      * @return true if getA() is strictly greater than 0
  189.      * @since 12.0
  190.      */
  191.     public boolean isElliptical() {
  192.         return getA() > 0.;
  193.     }

  194.     /** Get the orbit type.
  195.      * @return orbit type
  196.      */
  197.     public abstract OrbitType getType();

  198.     /** Ensure the defining frame is a pseudo-inertial frame.
  199.      * @param frame frame to check
  200.      * @exception IllegalArgumentException if frame is not a {@link
  201.      * Frame#isPseudoInertial pseudo-inertial frame}
  202.      */
  203.     private static void ensurePseudoInertialFrame(final Frame frame)
  204.         throws IllegalArgumentException {
  205.         if (!frame.isPseudoInertial()) {
  206.             throw new OrekitIllegalArgumentException(OrekitMessages.NON_PSEUDO_INERTIAL_FRAME,
  207.                                                      frame.getName());
  208.         }
  209.     }

  210.     /** Get the frame in which the orbital parameters are defined.
  211.      * @return frame in which the orbital parameters are defined
  212.      */
  213.     public Frame getFrame() {
  214.         return frame;
  215.     }

  216.     /** Get the semi-major axis.
  217.      * <p>Note that the semi-major axis is considered negative for hyperbolic orbits.</p>
  218.      * @return semi-major axis (m)
  219.      */
  220.     public abstract double getA();

  221.     /** Get the semi-major axis derivative.
  222.      * <p>Note that the semi-major axis is considered negative for hyperbolic orbits.</p>
  223.      * <p>
  224.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  225.      * </p>
  226.      * @return semi-major axis  derivative (m/s)
  227.      * @since 9.0
  228.      */
  229.     public abstract double getADot();

  230.     /** Get the first component of the equinoctial eccentricity vector.
  231.      * @return first component of the equinoctial eccentricity vector
  232.      */
  233.     public abstract double getEquinoctialEx();

  234.     /** Get the first component of the equinoctial eccentricity vector derivative.
  235.      * <p>
  236.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  237.      * </p>
  238.      * @return first component of the equinoctial eccentricity vector derivative
  239.      * @since 9.0
  240.      */
  241.     public abstract double getEquinoctialExDot();

  242.     /** Get the second component of the equinoctial eccentricity vector.
  243.      * @return second component of the equinoctial eccentricity vector
  244.      */
  245.     public abstract double getEquinoctialEy();

  246.     /** Get the second component of the equinoctial eccentricity vector derivative.
  247.      * <p>
  248.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  249.      * </p>
  250.      * @return second component of the equinoctial eccentricity vector derivative
  251.      * @since 9.0
  252.      */
  253.     public abstract double getEquinoctialEyDot();

  254.     /** Get the first component of the inclination vector.
  255.      * @return first component of the inclination vector
  256.      */
  257.     public abstract double getHx();

  258.     /** Get the first component of the inclination vector derivative.
  259.      * <p>
  260.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  261.      * </p>
  262.      * @return first component of the inclination vector derivative

  263.      * @since 9.0
  264.      */
  265.     public abstract double getHxDot();

  266.     /** Get the second component of the inclination vector.
  267.      * @return second component of the inclination vector
  268.      */
  269.     public abstract double getHy();

  270.     /** Get the second component of the inclination vector derivative.
  271.      * <p>
  272.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  273.      * </p>
  274.      * @return second component of the inclination vector derivative
  275.      * @since 9.0
  276.      */
  277.     public abstract double getHyDot();

  278.     /** Get the eccentric longitude argument.
  279.      * @return E + ω + Ω eccentric longitude argument (rad)
  280.      */
  281.     public abstract double getLE();

  282.     /** Get the eccentric longitude argument derivative.
  283.      * <p>
  284.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  285.      * </p>
  286.      * @return d(E + ω + Ω)/dt eccentric longitude argument derivative (rad/s)
  287.      * @since 9.0
  288.      */
  289.     public abstract double getLEDot();

  290.     /** Get the true longitude argument.
  291.      * @return v + ω + Ω true longitude argument (rad)
  292.      */
  293.     public abstract double getLv();

  294.     /** Get the true longitude argument derivative.
  295.      * <p>
  296.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  297.      * </p>
  298.      * @return d(v + ω + Ω)/dt true longitude argument derivative (rad/s)
  299.      * @since 9.0
  300.      */
  301.     public abstract double getLvDot();

  302.     /** Get the mean longitude argument.
  303.      * @return M + ω + Ω mean longitude argument (rad)
  304.      */
  305.     public abstract double getLM();

  306.     /** Get the mean longitude argument derivative.
  307.      * <p>
  308.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  309.      * </p>
  310.      * @return d(M + ω + Ω)/dt mean longitude argument derivative (rad/s)
  311.      * @since 9.0
  312.      */
  313.     public abstract double getLMDot();

  314.     // Additional orbital elements

  315.     /** Get the eccentricity.
  316.      * @return eccentricity
  317.      */
  318.     public abstract double getE();

  319.     /** Get the eccentricity derivative.
  320.      * <p>
  321.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  322.      * </p>
  323.      * @return eccentricity derivative
  324.      * @since 9.0
  325.      */
  326.     public abstract double getEDot();

  327.     /** Get the inclination.
  328.      * @return inclination (rad)
  329.      */
  330.     public abstract double getI();

  331.     /** Get the inclination derivative.
  332.      * <p>
  333.      * If the orbit was created without derivatives, the value returned is {@link Double#NaN}.
  334.      * </p>
  335.      * @return inclination derivative (rad/s)
  336.      * @since 9.0
  337.      */
  338.     public abstract double getIDot();

  339.     /** Check if orbit includes non-Keplerian rates.
  340.      * @return true if orbit includes non-Keplerian derivatives
  341.      * @see #getADot()
  342.      * @see #getEquinoctialExDot()
  343.      * @see #getEquinoctialEyDot()
  344.      * @see #getHxDot()
  345.      * @see #getHyDot()
  346.      * @see #getLEDot()
  347.      * @see #getLvDot()
  348.      * @see #getLMDot()
  349.      * @see #getEDot()
  350.      * @see #getIDot()
  351.      * @since 13.0
  352.      */
  353.     public boolean hasNonKeplerianAcceleration() {
  354.         return hasNonKeplerianAcceleration(getPVCoordinates(), getMu());
  355.     }

  356.     /** Get the central acceleration constant.
  357.      * @return central acceleration constant
  358.      */
  359.     public double getMu() {
  360.         return mu;
  361.     }

  362.     /** Get the Keplerian period.
  363.      * <p>The Keplerian period is computed directly from semi major axis
  364.      * and central acceleration constant.</p>
  365.      * @return Keplerian period in seconds, or positive infinity for hyperbolic orbits
  366.      */
  367.     public double getKeplerianPeriod() {
  368.         final double a = getA();
  369.         return isElliptical() ? 2.0 * FastMath.PI * a * FastMath.sqrt(a / mu) : Double.POSITIVE_INFINITY;
  370.     }

  371.     /** Get the Keplerian mean motion.
  372.      * <p>The Keplerian mean motion is computed directly from semi major axis
  373.      * and central acceleration constant.</p>
  374.      * @return Keplerian mean motion in radians per second
  375.      */
  376.     public double getKeplerianMeanMotion() {
  377.         final double absA = FastMath.abs(getA());
  378.         return FastMath.sqrt(mu / absA) / absA;
  379.     }

  380.     /** Get the derivative of the mean anomaly with respect to the semi major axis.
  381.      * @return derivative of the mean anomaly with respect to the semi major axis
  382.      */
  383.     public double getMeanAnomalyDotWrtA() {
  384.         return -1.5 * getKeplerianMeanMotion() / getA();
  385.     }

  386.     /** Get the date of orbital parameters.
  387.      * @return date of the orbital parameters
  388.      */
  389.     public AbsoluteDate getDate() {
  390.         return date;
  391.     }

  392.     /** Get the {@link TimeStampedPVCoordinates} in a specified frame.
  393.      * @param outputFrame frame in which the position/velocity coordinates shall be computed
  394.      * @return pvCoordinates in the specified output frame
  395.           * @see #getPVCoordinates()
  396.      */
  397.     public TimeStampedPVCoordinates getPVCoordinates(final Frame outputFrame) {
  398.         if (pvCoordinates == null) {
  399.             pvCoordinates = initPVCoordinates();
  400.         }

  401.         // If output frame requested is the same as definition frame,
  402.         // PV coordinates are returned directly
  403.         if (outputFrame == frame) {
  404.             return pvCoordinates;
  405.         }

  406.         // Else, PV coordinates are transformed to output frame
  407.         final Transform t = frame.getTransformTo(outputFrame, date);
  408.         return t.transformPVCoordinates(pvCoordinates);
  409.     }

  410.     /** {@inheritDoc} */
  411.     public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate otherDate, final Frame otherFrame) {
  412.         return shiftedBy(otherDate.durationFrom(getDate())).getPVCoordinates(otherFrame);
  413.     }

  414.     /** {@inheritDoc} */
  415.     @Override
  416.     public Vector3D getPosition(final AbsoluteDate otherDate, final Frame otherFrame) {
  417.         return shiftedBy(otherDate.durationFrom(getDate())).getPosition(otherFrame);
  418.     }

  419.     /** Get the position in a specified frame.
  420.      * @param outputFrame frame in which the position coordinates shall be computed
  421.      * @return position in the specified output frame
  422.      * @see #getPosition()
  423.      * @since 12.0
  424.      */
  425.     public Vector3D getPosition(final Frame outputFrame) {
  426.         if (position == null) {
  427.             position = initPosition();
  428.         }

  429.         // If output frame requested is the same as definition frame,
  430.         // Position vector is returned directly
  431.         if (outputFrame == frame) {
  432.             return position;
  433.         }

  434.         // Else, position vector is transformed to output frame
  435.         final StaticTransform t = frame.getStaticTransformTo(outputFrame, date);
  436.         return t.transformPosition(position);

  437.     }

  438.     /** Get the position in definition frame.
  439.      * @return position in the definition frame
  440.      * @see #getPVCoordinates()
  441.      * @since 12.0
  442.      */
  443.     public Vector3D getPosition() {
  444.         if (position == null) {
  445.             position = initPosition();
  446.         }
  447.         return position;
  448.     }

  449.     /** Get the {@link TimeStampedPVCoordinates} in definition frame.
  450.      * @return pvCoordinates in the definition frame
  451.      * @see #getPVCoordinates(Frame)
  452.      */
  453.     public TimeStampedPVCoordinates getPVCoordinates() {
  454.         if (pvCoordinates == null) {
  455.             pvCoordinates = initPVCoordinates();
  456.             position      = pvCoordinates.getPosition();
  457.         }
  458.         return pvCoordinates;
  459.     }

  460.     /** Compute the position coordinates from the canonical parameters.
  461.      * @return computed position coordinates
  462.      * @since 12.0
  463.      */
  464.     protected abstract Vector3D initPosition();

  465.     /** Compute the position/velocity coordinates from the canonical parameters.
  466.      * @return computed position/velocity coordinates
  467.      */
  468.     protected abstract TimeStampedPVCoordinates initPVCoordinates();

  469.     /**
  470.      * Create a new object representing the same physical orbital state, but attached to a different reference frame.
  471.      * If the new frame is not inertial, an exception will be thrown.
  472.      *
  473.      * @param inertialFrame reference frame of output orbit
  474.      * @return orbit with different frame
  475.      * @since 13.0
  476.      */
  477.     public abstract Orbit inFrame(Frame inertialFrame);

  478.     /** Get a time-shifted orbit.
  479.      * <p>
  480.      * The orbit can be slightly shifted to close dates. The shifting model is a
  481.      * Keplerian one if no derivatives are available in the orbit, or Keplerian
  482.      * plus quadratic effect of the non-Keplerian acceleration if derivatives are
  483.      * available. Shifting is <em>not</em> intended as a replacement for proper
  484.      * orbit propagation but should be sufficient for small time shifts or coarse
  485.      * accuracy.
  486.      * </p>
  487.      * @param dt time shift in seconds
  488.      * @return a new orbit, shifted with respect to the instance (which is immutable)
  489.      */
  490.     @Override
  491.     public abstract Orbit shiftedBy(double dt);

  492.     /** Get a time-shifted orbit.
  493.      * <p>
  494.      * The orbit can be slightly shifted to close dates. The shifting model is a
  495.      * Keplerian one if no derivatives are available in the orbit, or Keplerian
  496.      * plus quadratic effect of the non-Keplerian acceleration if derivatives are
  497.      * available. Shifting is <em>not</em> intended as a replacement for proper
  498.      * orbit propagation but should be sufficient for small time shifts or coarse
  499.      * accuracy.
  500.      * </p>
  501.      * @param dt time shift
  502.      * @return a new orbit, shifted with respect to the instance (which is immutable)
  503.      */
  504.     @Override
  505.     public abstract Orbit shiftedBy(TimeOffset dt);

  506.     /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
  507.      * <p>
  508.      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
  509.      * respect to Cartesian coordinate j. This means each row corresponds to one orbital parameter
  510.      * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
  511.      * </p>
  512.      * @param type type of the position angle to use
  513.      * @param jacobian placeholder 6x6 (or larger) matrix to be filled with the Jacobian, if matrix
  514.      * is larger than 6x6, only the 6x6 upper left corner will be modified
  515.      */
  516.     public void getJacobianWrtCartesian(final PositionAngleType type, final double[][] jacobian) {

  517.         final double[][] cachedJacobian;
  518.         synchronized (this) {
  519.             switch (type) {
  520.                 case MEAN :
  521.                     if (jacobianMeanWrtCartesian == null) {
  522.                         // first call, we need to compute the Jacobian and cache it
  523.                         jacobianMeanWrtCartesian = computeJacobianMeanWrtCartesian();
  524.                     }
  525.                     cachedJacobian = jacobianMeanWrtCartesian;
  526.                     break;
  527.                 case ECCENTRIC :
  528.                     if (jacobianEccentricWrtCartesian == null) {
  529.                         // first call, we need to compute the Jacobian and cache it
  530.                         jacobianEccentricWrtCartesian = computeJacobianEccentricWrtCartesian();
  531.                     }
  532.                     cachedJacobian = jacobianEccentricWrtCartesian;
  533.                     break;
  534.                 case TRUE :
  535.                     if (jacobianTrueWrtCartesian == null) {
  536.                         // first call, we need to compute the Jacobian and cache it
  537.                         jacobianTrueWrtCartesian = computeJacobianTrueWrtCartesian();
  538.                     }
  539.                     cachedJacobian = jacobianTrueWrtCartesian;
  540.                     break;
  541.                 default :
  542.                     throw new OrekitInternalError(null);
  543.             }
  544.         }

  545.         // fill the user provided array
  546.         for (int i = 0; i < cachedJacobian.length; ++i) {
  547.             System.arraycopy(cachedJacobian[i], 0, jacobian[i], 0, cachedJacobian[i].length);
  548.         }

  549.     }

  550.     /** Compute the Jacobian of the Cartesian parameters with respect to the orbital parameters.
  551.      * <p>
  552.      * Element {@code jacobian[i][j]} is the derivative of Cartesian coordinate i of the orbit with
  553.      * respect to orbital parameter j. This means each row corresponds to one Cartesian coordinate
  554.      * x, y, z, xdot, ydot, zdot whereas columns 0 to 5 correspond to the orbital parameters.
  555.      * </p>
  556.      * @param type type of the position angle to use
  557.      * @param jacobian placeholder 6x6 (or larger) matrix to be filled with the Jacobian, if matrix
  558.      * is larger than 6x6, only the 6x6 upper left corner will be modified
  559.      */
  560.     public void getJacobianWrtParameters(final PositionAngleType type, final double[][] jacobian) {

  561.         final double[][] cachedJacobian;
  562.         synchronized (this) {
  563.             switch (type) {
  564.                 case MEAN :
  565.                     if (jacobianWrtParametersMean == null) {
  566.                         // first call, we need to compute the Jacobian and cache it
  567.                         jacobianWrtParametersMean = createInverseJacobian(type);
  568.                     }
  569.                     cachedJacobian = jacobianWrtParametersMean;
  570.                     break;
  571.                 case ECCENTRIC :
  572.                     if (jacobianWrtParametersEccentric == null) {
  573.                         // first call, we need to compute the Jacobian and cache it
  574.                         jacobianWrtParametersEccentric = createInverseJacobian(type);
  575.                     }
  576.                     cachedJacobian = jacobianWrtParametersEccentric;
  577.                     break;
  578.                 case TRUE :
  579.                     if (jacobianWrtParametersTrue == null) {
  580.                         // first call, we need to compute the Jacobian and cache it
  581.                         jacobianWrtParametersTrue = createInverseJacobian(type);
  582.                     }
  583.                     cachedJacobian = jacobianWrtParametersTrue;
  584.                     break;
  585.                 default :
  586.                     throw new OrekitInternalError(null);
  587.             }
  588.         }

  589.         // fill the user-provided array
  590.         for (int i = 0; i < cachedJacobian.length; ++i) {
  591.             System.arraycopy(cachedJacobian[i], 0, jacobian[i], 0, cachedJacobian[i].length);
  592.         }

  593.     }

  594.     /** Create an inverse Jacobian.
  595.      * @param type type of the position angle to use
  596.      * @return inverse Jacobian
  597.      */
  598.     private double[][] createInverseJacobian(final PositionAngleType type) {

  599.         // get the direct Jacobian
  600.         final double[][] directJacobian = new double[6][6];
  601.         getJacobianWrtCartesian(type, directJacobian);

  602.         // invert the direct Jacobian
  603.         final RealMatrix matrix = MatrixUtils.createRealMatrix(directJacobian);
  604.         final DecompositionSolver solver = getDecompositionSolver(matrix);
  605.         return solver.getInverse().getData();

  606.     }

  607.     /**
  608.      * Method to build a matrix decomposition solver.
  609.      * @param realMatrix matrix
  610.      * @return solver
  611.      * @since 13.1
  612.      */
  613.     protected DecompositionSolver getDecompositionSolver(final RealMatrix realMatrix) {
  614.         return new QRDecomposition(realMatrix).getSolver();
  615.     }

  616.     /** Compute the Jacobian of the orbital parameters with mean angle with respect to the Cartesian parameters.
  617.      * <p>
  618.      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
  619.      * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter
  620.      * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
  621.      * </p>
  622.      * <p>
  623.      * The array returned by this method will not be modified.
  624.      * </p>
  625.      * @return 6x6 Jacobian matrix
  626.      * @see #computeJacobianEccentricWrtCartesian()
  627.      * @see #computeJacobianTrueWrtCartesian()
  628.      */
  629.     protected abstract double[][] computeJacobianMeanWrtCartesian();

  630.     /** Compute the Jacobian of the orbital parameters with eccentric angle with respect to the Cartesian parameters.
  631.      * <p>
  632.      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
  633.      * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter
  634.      * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
  635.      * </p>
  636.      * <p>
  637.      * The array returned by this method will not be modified.
  638.      * </p>
  639.      * @return 6x6 Jacobian matrix
  640.      * @see #computeJacobianMeanWrtCartesian()
  641.      * @see #computeJacobianTrueWrtCartesian()
  642.      */
  643.     protected abstract double[][] computeJacobianEccentricWrtCartesian();

  644.     /** Compute the Jacobian of the orbital parameters with true angle with respect to the Cartesian parameters.
  645.      * <p>
  646.      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
  647.      * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter
  648.      * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
  649.      * </p>
  650.      * <p>
  651.      * The array returned by this method will not be modified.
  652.      * </p>
  653.      * @return 6x6 Jacobian matrix
  654.      * @see #computeJacobianMeanWrtCartesian()
  655.      * @see #computeJacobianEccentricWrtCartesian()
  656.      */
  657.     protected abstract double[][] computeJacobianTrueWrtCartesian();

  658.     /** Add the contribution of the Keplerian motion to parameters derivatives
  659.      * <p>
  660.      * This method is used by integration-based propagators to evaluate the part of Keplerian
  661.      * motion to evolution of the orbital state.
  662.      * </p>
  663.      * @param type type of the position angle in the state
  664.      * @param gm attraction coefficient to use
  665.      * @param pDot array containing orbital state derivatives to update (the Keplerian
  666.      * part must be <em>added</em> to the array components, as the array may already
  667.      * contain some non-zero elements corresponding to non-Keplerian parts)
  668.      */
  669.     public abstract void addKeplerContribution(PositionAngleType type, double gm, double[] pDot);

  670.         /** Fill a Jacobian half row with a single vector.
  671.      * @param a coefficient of the vector
  672.      * @param v vector
  673.      * @param row Jacobian matrix row
  674.      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
  675.      */
  676.     protected static void fillHalfRow(final double a, final Vector3D v, final double[] row, final int j) {
  677.         row[j]     = a * v.getX();
  678.         row[j + 1] = a * v.getY();
  679.         row[j + 2] = a * v.getZ();
  680.     }

  681.     /** Fill a Jacobian half row with a linear combination of vectors.
  682.      * @param a1 coefficient of the first vector
  683.      * @param v1 first vector
  684.      * @param a2 coefficient of the second vector
  685.      * @param v2 second vector
  686.      * @param row Jacobian matrix row
  687.      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
  688.      */
  689.     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
  690.                                       final double[] row, final int j) {
  691.         row[j]     = MathArrays.linearCombination(a1, v1.getX(), a2, v2.getX());
  692.         row[j + 1] = MathArrays.linearCombination(a1, v1.getY(), a2, v2.getY());
  693.         row[j + 2] = MathArrays.linearCombination(a1, v1.getZ(), a2, v2.getZ());
  694.     }

  695.     /** Fill a Jacobian half row with a linear combination of vectors.
  696.      * @param a1 coefficient of the first vector
  697.      * @param v1 first vector
  698.      * @param a2 coefficient of the second vector
  699.      * @param v2 second vector
  700.      * @param a3 coefficient of the third vector
  701.      * @param v3 third vector
  702.      * @param row Jacobian matrix row
  703.      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
  704.      */
  705.     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
  706.                                       final double a3, final Vector3D v3,
  707.                                       final double[] row, final int j) {
  708.         row[j]     = MathArrays.linearCombination(a1, v1.getX(), a2, v2.getX(), a3, v3.getX());
  709.         row[j + 1] = MathArrays.linearCombination(a1, v1.getY(), a2, v2.getY(), a3, v3.getY());
  710.         row[j + 2] = MathArrays.linearCombination(a1, v1.getZ(), a2, v2.getZ(), a3, v3.getZ());
  711.     }

  712.     /** Fill a Jacobian half row with a linear combination of vectors.
  713.      * @param a1 coefficient of the first vector
  714.      * @param v1 first vector
  715.      * @param a2 coefficient of the second vector
  716.      * @param v2 second vector
  717.      * @param a3 coefficient of the third vector
  718.      * @param v3 third vector
  719.      * @param a4 coefficient of the fourth vector
  720.      * @param v4 fourth vector
  721.      * @param row Jacobian matrix row
  722.      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
  723.      */
  724.     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
  725.                                       final double a3, final Vector3D v3, final double a4, final Vector3D v4,
  726.                                       final double[] row, final int j) {
  727.         row[j]     = MathArrays.linearCombination(a1, v1.getX(), a2, v2.getX(), a3, v3.getX(), a4, v4.getX());
  728.         row[j + 1] = MathArrays.linearCombination(a1, v1.getY(), a2, v2.getY(), a3, v3.getY(), a4, v4.getY());
  729.         row[j + 2] = MathArrays.linearCombination(a1, v1.getZ(), a2, v2.getZ(), a3, v3.getZ(), a4, v4.getZ());
  730.     }

  731.     /** Fill a Jacobian half row with a linear combination of vectors.
  732.      * @param a1 coefficient of the first vector
  733.      * @param v1 first vector
  734.      * @param a2 coefficient of the second vector
  735.      * @param v2 second vector
  736.      * @param a3 coefficient of the third vector
  737.      * @param v3 third vector
  738.      * @param a4 coefficient of the fourth vector
  739.      * @param v4 fourth vector
  740.      * @param a5 coefficient of the fifth vector
  741.      * @param v5 fifth vector
  742.      * @param row Jacobian matrix row
  743.      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
  744.      */
  745.     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
  746.                                       final double a3, final Vector3D v3, final double a4, final Vector3D v4,
  747.                                       final double a5, final Vector3D v5,
  748.                                       final double[] row, final int j) {
  749.         final double[] a = new double[] {
  750.             a1, a2, a3, a4, a5
  751.         };
  752.         row[j]     = MathArrays.linearCombination(a, new double[] {
  753.             v1.getX(), v2.getX(), v3.getX(), v4.getX(), v5.getX()
  754.         });
  755.         row[j + 1] = MathArrays.linearCombination(a, new double[] {
  756.             v1.getY(), v2.getY(), v3.getY(), v4.getY(), v5.getY()
  757.         });
  758.         row[j + 2] = MathArrays.linearCombination(a, new double[] {
  759.             v1.getZ(), v2.getZ(), v3.getZ(), v4.getZ(), v5.getZ()
  760.         });
  761.     }

  762.     /** Fill a Jacobian half row with a linear combination of vectors.
  763.      * @param a1 coefficient of the first vector
  764.      * @param v1 first vector
  765.      * @param a2 coefficient of the second vector
  766.      * @param v2 second vector
  767.      * @param a3 coefficient of the third vector
  768.      * @param v3 third vector
  769.      * @param a4 coefficient of the fourth vector
  770.      * @param v4 fourth vector
  771.      * @param a5 coefficient of the fifth vector
  772.      * @param v5 fifth vector
  773.      * @param a6 coefficient of the sixth vector
  774.      * @param v6 sixth vector
  775.      * @param row Jacobian matrix row
  776.      * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
  777.      */
  778.     protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2,
  779.                                       final double a3, final Vector3D v3, final double a4, final Vector3D v4,
  780.                                       final double a5, final Vector3D v5, final double a6, final Vector3D v6,
  781.                                       final double[] row, final int j) {
  782.         final double[] a = new double[] {
  783.             a1, a2, a3, a4, a5, a6
  784.         };
  785.         row[j]     = MathArrays.linearCombination(a, new double[] {
  786.             v1.getX(), v2.getX(), v3.getX(), v4.getX(), v5.getX(), v6.getX()
  787.         });
  788.         row[j + 1] = MathArrays.linearCombination(a, new double[] {
  789.             v1.getY(), v2.getY(), v3.getY(), v4.getY(), v5.getY(), v6.getY()
  790.         });
  791.         row[j + 2] = MathArrays.linearCombination(a, new double[] {
  792.             v1.getZ(), v2.getZ(), v3.getZ(), v4.getZ(), v5.getZ(), v6.getZ()
  793.         });
  794.     }

  795. }