1   /* Copyright 2002-2024 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.forces.gravity;
18  
19  
20  import org.hipparchus.dfp.Dfp;
21  import org.hipparchus.dfp.DfpField;
22  import org.hipparchus.dfp.DfpMath;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.hipparchus.util.FastMath;
25  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
26  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider.NormalizedSphericalHarmonics;
27  import org.orekit.forces.gravity.potential.RawSphericalHarmonicsProvider;
28  import org.orekit.forces.gravity.potential.RawSphericalHarmonicsProvider.RawSphericalHarmonics;
29  import org.orekit.forces.gravity.potential.SphericalHarmonicsProvider;
30  import org.orekit.forces.gravity.potential.TideSystem;
31  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
32  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics;
33  import org.orekit.time.AbsoluteDate;
34  
35  
36  /** Implementation of gravity field model from defining formulas.
37   * <p>
38   * This implementation is for test purposes only! It is extremely slow.
39   * </p>
40   * <p>
41   * The major features of this implementation are:
42   * <ul>
43   *   <li>its accuracy can be adjusted at will to any number of digits,</li>
44   *   <li>it relies on direct defining formulas and hence is completely independent from
45   *   the optimized practical recursions.</li>
46   * </ul>
47   * Both features are helpful for cross-checking practical optimized implementations.
48   * </p>
49   * <p>
50   * Note that since this uses defining formulas to perform direct computation, it is
51   * subject to the high instability of these formulas near poles. Tests performed
52   * with the D. M. Gleason resting testing regime have shown for example that setting
53   * the accuracy to 28 digits for the computation leads to field values having only about
54   * 11 digits accuracy left near poles! In order to get 16 digits and be able to use this
55   * class as a reference for testing other implementations, we needed to set accuracy to
56   * at lest 30 digits. Further tests have shown that setting accuracy to 40 digits for
57   * computation leads to about 24 digits left near poles.
58   * </p>
59   * <p>
60   * An interesting conclusion from the above examples is that simply using better
61   * arithmetic as we do here is much <em>less</em> efficient than using dedicated
62   * stable algorithms (like {@link HolmesFeatherstoneAttractionModel Holmes-Featherstone}
63   * algorithms.
64   * </p>
65   * @author Luc Maisonobe
66   */
67  class ReferenceFieldModel {
68  
69      private final DfpField dfpField;
70      private final SphericalHarmonicsProvider provider;
71      private final AssociatedLegendreFunction[][] alf;
72  
73      public ReferenceFieldModel(NormalizedSphericalHarmonicsProvider provider, int digits) {
74          this.dfpField = new DfpField(digits);
75          this.provider = provider;
76          this.alf = createAlf(provider.getMaxDegree(), provider.getMaxOrder(), true, dfpField);
77      }
78  
79      public ReferenceFieldModel(UnnormalizedSphericalHarmonicsProvider provider, int digits) {
80          this.dfpField = new DfpField(digits);
81          this.provider = provider;
82          this.alf = createAlf(provider.getMaxDegree(), provider.getMaxOrder(), false, dfpField);
83      }
84  
85      private static AssociatedLegendreFunction[][] createAlf(int degree, int order, boolean isNormalized,
86                                                              DfpField dfpField) {
87          AssociatedLegendreFunction[][] alf = new AssociatedLegendreFunction[degree + 1][];
88          for (int n = 2; n < alf.length; ++n) {
89              alf[n] = new AssociatedLegendreFunction[FastMath.min(n, order) + 1];
90              for (int m = 0; m < alf[n].length; ++m) {
91                  alf[n][m] = new AssociatedLegendreFunction(true, n, m, dfpField);
92              }
93          }
94          return alf;
95      }
96  
97      public Dfp nonCentralPart(final AbsoluteDate date, final Vector3D position)
98              {
99  
100         int degree = provider.getMaxDegree();
101         int order  = provider.getMaxOrder();
102         //use coefficients without caring if they are the correct type
103         final RawSphericalHarmonics harmonics = raw(provider).onDate(date);
104 
105         Dfp x      = dfpField.newDfp(position.getX());
106         Dfp y      = dfpField.newDfp(position.getY());
107         Dfp z      = dfpField.newDfp(position.getZ());
108 
109         Dfp rho2     = x.square().add(y.square());
110         Dfp rho      = rho2.sqrt();
111         Dfp r2       = rho2.add(z.square());
112         Dfp r        = r2.sqrt();
113         Dfp aOr      = dfpField.newDfp(provider.getAe()).divide(r);
114         Dfp lambda   = position.getX() > 0 ?
115                        dfpField.getTwo().multiply(DfpMath.atan(y.divide(rho.add(x)))) :
116                        dfpField.getPi().subtract(dfpField.getTwo().multiply(DfpMath.atan(y.divide(rho.subtract(x)))));
117         Dfp cosTheta = z.divide(r);
118 
119         Dfp value = dfpField.getZero();
120         Dfp aOrN      = aOr;
121         for (int n = 2; n <= degree; ++n) {
122             Dfp sum = dfpField.getZero();
123             for (int m = 0; m <= FastMath.min(n, order); ++m) {
124                 double cnm = harmonics.getRawCnm(n, m);
125                 double snm = harmonics.getRawSnm(n, m);
126                 Dfp mLambda = lambda.multiply(m);
127                 Dfp c       = DfpMath.cos(mLambda).multiply(dfpField.newDfp(cnm));
128                 Dfp s       = DfpMath.sin(mLambda).multiply(dfpField.newDfp(snm));
129                 Dfp pnm     = alf[n][m].value(cosTheta);
130                  sum = sum.add(pnm.multiply(c.add(s)));
131             }
132             aOrN = aOrN.multiply(aOr);
133             value = value.add(aOrN.multiply(sum));
134         }
135 
136         return value.multiply(dfpField.newDfp(provider.getMu())).divide(r);
137 
138     }
139 
140     /**
141      * Wrap the given harmonics with a {@link RawSphericalHarmonicsProvider} to ignore the
142      * type of the coefficients.
143      *
144      * @param provider harmonics provider
145      * @return a raw provider wrapping {@code provider}.
146      */
147     private RawSphericalHarmonicsProvider raw(final SphericalHarmonicsProvider provider) {
148         if (provider instanceof RawSphericalHarmonicsProvider) {
149             return (RawSphericalHarmonicsProvider) provider;
150         } else if (provider instanceof NormalizedSphericalHarmonicsProvider) {
151             return new RawerSphericalHarmonicsProvider(provider) {
152                 @Override
153                 public RawSphericalHarmonics onDate(final AbsoluteDate date)
154                         {
155                     final NormalizedSphericalHarmonics normalized =
156                             ((NormalizedSphericalHarmonicsProvider) provider).onDate(date);
157                     return new RawSphericalHarmonics() {
158                         @Override
159                         public double getRawCnm(int n, int m) {
160                             return normalized.getNormalizedCnm(n, m);
161                         }
162 
163                         @Override
164                         public double getRawSnm(int n, int m) {
165                             return normalized.getNormalizedSnm(n, m);
166                         }
167 
168                         @Override
169                         public AbsoluteDate getDate() {
170                             return date;
171                         }
172                     };
173                 }
174             };
175         } else if (provider instanceof UnnormalizedSphericalHarmonicsProvider) {
176             return new RawerSphericalHarmonicsProvider(provider) {
177                 @Override
178                 public RawSphericalHarmonics onDate(final AbsoluteDate date)
179                         {
180                     final UnnormalizedSphericalHarmonics unnormalized =
181                             ((UnnormalizedSphericalHarmonicsProvider) provider).onDate(date);
182                     return new RawSphericalHarmonics() {
183                         @Override
184                         public double getRawCnm(int n, int m) {
185                             return unnormalized.getUnnormalizedCnm(n, m);
186                         }
187 
188                         @Override
189                         public double getRawSnm(int n, int m) {
190                             return unnormalized.getUnnormalizedSnm(n, m);
191                         }
192 
193                         @Override
194                         public AbsoluteDate getDate() {
195                             return date;
196                         }
197                     };
198                 }
199             };
200         } else {
201             throw new RuntimeException("Unknown harmonics provider type: " + provider);
202         }
203     }
204 
205     /** Delegating Provider class */
206     public static abstract class RawerSphericalHarmonicsProvider
207             implements RawSphericalHarmonicsProvider {
208 
209         /** wrapped provider */
210         private final SphericalHarmonicsProvider provider;
211 
212         /**
213          * Wrap the given provider.
214          *
215          * @param provider the provider to delegate to
216          */
217         public RawerSphericalHarmonicsProvider(SphericalHarmonicsProvider provider) {
218             this.provider = provider;
219         }
220 
221         public int getMaxDegree() {
222             return provider.getMaxDegree();
223         }
224 
225         public int getMaxOrder() {
226             return provider.getMaxOrder();
227         }
228 
229         public double getMu() {
230             return provider.getMu();
231         }
232 
233         public double getAe() {
234             return provider.getAe();
235         }
236 
237         public AbsoluteDate getReferenceDate() {
238             return provider.getReferenceDate();
239         }
240 
241         public TideSystem getTideSystem() {
242             return provider.getTideSystem();
243         }
244     }
245 }