KeplerianAnomalyUtility.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.exception.MathIllegalStateException;
  19. import org.hipparchus.util.FastMath;
  20. import org.hipparchus.util.MathUtils;
  21. import org.hipparchus.util.SinCos;
  22. import org.orekit.errors.OrekitInternalError;
  23. import org.orekit.errors.OrekitMessages;

  24. /**
  25.  * Utility methods for converting between different Keplerian anomalies.
  26.  * @author Luc Maisonobe
  27.  * @author Andrew Goetz
  28.  */
  29. public final class KeplerianAnomalyUtility {

  30.     /** First coefficient to compute elliptic Kepler equation solver starter. */
  31.     private static final double A;

  32.     /** Second coefficient to compute elliptic Kepler equation solver starter. */
  33.     private static final double B;

  34.     static {
  35.         final double k1 = 3 * FastMath.PI + 2;
  36.         final double k2 = FastMath.PI - 1;
  37.         final double k3 = 6 * FastMath.PI - 1;
  38.         A = 3 * k2 * k2 / k1;
  39.         B = k3 * k3 / (6 * k1);
  40.     }

  41.     /** Private constructor for utility class. */
  42.     private KeplerianAnomalyUtility() {
  43.         // Nothing to do
  44.     }

  45.     /**
  46.      * Computes the elliptic true anomaly from the elliptic mean anomaly.
  47.      * @param e eccentricity such that 0 ≤ e < 1
  48.      * @param M elliptic mean anomaly (rad)
  49.      * @return elliptic true anomaly (rad)
  50.      */
  51.     public static double ellipticMeanToTrue(final double e, final double M) {
  52.         final double E = ellipticMeanToEccentric(e, M);
  53.         final double v = ellipticEccentricToTrue(e, E);
  54.         return v;
  55.     }

  56.     /**
  57.      * Computes the elliptic mean anomaly from the elliptic true anomaly.
  58.      * @param e eccentricity such that 0 ≤ e < 1
  59.      * @param v elliptic true anomaly (rad)
  60.      * @return elliptic mean anomaly (rad)
  61.      */
  62.     public static double ellipticTrueToMean(final double e, final double v) {
  63.         final double E = ellipticTrueToEccentric(e, v);
  64.         final double M = ellipticEccentricToMean(e, E);
  65.         return M;
  66.     }

  67.     /**
  68.      * Computes the elliptic true anomaly from the elliptic eccentric anomaly.
  69.      * @param e eccentricity such that 0 ≤ e < 1
  70.      * @param E elliptic eccentric anomaly (rad)
  71.      * @return elliptic true anomaly (rad)
  72.      */
  73.     public static double ellipticEccentricToTrue(final double e, final double E) {
  74.         final double beta = e / (1 + FastMath.sqrt((1 - e) * (1 + e)));
  75.         final SinCos scE = FastMath.sinCos(E);
  76.         return E + 2 * FastMath.atan(beta * scE.sin() / (1 - beta * scE.cos()));
  77.     }

  78.     /**
  79.      * Computes the elliptic eccentric anomaly from the elliptic true anomaly.
  80.      * @param e eccentricity such that 0 ≤ e < 1
  81.      * @param v elliptic true anomaly (rad)
  82.      * @return elliptic eccentric anomaly (rad)
  83.      */
  84.     public static double ellipticTrueToEccentric(final double e, final double v) {
  85.         final double beta = e / (1 + FastMath.sqrt(1 - e * e));
  86.         final SinCos scv = FastMath.sinCos(v);
  87.         return v - 2 * FastMath.atan(beta * scv.sin() / (1 + beta * scv.cos()));
  88.     }

  89.     /**
  90.      * Computes the elliptic eccentric anomaly from the elliptic mean anomaly.
  91.      * <p>
  92.      * The algorithm used here for solving hyperbolic Kepler equation is from Odell,
  93.      * A.W., Gooding, R.H. "Procedures for solving Kepler's equation." Celestial
  94.      * Mechanics 38, 307–334 (1986). https://doi.org/10.1007/BF01238923
  95.      * </p>
  96.      * @param e eccentricity such that 0 &le; e &lt; 1
  97.      * @param M elliptic mean anomaly (rad)
  98.      * @return elliptic eccentric anomaly (rad)
  99.      */
  100.     public static double ellipticMeanToEccentric(final double e, final double M) {
  101.         // reduce M to [-PI PI) interval
  102.         final double reducedM = MathUtils.normalizeAngle(M, 0.0);

  103.         // compute start value according to A. W. Odell and R. H. Gooding S12 starter
  104.         double E;
  105.         if (FastMath.abs(reducedM) < 1.0 / 6.0) {
  106.             E = reducedM + e * (FastMath.cbrt(6 * reducedM) - reducedM);
  107.         } else {
  108.             if (reducedM < 0) {
  109.                 final double w = FastMath.PI + reducedM;
  110.                 E = reducedM + e * (A * w / (B - w) - FastMath.PI - reducedM);
  111.             } else {
  112.                 final double w = FastMath.PI - reducedM;
  113.                 E = reducedM + e * (FastMath.PI - A * w / (B - w) - reducedM);
  114.             }
  115.         }

  116.         final double e1 = 1 - e;
  117.         final boolean noCancellationRisk = (e1 + E * E / 6) >= 0.1;

  118.         // perform two iterations, each consisting of one Halley step and one
  119.         // Newton-Raphson step
  120.         for (int j = 0; j < 2; ++j) {
  121.             final double f;
  122.             double fd;
  123.             final SinCos sc = FastMath.sinCos(E);
  124.             final double fdd = e * sc.sin();
  125.             final double fddd = e * sc.cos();
  126.             if (noCancellationRisk) {
  127.                 f = (E - fdd) - reducedM;
  128.                 fd = 1 - fddd;
  129.             } else {
  130.                 f = eMeSinE(e, E) - reducedM;
  131.                 final double s = FastMath.sin(0.5 * E);
  132.                 fd = e1 + 2 * e * s * s;
  133.             }
  134.             final double dee = f * fd / (0.5 * f * fdd - fd * fd);

  135.             // update eccentric anomaly, using expressions that limit underflow problems
  136.             final double w = fd + 0.5 * dee * (fdd + dee * fddd / 3);
  137.             fd += dee * (fdd + 0.5 * dee * fddd);
  138.             E -= (f - dee * (fd - w)) / fd;

  139.         }

  140.         // expand the result back to original range
  141.         E += M - reducedM;

  142.         return E;
  143.     }

  144.     /**
  145.      * Accurate computation of E - e sin(E).
  146.      * <p>
  147.      * This method is used when E is close to 0 and e close to 1, i.e. near the
  148.      * perigee of almost parabolic orbits
  149.      * </p>
  150.      * @param e eccentricity
  151.      * @param E eccentric anomaly (rad)
  152.      * @return E - e sin(E)
  153.      */
  154.     private static double eMeSinE(final double e, final double E) {
  155.         double x = (1 - e) * FastMath.sin(E);
  156.         final double mE2 = -E * E;
  157.         double term = E;
  158.         double d = 0;
  159.         // the inequality test below IS intentional and should NOT be replaced by a
  160.         // check with a small tolerance
  161.         for (double x0 = Double.NaN; !Double.valueOf(x).equals(x0);) {
  162.             d += 2;
  163.             term *= mE2 / (d * (d + 1));
  164.             x0 = x;
  165.             x = x - term;
  166.         }
  167.         return x;
  168.     }

  169.     /**
  170.      * Computes the elliptic mean anomaly from the elliptic eccentric anomaly.
  171.      * @param e eccentricity such that 0 &le; e &lt; 1
  172.      * @param E elliptic eccentric anomaly (rad)
  173.      * @return elliptic mean anomaly (rad)
  174.      */
  175.     public static double ellipticEccentricToMean(final double e, final double E) {
  176.         return E - e * FastMath.sin(E);
  177.     }

  178.     /**
  179.      * Computes the hyperbolic true anomaly from the hyperbolic mean anomaly.
  180.      * @param e eccentricity &gt; 1
  181.      * @param M hyperbolic mean anomaly
  182.      * @return hyperbolic true anomaly (rad)
  183.      */
  184.     public static double hyperbolicMeanToTrue(final double e, final double M) {
  185.         final double H = hyperbolicMeanToEccentric(e, M);
  186.         final double v = hyperbolicEccentricToTrue(e, H);
  187.         return v;
  188.     }

  189.     /**
  190.      * Computes the hyperbolic mean anomaly from the hyperbolic true anomaly.
  191.      * @param e eccentricity &gt; 1
  192.      * @param v hyperbolic true anomaly (rad)
  193.      * @return hyperbolic mean anomaly
  194.      */
  195.     public static double hyperbolicTrueToMean(final double e, final double v) {
  196.         final double H = hyperbolicTrueToEccentric(e, v);
  197.         final double M = hyperbolicEccentricToMean(e, H);
  198.         return M;
  199.     }

  200.     /**
  201.      * Computes the hyperbolic true anomaly from the hyperbolic eccentric anomaly.
  202.      * @param e eccentricity &gt; 1
  203.      * @param H hyperbolic eccentric anomaly
  204.      * @return hyperbolic true anomaly (rad)
  205.      */
  206.     public static double hyperbolicEccentricToTrue(final double e, final double H) {
  207.         return 2 * FastMath.atan(FastMath.sqrt((e + 1) / (e - 1)) * FastMath.tanh(0.5 * H));
  208.     }

  209.     /**
  210.      * Computes the hyperbolic eccentric anomaly from the hyperbolic true anomaly.
  211.      * @param e eccentricity &gt; 1
  212.      * @param v hyperbolic true anomaly (rad)
  213.      * @return hyperbolic eccentric anomaly
  214.      */
  215.     public static double hyperbolicTrueToEccentric(final double e, final double v) {
  216.         final SinCos scv = FastMath.sinCos(v);
  217.         final double sinhH = FastMath.sqrt(e * e - 1) * scv.sin() / (1 + e * scv.cos());
  218.         return FastMath.asinh(sinhH);
  219.     }

  220.     /**
  221.      * Computes the hyperbolic eccentric anomaly from the hyperbolic mean anomaly.
  222.      * <p>
  223.      * The algorithm used here for solving hyperbolic Kepler equation is from
  224.      * Gooding, R.H., Odell, A.W. "The hyperbolic Kepler equation (and the elliptic
  225.      * equation revisited)." Celestial Mechanics 44, 267–282 (1988).
  226.      * https://doi.org/10.1007/BF01235540
  227.      * </p>
  228.      * @param e eccentricity &gt; 1
  229.      * @param M hyperbolic mean anomaly
  230.      * @return hyperbolic eccentric anomaly
  231.      */
  232.     public static double hyperbolicMeanToEccentric(final double e, final double M) {
  233.         // Solve L = S - g * asinh(S) for S = sinh(H).
  234.         final double L = M / e;
  235.         final double g = 1.0 / e;
  236.         final double g1 = 1.0 - g;

  237.         // Starter based on Lagrange's theorem.
  238.         double S = L;
  239.         if (L == 0.0) {
  240.             return 0.0;
  241.         }
  242.         final double cl = FastMath.sqrt(1.0 + L * L);
  243.         final double al = FastMath.asinh(L);
  244.         final double w = g * g * al / (cl * cl * cl);
  245.         S = 1.0 - g / cl;
  246.         S = L + g * al / FastMath.cbrt(S * S * S + w * L * (1.5 - (4.0 / 3.0) * g));

  247.         // Two iterations (at most) of Halley-then-Newton process.
  248.         for (int i = 0; i < 2; ++i) {
  249.             final double s0 = S * S;
  250.             final double s1 = s0 + 1.0;
  251.             final double s2 = FastMath.sqrt(s1);
  252.             final double s3 = s1 * s2;
  253.             final double fdd = g * S / s3;
  254.             final double fddd = g * (1.0 - 2.0 * s0) / (s1 * s3);

  255.             double f;
  256.             double fd;
  257.             if (s0 / 6.0 + g1 >= 0.5) {
  258.                 f = (S - g * FastMath.asinh(S)) - L;
  259.                 fd = 1.0 - g / s2;
  260.             } else {
  261.                 // Accurate computation of S - (1 - g1) * asinh(S)
  262.                 // when (g1, S) is close to (0, 0).
  263.                 final double t = S / (1.0 + FastMath.sqrt(1.0 + S * S));
  264.                 final double tsq = t * t;
  265.                 double x = S * (g1 + g * tsq);
  266.                 double term = 2.0 * g * t;
  267.                 double twoI1 = 1.0;
  268.                 double x0;
  269.                 int j = 0;
  270.                 do {
  271.                     if (++j == 1000000) {
  272.                         // This isn't expected to happen, but it protects against an infinite loop.
  273.                         throw new MathIllegalStateException(
  274.                                 OrekitMessages.UNABLE_TO_COMPUTE_HYPERBOLIC_ECCENTRIC_ANOMALY, j);
  275.                     }
  276.                     twoI1 += 2.0;
  277.                     term *= tsq;
  278.                     x0 = x;
  279.                     x -= term / twoI1;
  280.                 } while (x != x0);
  281.                 f = x - L;
  282.                 fd = (s0 / (s2 + 1.0) + g1) / s2;
  283.             }
  284.             final double ds = f * fd / (0.5 * f * fdd - fd * fd);
  285.             final double stemp = S + ds;
  286.             if (S == stemp) {
  287.                 break;
  288.             }
  289.             f += ds * (fd + 0.5 * ds * (fdd + ds / 3.0 * fddd));
  290.             fd += ds * (fdd + 0.5 * ds * fddd);
  291.             S = stemp - f / fd;
  292.         }

  293.         final double H = FastMath.asinh(S);
  294.         return H;
  295.     }

  296.     /**
  297.      * Computes the hyperbolic mean anomaly from the hyperbolic eccentric anomaly.
  298.      * @param e eccentricity &gt; 1
  299.      * @param H hyperbolic eccentric anomaly
  300.      * @return hyperbolic mean anomaly
  301.      */
  302.     public static double hyperbolicEccentricToMean(final double e, final double H) {
  303.         return e * FastMath.sinh(H) - H;
  304.     }

  305.     /**
  306.      * Convert anomaly.
  307.      * @param oldType old position angle type
  308.      * @param anomaly old value for anomaly
  309.      * @param e eccentricity
  310.      * @param newType new position angle type
  311.      * @return converted anomaly
  312.      * @since 12.2
  313.      */
  314.     public static double convertAnomaly(final PositionAngleType oldType, final double anomaly, final double e,
  315.                                         final PositionAngleType newType) {
  316.         if (newType == oldType) {
  317.             return anomaly;

  318.         } else {
  319.             if (e > 1) {
  320.                 switch (newType) {
  321.                     case MEAN:
  322.                         if (oldType == PositionAngleType.ECCENTRIC) {
  323.                             return KeplerianAnomalyUtility.hyperbolicEccentricToMean(e, anomaly);
  324.                         } else {
  325.                             return KeplerianAnomalyUtility.hyperbolicTrueToMean(e, anomaly);
  326.                         }

  327.                     case ECCENTRIC:
  328.                         if (oldType == PositionAngleType.MEAN) {
  329.                             return KeplerianAnomalyUtility.hyperbolicMeanToEccentric(e, anomaly);
  330.                         } else {
  331.                             return KeplerianAnomalyUtility.hyperbolicTrueToEccentric(e, anomaly);
  332.                         }

  333.                     case TRUE:
  334.                         if (oldType == PositionAngleType.ECCENTRIC) {
  335.                             return KeplerianAnomalyUtility.hyperbolicEccentricToTrue(e, anomaly);
  336.                         } else {
  337.                             return KeplerianAnomalyUtility.hyperbolicMeanToTrue(e, anomaly);
  338.                         }

  339.                     default:
  340.                         break;
  341.                 }

  342.             } else {
  343.                 switch (newType) {
  344.                     case MEAN:
  345.                         if (oldType == PositionAngleType.ECCENTRIC) {
  346.                             return KeplerianAnomalyUtility.ellipticEccentricToMean(e, anomaly);
  347.                         } else {
  348.                             return KeplerianAnomalyUtility.ellipticTrueToMean(e, anomaly);
  349.                         }

  350.                     case ECCENTRIC:
  351.                         if (oldType == PositionAngleType.MEAN) {
  352.                             return KeplerianAnomalyUtility.ellipticMeanToEccentric(e, anomaly);
  353.                         } else {
  354.                             return KeplerianAnomalyUtility.ellipticTrueToEccentric(e, anomaly);
  355.                         }

  356.                     case TRUE:
  357.                         if (oldType == PositionAngleType.ECCENTRIC) {
  358.                             return KeplerianAnomalyUtility.ellipticEccentricToTrue(e, anomaly);
  359.                         } else {
  360.                             return KeplerianAnomalyUtility.ellipticMeanToTrue(e, anomaly);
  361.                         }

  362.                     default:
  363.                         break;
  364.                 }
  365.             }
  366.             throw new OrekitInternalError(null);
  367.         }
  368.     }
  369. }