1   /* Copyright 2002-2022 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.orbits;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.exception.MathIllegalStateException;
22  import org.hipparchus.util.FastMath;
23  import org.hipparchus.util.FieldSinCos;
24  import org.hipparchus.util.MathUtils;
25  import org.hipparchus.util.Precision;
26  import org.orekit.errors.OrekitMessages;
27  
28  /**
29   * Utility methods for converting between different Keplerian anomalies.
30   * @author Luc Maisonobe
31   * @author Andrea Antolino
32   * @author Andrew Goetz
33   */
34  public class FieldKeplerianAnomalyUtility {
35  
36      /** First coefficient to compute elliptic Kepler equation solver starter. */
37      private static final double A;
38  
39      /** Second coefficient to compute elliptic Kepler equation solver starter. */
40      private static final double B;
41  
42      static {
43          final double k1 = 3 * FastMath.PI + 2;
44          final double k2 = FastMath.PI - 1;
45          final double k3 = 6 * FastMath.PI - 1;
46          A = 3 * k2 * k2 / k1;
47          B = k3 * k3 / (6 * k1);
48      }
49  
50      private FieldKeplerianAnomalyUtility() {
51      }
52  
53      /**
54       * Computes the elliptic true anomaly from the elliptic mean anomaly.
55       * @param <T> field type
56       * @param e eccentricity such that 0 &le; e &lt; 1
57       * @param M elliptic mean anomaly (rad)
58       * @return elliptic true anomaly (rad)
59       */
60      public static <T extends CalculusFieldElement<T>> T ellipticMeanToTrue(final T e, final T M) {
61          final T E = ellipticMeanToEccentric(e, M);
62          final T v = ellipticEccentricToTrue(e, E);
63          return v;
64      }
65  
66      /**
67       * Computes the elliptic mean anomaly from the elliptic true anomaly.
68       * @param <T> field type
69       * @param e eccentricity such that 0 &le; e &lt; 1
70       * @param v elliptic true anomaly (rad)
71       * @return elliptic mean anomaly (rad)
72       */
73      public static <T extends CalculusFieldElement<T>> T ellipticTrueToMean(final T e, final T v) {
74          final T E = ellipticTrueToEccentric(e, v);
75          final T M = ellipticEccentricToMean(e, E);
76          return M;
77      }
78  
79      /**
80       * Computes the elliptic true anomaly from the elliptic eccentric anomaly.
81       * @param <T> field type
82       * @param e eccentricity such that 0 &le; e &lt; 1
83       * @param E elliptic eccentric anomaly (rad)
84       * @return elliptic true anomaly (rad)
85       */
86      public static <T extends CalculusFieldElement<T>> T ellipticEccentricToTrue(final T e, final T E) {
87          final T beta = e.divide(e.multiply(e).negate().add(1).sqrt().add(1));
88          final FieldSinCos<T> scE = FastMath.sinCos(E);
89          return E.add(beta.multiply(scE.sin()).divide(beta.multiply(scE.cos()).subtract(1).negate()).atan().multiply(2));
90      }
91  
92      /**
93       * Computes the elliptic eccentric anomaly from the elliptic true anomaly.
94       * @param <T> field type
95       * @param e eccentricity such that 0 &le; e &lt; 1
96       * @param v elliptic true anomaly (rad)
97       * @return elliptic eccentric anomaly (rad)
98       */
99      public static <T extends CalculusFieldElement<T>> T ellipticTrueToEccentric(final T e, final T v) {
100         final T beta = e.divide(e.multiply(e).negate().add(1).sqrt().add(1));
101         final FieldSinCos<T> scv = FastMath.sinCos(v);
102         return v.subtract((beta.multiply(scv.sin()).divide(beta.multiply(scv.cos()).add(1))).atan().multiply(2));
103     }
104 
105     /**
106      * Computes the elliptic eccentric anomaly from the elliptic mean anomaly.
107      * <p>
108      * The algorithm used here for solving hyperbolic Kepler equation is from Odell,
109      * A.W., Gooding, R.H. "Procedures for solving Kepler's equation." Celestial
110      * Mechanics 38, 307–334 (1986). https://doi.org/10.1007/BF01238923
111      * </p>
112      * @param <T> field type
113      * @param e eccentricity such that 0 &le; e &lt; 1
114      * @param M elliptic mean anomaly (rad)
115      * @return elliptic eccentric anomaly (rad)
116      */
117     public static <T extends CalculusFieldElement<T>> T ellipticMeanToEccentric(final T e, final T M) {
118         // reduce M to [-PI PI) interval
119         final T reducedM = MathUtils.normalizeAngle(M, M.getField().getZero());
120 
121         // compute start value according to A. W. Odell and R. H. Gooding S12 starter
122         T E;
123         if (reducedM.abs().getReal() < 1.0 / 6.0) {
124             if (FastMath.abs(reducedM.getReal()) < Precision.SAFE_MIN) {
125                 // this is an Orekit change to the S12 starter, mainly used when T is some kind
126                 // of derivative structure. If reducedM is 0.0, the derivative of cbrt is
127                 // infinite which induces NaN appearing later in the computation. As in this
128                 // case E and M are almost equal, we initialize E with reducedM
129                 E = reducedM;
130             } else {
131                 // this is the standard S12 starter
132                 E = reducedM.add(e.multiply((reducedM.multiply(6).cbrt()).subtract(reducedM)));
133             }
134         } else {
135             final T pi = e.getPi();
136             if (reducedM.getReal() < 0) {
137                 final T w = reducedM.add(pi);
138                 E = reducedM.add(e.multiply(w.multiply(A).divide(w.negate().add(B)).subtract(pi).subtract(reducedM)));
139             } else {
140                 final T w = reducedM.negate().add(pi);
141                 E = reducedM
142                         .add(e.multiply(w.multiply(A).divide(w.negate().add(B)).negate().subtract(reducedM).add(pi)));
143             }
144         }
145 
146         final T e1 = e.negate().add(1);
147         final boolean noCancellationRisk = (e1.getReal() + E.getReal() * E.getReal() / 6) >= 0.1;
148 
149         // perform two iterations, each consisting of one Halley step and one
150         // Newton-Raphson step
151         for (int j = 0; j < 2; ++j) {
152 
153             final T f;
154             T fd;
155             final FieldSinCos<T> scE = FastMath.sinCos(E);
156             final T fdd = e.multiply(scE.sin());
157             final T fddd = e.multiply(scE.cos());
158 
159             if (noCancellationRisk) {
160 
161                 f = (E.subtract(fdd)).subtract(reducedM);
162                 fd = fddd.negate().add(1);
163             } else {
164 
165                 f = eMeSinE(e, E).subtract(reducedM);
166                 final T s = E.multiply(0.5).sin();
167                 fd = e1.add(e.multiply(s).multiply(s).multiply(2));
168             }
169             final T dee = f.multiply(fd).divide(f.multiply(fdd).multiply(0.5).subtract(fd.multiply(fd)));
170 
171             // update eccentric anomaly, using expressions that limit underflow problems
172             final T w = fd.add(dee.multiply(fdd.add(dee.multiply(fddd.divide(3)))).multiply(0.5));
173             fd = fd.add(dee.multiply(fdd.add(dee.multiply(fddd).multiply(0.5))));
174             E = E.subtract(f.subtract(dee.multiply(fd.subtract(w))).divide(fd));
175 
176         }
177 
178         // expand the result back to original range
179         E = E.add(M).subtract(reducedM);
180         return E;
181     }
182 
183     /**
184      * Accurate computation of E - e sin(E).
185      * <p>
186      * This method is used when E is close to 0 and e close to 1, i.e. near the
187      * perigee of almost parabolic orbits
188      * </p>
189      * @param <T> field type
190      * @param e eccentricity
191      * @param E eccentric anomaly (rad)
192      * @return E - e sin(E)
193      */
194     private static <T extends CalculusFieldElement<T>> T eMeSinE(final T e, final T E) {
195         T x = (e.negate().add(1)).multiply(E.sin());
196         final T mE2 = E.negate().multiply(E);
197         T term = E;
198         double d = 0;
199         // the inequality test below IS intentional and should NOT be replaced by a
200         // check with a small tolerance
201         for (T x0 = E.getField().getZero().add(Double.NaN); !Double.valueOf(x.getReal())
202                 .equals(Double.valueOf(x0.getReal()));) {
203             d += 2;
204             term = term.multiply(mE2.divide(d * (d + 1)));
205             x0 = x;
206             x = x.subtract(term);
207         }
208         return x;
209     }
210 
211     /**
212      * Computes the elliptic mean anomaly from the elliptic eccentric anomaly.
213      * @param <T> field type
214      * @param e eccentricity such that 0 &le; e &lt; 1
215      * @param E elliptic eccentric anomaly (rad)
216      * @return elliptic mean anomaly (rad)
217      */
218     public static <T extends CalculusFieldElement<T>> T ellipticEccentricToMean(final T e, final T E) {
219         return E.subtract(e.multiply(E.sin()));
220     }
221 
222     /**
223      * Computes the hyperbolic true anomaly from the hyperbolic mean anomaly.
224      * @param <T> field type
225      * @param e eccentricity &gt; 1
226      * @param M hyperbolic mean anomaly
227      * @return hyperbolic true anomaly (rad)
228      */
229     public static <T extends CalculusFieldElement<T>> T hyperbolicMeanToTrue(final T e, final T M) {
230         final T H = hyperbolicMeanToEccentric(e, M);
231         final T v = hyperbolicEccentricToTrue(e, H);
232         return v;
233     }
234 
235     /**
236      * Computes the hyperbolic mean anomaly from the hyperbolic true anomaly.
237      * @param <T> field type
238      * @param e eccentricity &gt; 1
239      * @param v hyperbolic true anomaly (rad)
240      * @return hyperbolic mean anomaly
241      */
242     public static <T extends CalculusFieldElement<T>> T hyperbolicTrueToMean(final T e, final T v) {
243         final T H = hyperbolicTrueToEccentric(e, v);
244         final T M = hyperbolicEccentricToMean(e, H);
245         return M;
246     }
247 
248     /**
249      * Computes the hyperbolic true anomaly from the hyperbolic eccentric anomaly.
250      * @param <T> field type
251      * @param e eccentricity &gt; 1
252      * @param H hyperbolic eccentric anomaly
253      * @return hyperbolic true anomaly (rad)
254      */
255     public static <T extends CalculusFieldElement<T>> T hyperbolicEccentricToTrue(final T e, final T H) {
256         final T s = e.add(1).divide(e.subtract(1)).sqrt();
257         final T tanH = H.multiply(0.5).tanh();
258         return s.multiply(tanH).atan().multiply(2);
259     }
260 
261     /**
262      * Computes the hyperbolic eccentric anomaly from the hyperbolic true anomaly.
263      * @param <T> field type
264      * @param e eccentricity &gt; 1
265      * @param v hyperbolic true anomaly (rad)
266      * @return hyperbolic eccentric anomaly
267      */
268     public static <T extends CalculusFieldElement<T>> T hyperbolicTrueToEccentric(final T e, final T v) {
269         final FieldSinCos<T> scv = FastMath.sinCos(v);
270         final T sinhH = e.multiply(e).subtract(1).sqrt().multiply(scv.sin()).divide(e.multiply(scv.cos()).add(1));
271         return sinhH.asinh();
272     }
273 
274     /**
275      * Computes the hyperbolic eccentric anomaly from the hyperbolic mean anomaly.
276      * <p>
277      * The algorithm used here for solving hyperbolic Kepler equation is from
278      * Gooding, R.H., Odell, A.W. "The hyperbolic Kepler equation (and the elliptic
279      * equation revisited)." Celestial Mechanics 44, 267–282 (1988).
280      * https://doi.org/10.1007/BF01235540
281      * </p>
282      * @param <T> field type
283      * @param e eccentricity &gt; 1
284      * @param M hyperbolic mean anomaly
285      * @return hyperbolic eccentric anomaly
286      */
287     public static <T extends CalculusFieldElement<T>> T hyperbolicMeanToEccentric(final T e, final T M) {
288         final Field<T> field = e.getField();
289         final T zero = field.getZero();
290         final T one = field.getOne();
291         final T two = zero.add(2.0);
292         final T three = zero.add(3.0);
293         final T half = zero.add(0.5);
294         final T onePointFive = zero.add(1.5);
295         final T fourThirds = zero.add(4.0).divide(zero.add(3.0));
296 
297         // Solve L = S - g * asinh(S) for S = sinh(H).
298         final T L = M.divide(e);
299         final T g = e.reciprocal();
300         final T g1 = one.subtract(g);
301 
302         // Starter based on Lagrange's theorem.
303         T S = L;
304         if (L.isZero()) {
305             return M.getField().getZero();
306         }
307         final T cl = L.multiply(L).add(one).sqrt();
308         final T al = L.asinh();
309         final T w = g.multiply(g).multiply(al).divide(cl.multiply(cl).multiply(cl));
310         S = one.subtract(g.divide(cl));
311         S = L.add(g.multiply(al).divide(S.multiply(S).multiply(S)
312                 .add(w.multiply(L).multiply(onePointFive.subtract(fourThirds.multiply(g)))).cbrt()));
313 
314         // Two iterations (at most) of Halley-then-Newton process.
315         for (int i = 0; i < 2; ++i) {
316             final T s0 = S.multiply(S);
317             final T s1 = s0.add(one);
318             final T s2 = s1.sqrt();
319             final T s3 = s1.multiply(s2);
320             final T fdd = g.multiply(S).divide(s3);
321             final T fddd = g.multiply(one.subtract(two.multiply(s0))).divide(s1.multiply(s3));
322 
323             T f;
324             T fd;
325             if (s0.divide(zero.add(6.0)).add(g1).getReal() >= 0.5) {
326                 f = S.subtract(g.multiply(S.asinh())).subtract(L);
327                 fd = one.subtract(g.divide(s2));
328             } else {
329                 // Accurate computation of S - (1 - g1) * asinh(S)
330                 // when (g1, S) is close to (0, 0).
331                 final T t = S.divide(one.add(one.add(S.multiply(S)).sqrt()));
332                 final T tsq = t.multiply(t);
333                 T x = S.multiply(g1.add(g.multiply(tsq)));
334                 T term = two.multiply(g).multiply(t);
335                 T twoI1 = one;
336                 T x0;
337                 int j = 0;
338                 do {
339                     if (++j == 1000000) {
340                         // This isn't expected to happen, but it protects against an infinite loop.
341                         throw new MathIllegalStateException(
342                                 OrekitMessages.UNABLE_TO_COMPUTE_HYPERBOLIC_ECCENTRIC_ANOMALY, j);
343                     }
344                     twoI1 = twoI1.add(2.0);
345                     term = term.multiply(tsq);
346                     x0 = x;
347                     x = x.subtract(term.divide(twoI1));
348                 } while (x.getReal() != x0.getReal());
349                 f = x.subtract(L);
350                 fd = s0.divide(s2.add(one)).add(g1).divide(s2);
351             }
352             final T ds = f.multiply(fd).divide(half.multiply(f).multiply(fdd).subtract(fd.multiply(fd)));
353             final T stemp = S.add(ds);
354             if (S.getReal() == stemp.getReal()) {
355                 break;
356             }
357             f = f.add(ds.multiply(fd.add(half.multiply(ds.multiply(fdd.add(ds.divide(three).multiply(fddd)))))));
358             fd = fd.add(ds.multiply(fdd.add(half.multiply(ds).multiply(fddd))));
359             S = stemp.subtract(f.divide(fd));
360         }
361 
362         final T H = S.asinh();
363         return H;
364     }
365 
366     /**
367      * Computes the hyperbolic mean anomaly from the hyperbolic eccentric anomaly.
368      * @param <T> field type
369      * @param e eccentricity &gt; 1
370      * @param H hyperbolic eccentric anomaly
371      * @return hyperbolic mean anomaly
372      */
373     public static <T extends CalculusFieldElement<T>> T hyperbolicEccentricToMean(final T e, final T H) {
374         return e.multiply(H.sinh()).subtract(H);
375     }
376 
377 }