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.semianalytical.dsst.utilities;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.complex.Complex;
22  import org.hipparchus.exception.NullArgumentException;
23  import org.hipparchus.random.MersenneTwister;
24  import org.hipparchus.util.Binary64Field;
25  import org.hipparchus.util.FastMath;
26  import org.hipparchus.util.MathArrays;
27  import org.junit.jupiter.api.Assertions;
28  import org.junit.jupiter.api.Test;
29  
30  public class FieldGHmjTest {
31  
32      private static final double eps = 1e-10;
33  
34      @Test
35      public void testGHmsj() {
36          doTestGHmsj(Binary64Field.getInstance());
37      }
38  
39      /** Gmsj and Hmsj computation test based on 2 independent methods.
40       *  If they give same results, we assume them to be consistent.
41       */
42      private <T extends CalculusFieldElement<T>> void doTestGHmsj(Field<T> field) {
43          final T zero = field.getZero();
44          final int sMax = 30;
45          final int mMax = 20;
46          final MersenneTwister random = new MersenneTwister(123456);
47          for (int i = 0; i < 10; i++) {
48              final T k = zero.add(random.nextDouble());
49              final T h = zero.add(random.nextDouble());
50              final T a = zero.add(random.nextDouble());
51              final T b = zero.add(random.nextDouble());
52              final FieldGHmsjPolynomials<T> gMSJ = new FieldGHmsjPolynomials<>(k, h, a, b, 1, field);
53              for (int s = -sMax; s <= sMax; s++) {
54                  for (int m = 2; m <= mMax; m+=2) {
55                      final int j = m / 2;
56                      T[] GHmsj = MathArrays.buildArray(field, 2);
57                      GHmsj = getGHmsj(k, h, a, b, m, s, j, field);
58                      Assertions.assertEquals(GHmsj[0].getReal(), gMSJ.getGmsj(m, s, j).getReal(), FastMath.abs(GHmsj[0].multiply(eps)).getReal());
59                      Assertions.assertEquals(GHmsj[1].getReal(), gMSJ.getHmsj(m, s, j).getReal(), FastMath.abs(GHmsj[1].multiply(eps)).getReal());
60                  }
61              }
62          }
63      }
64  
65      @Test
66      public void testdGHdk() {
67          doTestdGHdk(Binary64Field.getInstance());
68      }
69  
70      /** dG/dk and dH/dk computations test based on 2 independent methods.
71       *  If they give same results, we assume them to be consistent.
72       */
73      private <T extends CalculusFieldElement<T>> void doTestdGHdk(Field<T> field) {
74          final T zero = field.getZero();
75          final int sMax = 30;
76          final int mMax = 20;
77          final MersenneTwister random = new MersenneTwister(456789);
78          for (int i = 0; i < 10; i++) {
79              final T k = zero.add(random.nextDouble());
80              final T h = zero.add(random.nextDouble());
81              final T a = zero.add(random.nextDouble());
82              final T b = zero.add(random.nextDouble());
83              final FieldGHmsjPolynomials<T> gMSJ = new FieldGHmsjPolynomials<>(k, h, a, b, 1, field);
84              for (int s = -sMax; s <= sMax; s++) {
85                  for (int m = 2; m <= mMax; m+=2) {
86                      final int j = m / 2;
87                      final T[] dGHdk = getdGHdk(k, h, a, b, m, s, j, field);
88                      Assertions.assertEquals(dGHdk[0].getReal(), gMSJ.getdGmsdk(m, s, j).getReal(), FastMath.abs(dGHdk[0].multiply(eps)).getReal());
89                      Assertions.assertEquals(dGHdk[1].getReal(), gMSJ.getdHmsdk(m, s, j).getReal(), FastMath.abs(dGHdk[1].multiply(eps)).getReal());
90                  }
91              }
92          }
93      }
94  
95      @Test
96      public void testdGHdh() {
97          doTestdGHdh(Binary64Field.getInstance());
98      }
99  
100     /** dG/dh computation test based on 2 independent methods.
101      *  If they give same results, we assume them to be consistent.
102      */
103     private <T extends CalculusFieldElement<T>> void doTestdGHdh(Field<T> field) {
104         final T zero = field.getZero();
105         final int sMax = 30;
106         final int mMax = 20;
107         final MersenneTwister random = new MersenneTwister(123789);
108         for (int i = 0; i < 10; i++) {
109             final T k = zero.add(random.nextDouble());
110             final T h = zero.add(random.nextDouble());
111             final T a = zero.add(random.nextDouble());
112             final T b = zero.add(random.nextDouble());
113             final FieldGHmsjPolynomials<T> gMSJ = new FieldGHmsjPolynomials<>(k, h, a, b, 1, field);
114             for (int s = -sMax; s <= sMax; s++) {
115                 for (int m = 2; m <= mMax; m+=2) {
116                     final int j = m / 2;
117                     final T[] dGHdh = getdGHdh(k, h, a, b, m, s, j, field);
118                     Assertions.assertEquals(dGHdh[0].getReal(), gMSJ.getdGmsdh(m, s, j).getReal(), FastMath.abs(dGHdh[0].multiply(eps)).getReal());
119                     Assertions.assertEquals(dGHdh[1].getReal(), gMSJ.getdHmsdh(m, s, j).getReal(), FastMath.abs(dGHdh[1].multiply(eps)).getReal());
120                 }
121             }
122         }
123     }
124 
125     @Test
126     public void testdGHdAlpha() {
127         doTestdGHdAlpha(Binary64Field.getInstance());
128     }
129 
130     /** dG/dα and dH/dα computations test based on 2 independent methods.
131      *  If they give same results, we assume them to be consistent.
132      */
133     private <T extends CalculusFieldElement<T>> void doTestdGHdAlpha(Field<T> field) {
134         final T zero = field.getZero();
135         final int sMax = 30;
136         final int mMax = 20;
137         final MersenneTwister random = new MersenneTwister(123456789);
138         for (int i = 0; i < 10; i++) {
139             final T k = zero.add(random.nextDouble());
140             final T h = zero.add(random.nextDouble());
141             final T a = zero.add(random.nextDouble());
142             final T b = zero.add(random.nextDouble());
143             final FieldGHmsjPolynomials<T> gMSJ = new FieldGHmsjPolynomials<>(k, h, a, b, 1, field);
144             for (int s = -sMax; s <= sMax; s++) {
145                 for (int m = 2; m <= mMax; m+=2) {
146                     final int j = m / 2;
147                     final T[] dGHda = getdGHda(k, h, a, b, m, s, j, field);
148                     Assertions.assertEquals(dGHda[0].getReal(), gMSJ.getdGmsdAlpha(m, s, j).getReal(), FastMath.abs(dGHda[0].multiply(eps)).getReal());
149                     Assertions.assertEquals(dGHda[1].getReal(), gMSJ.getdHmsdAlpha(m, s, j).getReal(), FastMath.abs(dGHda[1].multiply(eps)).getReal());
150                 }
151             }
152         }
153     }
154 
155     @Test
156     public void testdGHdBeta() {
157         doTestdGHdBeta(Binary64Field.getInstance());
158     }
159 
160     /** dG/dβ and dH/dβ computations test based on 2 independent methods.
161      *  If they give same results, we assume them to be consistent.
162      */
163     private <T extends CalculusFieldElement<T>> void doTestdGHdBeta(Field<T> field) {
164         final T zero = field.getZero();
165         final int sMax = 30;
166         final int mMax = 20;
167         final MersenneTwister random = new MersenneTwister(987654321);
168         for (int i = 0; i < 10; i++) {
169             final T k = zero.add(random.nextDouble());
170             final T h = zero.add(random.nextDouble());
171             final T a = zero.add(random.nextDouble());
172             final T b = zero.add(random.nextDouble());
173             final FieldGHmsjPolynomials<T> gMSJ = new FieldGHmsjPolynomials<>(k, h, a, b, 1, field);
174             for (int s = -sMax; s <= sMax; s++) {
175                 for (int m = 2; m <= mMax; m+=2) {
176                     final int j = m / 2;
177                     final T[] dGHdb = getdGHdb(k, h, a, b, m, s, j, field);
178                     Assertions.assertEquals(dGHdb[0].getReal(), gMSJ.getdGmsdBeta(m, s, j).getReal(), FastMath.abs(dGHdb[0].multiply(eps)).getReal());
179                     Assertions.assertEquals(dGHdb[1].getReal(), gMSJ.getdHmsdBeta(m, s, j).getReal(), FastMath.abs(dGHdb[1].multiply(eps)).getReal());
180                 }
181             }
182         }
183     }
184 
185     /** Compute directly G<sup>j</sup><sub>ms</sub> and H<sup>j</sup><sub>ms</sub> from equation 2.7.1-(14).
186      * @param k x-component of the eccentricity vector
187      * @param h y-component of the eccentricity vector
188      * @param a 1st direction cosine
189      * @param b 2nd direction cosine
190      * @param m order
191      * @param s d'Alembert characteristic
192      * @param j index
193      * @return G<sub>ms</sub><sup>j</sup> and H<sup>j</sup><sub>ms</sub> values
194      */
195     private static <T extends CalculusFieldElement<T>> T[] getGHmsj(final T k, final T h,
196                                      final T a, final T b,
197                                      final int m, final int s, final int j,
198                                      final Field<T> field) {
199         final FieldComplex<T> kh = new FieldComplex<>(k, h.multiply(sgn(s - j))).pow(FastMath.abs(s - j));
200         FieldComplex<T> ab;
201         if (FastMath.abs(s) < m) {
202             ab = new FieldComplex<>(a, b).pow(m - s);
203         } else {
204             ab = new FieldComplex<>(a, b.multiply(sgn(s - m)).negate()).pow(FastMath.abs(s - m));
205         }
206         final FieldComplex<T> khab = kh.multiply(ab);
207 
208         final T[] values = MathArrays.buildArray(field, 2);
209         values[0] = khab.getReal();
210         values[1] = khab.getImaginary();
211         return values;
212     }
213 
214     /** Compute directly dG/dk and dH/dk from equation 2.7.1-(14).
215      * @param k x-component of the eccentricity vector
216      * @param h y-component of the eccentricity vector
217      * @param a 1st direction cosine
218      * @param b 2nd direction cosine
219      * @param m order
220      * @param s d'Alembert characteristic
221      * @param j index
222      * @return dG/dk and dH/dk values
223      */
224     private static <T extends CalculusFieldElement<T>> T[] getdGHdk(final T k, final T h,
225                                      final T a, final T b,
226                                      final int m, final int s, final int j,
227                                      final Field<T> field) {
228         final FieldComplex<T> kh = new FieldComplex<>(k, h.multiply(sgn(s - j))).pow(FastMath.abs(s - j)-1).multiply(FastMath.abs(s - j));
229         FieldComplex<T> ab;
230         if (FastMath.abs(s) < m) {
231             ab = new FieldComplex<>(a, b).pow(m - s);
232         } else {
233             ab = new FieldComplex<>(a, b.multiply(sgn(s - m)).negate()).pow(FastMath.abs(s - m));
234         }
235         final FieldComplex<T> khab = kh.multiply(ab);
236 
237         final T[] values = MathArrays.buildArray(field, 2);
238         values[0] = khab.getReal();
239         values[1] = khab.getImaginary();
240         return values;
241     }
242 
243     /** Compute directly dG/dh and dH/dh from equation 2.7.1-(14).
244      * @param k x-component of the eccentricity vector
245      * @param h y-component of the eccentricity vector
246      * @param a 1st direction cosine
247      * @param b 2nd direction cosine
248      * @param m order
249      * @param s d'Alembert characteristic
250      * @param j index
251      * @return dG/dh and dH/dh values
252      */
253     private static <T extends CalculusFieldElement<T>> T[] getdGHdh(final T k, final T h,
254                                      final T a, final T b,
255                                      final int m, final int s, final int j,
256                                      final Field<T> field) {
257         final T zero = field.getZero();
258         final FieldComplex<T> kh1 = new FieldComplex<>(k, h.multiply(sgn(s - j))).pow(FastMath.abs(s - j)-1);
259         final FieldComplex<T> kh2 = new FieldComplex<>(zero, FastMath.abs(zero.add(s - j)).multiply(sgn(s - j)));
260         final FieldComplex<T> kh = kh1.multiply(kh2);
261         FieldComplex<T> ab;
262         if (FastMath.abs(s) < m) {
263             ab = new FieldComplex<>(a, b).pow(m - s);
264         } else {
265             ab = new FieldComplex<>(a, b.multiply(sgn(s - m)).negate()).pow(FastMath.abs(s - m));
266         }
267         final FieldComplex<T> khab = kh.multiply(ab);
268 
269         final T[] values = MathArrays.buildArray(field, 2);
270         values[0] = khab.getReal();
271         values[1] = khab.getImaginary();
272         return values;
273     }
274 
275     /** Compute directly dG/dα and dH/dα from equation 2.7.1-(14).
276      * @param k x-component of the eccentricity vector
277      * @param h y-component of the eccentricity vector
278      * @param a 1st direction cosine
279      * @param b 2nd direction cosine
280      * @param m order
281      * @param s d'Alembert characteristic
282      * @param j index
283      * @return dG/dα and dH/dα values
284      */
285     private static <T extends CalculusFieldElement<T>> T[] getdGHda(final T k, final T h,
286                                      final T a, final T b,
287                                      final int m, final int s, final int j,
288                                      final Field<T> field) {
289         final FieldComplex<T> kh = new FieldComplex<>(k, h.multiply(sgn(s - j))).pow(FastMath.abs(s - j));
290         FieldComplex<T> ab;
291         if (FastMath.abs(s) < m) {
292             ab = new FieldComplex<>(a, b).pow(m - s - 1).multiply(m - s);
293         } else {
294             ab = new FieldComplex<>(a, b.multiply(sgn(s - m)).negate()).pow(FastMath.abs(s - m) - 1).multiply(FastMath.abs(s - m));
295         }
296         final FieldComplex<T> khab = kh.multiply(ab);
297 
298         final T[] values = MathArrays.buildArray(field, 2);
299         values[0] = khab.getReal();
300         values[1] = khab.getImaginary();
301         return values;
302     }
303 
304     /** Compute directly dG/dβ and dH/dβ from equation 2.7.1-(14).
305      * @param k x-component of the eccentricity vector
306      * @param h y-component of the eccentricity vector
307      * @param a 1st direction cosine
308      * @param b 2nd direction cosine
309      * @param m order
310      * @param s d'Alembert characteristic
311      * @param j index
312      * @return dG/dβ and dH/dβ values
313      */
314     private static <T extends CalculusFieldElement<T>> T[] getdGHdb(final T k, final T h,
315                                      final T a, final T b,
316                                      final int m, final int s, final int j,
317                                      final Field<T> field) {
318         final T zero = field.getZero();
319         final FieldComplex<T> kh = new FieldComplex<>(k, h.multiply(sgn(s - j))).pow(FastMath.abs(s - j));
320         FieldComplex<T> ab;
321         if (FastMath.abs(s) < m) {
322             FieldComplex<T> ab1 = new FieldComplex<>(a, b).pow(m - s - 1);
323             FieldComplex<T> ab2 = new FieldComplex<>(zero, zero.add(m - s));
324             ab = ab1.multiply(ab2);
325         } else {
326             FieldComplex<T> ab1 = new FieldComplex<>(a, b.multiply(sgn(s - m)).negate()).pow(FastMath.abs(s - m) - 1);
327             FieldComplex<T> ab2 = new FieldComplex<>(zero, FastMath.abs(zero.add(s - m)).multiply(sgn(m - s)));
328             ab = ab1.multiply(ab2);
329         }
330         final FieldComplex<T> khab = kh.multiply(ab);
331 
332         final T[] values = MathArrays.buildArray(field, 2);
333         values[0] = khab.getReal();
334         values[1] = khab.getImaginary();
335         return values;
336     }
337 
338     /** Get the sign of an integer.
339      *  @param i number on which evaluation is done
340      *  @return -1 or +1 depending on sign of i
341      */
342     private static int sgn(final int i) {
343         return (i < 0) ? -1 : 1;
344     }
345 
346     private static class FieldComplex <T extends CalculusFieldElement<T>> {
347 
348         /** The imaginary part. */
349         private final T imaginary;
350 
351         /** The real part. */
352         private final T real;
353 
354         /**
355          * Create a complex number given the real and imaginary parts.
356          *
357          * @param real Real part.
358          * @param imaginary Imaginary part.
359          */
360         FieldComplex(final T real, final T imaginary) {
361             this.real = real;
362             this.imaginary = imaginary;
363         }
364 
365         /**
366          * Access the real part.
367          *
368          * @return the real part.
369          */
370         public T getReal() {
371             return real;
372         }
373 
374         /**
375          * Access the imaginary part.
376          *
377          * @return the imaginary part.
378          */
379         public T getImaginary() {
380             return imaginary;
381         }
382 
383         /**
384          * Create a complex number given the real and imaginary parts.
385          *
386          * @param realPart Real part.
387          * @param imaginaryPart Imaginary part.
388          * @return a new complex number instance.
389          *
390          * @see #valueOf(double, double)
391          */
392         protected FieldComplex<T> createComplex(final T realPart, final T imaginaryPart) {
393             return new FieldComplex<>(realPart, imaginaryPart);
394         }
395 
396         /**
397          * Returns a {@code Complex} whose value is {@code this * factor}.
398          * Implements preliminary checks for {@code NaN} and infinity followed by
399          * the definitional formula:
400          * <p>
401          *   {@code (a + bi)(c + di) = (ac - bd) + (ad + bc)i}
402          * </p>
403          * <p>
404          * Returns finite values in components of the result per the definitional
405          * formula in all remaining cases.</p>
406          *
407          * @param  factor value to be multiplied by this {@code Complex}.
408          * @return {@code this * factor}.
409          * @throws NullArgumentException if {@code factor} is {@code null}.
410          */
411         public FieldComplex<T> multiply(final FieldComplex<T> factor) throws NullArgumentException {
412             return createComplex(real.multiply(factor.real).subtract(imaginary.multiply(factor.imaginary)),
413                                  real.multiply(factor.imaginary).add(imaginary.multiply(factor.real)));
414         }
415 
416         /**
417          * Returns a {@code Complex} whose value is {@code this * factor}, with {@code factor}
418          * interpreted as a integer number.
419          *
420          * @param  factor value to be multiplied by this {@code Complex}.
421          * @return {@code this * factor}.
422          * @see #multiply(Complex)
423          */
424         public FieldComplex<T> multiply(final int factor) {
425             return createComplex(real.multiply(factor), imaginary.multiply(factor));
426         }
427 
428         /**
429          * Returns of value of this complex number raised to the power of {@code x}.
430          *
431          * @param  x exponent to which this {@code Complex} is to be raised.
432          * @return <code>this<sup>x</sup></code>.
433          * @see #pow(Complex)
434          */
435          public FieldComplex<T> pow(int x) {
436             return this.log().multiply(x).exp();
437         }
438 
439          /**
440           * Compute the
441           * <a href="http://mathworld.wolfram.com/NaturalLogarithm.html" TARGET="_top">
442           * natural logarithm</a> of this complex number.
443           * Implements the formula:
444           * <pre>
445           *  <code>
446           *   log(a + bi) = ln(|a + bi|) + arg(a + bi)i
447           *  </code>
448           * </pre>
449           * where ln on the right hand side is {@link FastMath#log},
450           * {@code |a + bi|} is the modulus, {@link Complex#abs},  and
451           * {@code arg(a + bi) = }{@link FastMath#atan2}(b, a).
452           * <p>
453           * Returns {@link Complex#NaN} if either real or imaginary part of the
454           * input argument is {@code NaN}.
455           * </p>
456           * Infinite (or critical) values in real or imaginary parts of the input may
457           * result in infinite or NaN values returned in parts of the result.
458           * <pre>
459           *  Examples:
460           *  <code>
461           *   log(1 &plusmn; INFINITY i) = INFINITY &plusmn; (&pi;/2)i
462           *   log(INFINITY + i) = INFINITY + 0i
463           *   log(-INFINITY + i) = INFINITY + &pi;i
464           *   log(INFINITY &plusmn; INFINITY i) = INFINITY &plusmn; (&pi;/4)i
465           *   log(-INFINITY &plusmn; INFINITY i) = INFINITY &plusmn; (3&pi;/4)i
466           *   log(0 + 0i) = -INFINITY + 0i
467           *  </code>
468           * </pre>
469           *
470           * @return the value <code>ln &nbsp; this</code>, the natural logarithm
471           * of {@code this}.
472           */
473          public FieldComplex<T> log() {
474              return createComplex(FastMath.log(abs()),
475                                   FastMath.atan2(imaginary, real));
476          }
477 
478          /**
479           * Compute the
480           * <a href="http://mathworld.wolfram.com/ExponentialFunction.html" TARGET="_top">
481           * exponential function</a> of this complex number.
482           * Implements the formula:
483           * <pre>
484           *  <code>
485           *   exp(a + bi) = exp(a)cos(b) + exp(a)sin(b)i
486           *  </code>
487           * </pre>
488           * where the (real) functions on the right-hand side are
489           * {@link FastMath#exp}, {@link FastMath#cos}, and
490           * {@link FastMath#sin}.
491           * <p>
492           * Returns {@link Complex#NaN} if either real or imaginary part of the
493           * input argument is {@code NaN}.
494           * </p>
495           * Infinite values in real or imaginary parts of the input may result in
496           * infinite or NaN values returned in parts of the result.
497           * <pre>
498           *  Examples:
499           *  <code>
500           *   exp(1 &plusmn; INFINITY i) = NaN + NaN i
501           *   exp(INFINITY + i) = INFINITY + INFINITY i
502           *   exp(-INFINITY + i) = 0 + 0i
503           *   exp(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
504           *  </code>
505           * </pre>
506           *
507           * @return <code><i>e</i><sup>this</sup></code>.
508           */
509          public FieldComplex<T> exp() {
510              T expReal = FastMath.exp(real);
511              return createComplex(expReal.multiply(FastMath.cos(imaginary)),
512                                   expReal.multiply(FastMath.sin(imaginary)));
513          }
514 
515          /**
516           * Return the absolute value of this complex number.
517           * Returns {@code NaN} if either real or imaginary part is {@code NaN}
518           * and {@code Double.POSITIVE_INFINITY} if neither part is {@code NaN},
519           * but at least one part is infinite.
520           *
521           * @return the absolute value.
522           */
523          public T abs() {
524              if (FastMath.abs(real).getReal() < FastMath.abs(imaginary).getReal()) {
525                  if (imaginary.getReal() == 0.0) {
526                      return FastMath.abs(real);
527                  }
528                  T q = real.divide(imaginary);
529                  return FastMath.abs(imaginary).multiply(FastMath.sqrt(q.multiply(q).add(1.)));
530              } else {
531                  if (real.getReal() == 0.0) {
532                      return FastMath.abs(imaginary);
533                  }
534                  T q = imaginary.divide(real);
535                  return FastMath.abs(real).multiply(FastMath.sqrt(q.multiply(q).add(1.)));
536              }
537          }
538 
539     }
540 }