1   /* Copyright 2002-2026 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.conversion.osc2mean;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.util.FastMath;
22  import org.hipparchus.util.MathUtils;
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitMessages;
25  import org.orekit.orbits.EquinoctialOrbit;
26  import org.orekit.orbits.FieldEquinoctialOrbit;
27  import org.orekit.orbits.FieldOrbit;
28  import org.orekit.orbits.Orbit;
29  import org.orekit.orbits.PositionAngleType;
30  import org.orekit.time.FieldAbsoluteDate;
31  
32  /**
33   * Class enabling conversion from osculating to mean orbit
34   * for a given theory using a fixed-point algorithm.
35   *
36   * @author Pascal Parraud
37   * @since 13.0
38   */
39  public class FixedPointConverter implements OsculatingToMeanConverter {
40  
41      /** Default convergence threshold. */
42      public static final double DEFAULT_THRESHOLD   = 1e-12;
43  
44      /** Default maximum number of iterations. */
45      public static final int DEFAULT_MAX_ITERATIONS = 100;
46  
47      /** Default damping ratio. */
48      public static final double DEFAULT_DAMPING     = 1.;
49  
50      /** Mean theory used. */
51      private MeanTheory theory;
52  
53      /** Convergence threshold. */
54      private double threshold;
55  
56      /** Maximum number of iterations. */
57      private int maxIterations;
58  
59      /** Damping ratio. */
60      private double damping;
61  
62      /** Number of iterations performed. */
63      private int iterationsNb;
64  
65      /**
66       * Default constructor.
67       * <p>
68       * The mean theory must be set before converting.
69       */
70      public FixedPointConverter() {
71          this(null, DEFAULT_THRESHOLD, DEFAULT_MAX_ITERATIONS, DEFAULT_DAMPING);
72      }
73  
74      /**
75       * Constructor.
76       * @param theory mean theory to be used
77       */
78      public FixedPointConverter(final MeanTheory theory) {
79          this(theory, DEFAULT_THRESHOLD, DEFAULT_MAX_ITERATIONS, DEFAULT_DAMPING);
80      }
81  
82      /**
83       * Constructor.
84       * <p>
85       * The mean theory must be set before converting.
86       *
87       * @param threshold tolerance for convergence
88       * @param maxIterations maximum number of iterations
89       * @param damping damping ratio
90       */
91      public FixedPointConverter(final double threshold,
92                                 final int maxIterations,
93                                 final double damping) {
94          this(null, threshold, maxIterations, damping);
95      }
96  
97      /**
98       * Constructor.
99       * @param theory mean theory to be used
100      * @param threshold tolerance for convergence
101      * @param maxIterations maximum number of iterations
102      * @param damping damping ratio
103      */
104     public FixedPointConverter(final MeanTheory theory,
105                                final double threshold,
106                                final int maxIterations,
107                                final double damping) {
108         setMeanTheory(theory);
109         setThreshold(threshold);
110         setMaxIterations(maxIterations);
111         setDamping(damping);
112     }
113 
114     /** {@inheritDoc} */
115     @Override
116     public MeanTheory getMeanTheory() {
117         return theory;
118     }
119 
120     /** {@inheritDoc} */
121     @Override
122     public void setMeanTheory(final MeanTheory meanTheory) {
123         this.theory = meanTheory;
124     }
125 
126     /** {@inheritDoc} */
127     @Override
128     public double getThreshold() {
129         return threshold;
130     }
131 
132     /**
133      * Sets convergence threshold.
134      * @param threshold convergence threshold
135      */
136     public void setThreshold(final double threshold) {
137         this.threshold = threshold;
138     }
139 
140     /**
141      * Gets maximum number of iterations.
142      * @return maximum number of iterations
143      */
144     public int getMaxIterations() {
145         return maxIterations;
146     }
147 
148     /**
149      * Sets maximum number of iterations.
150      * @param maxIterations maximum number of iterations
151      */
152     public void setMaxIterations(final int maxIterations) {
153         this.maxIterations = maxIterations;
154     }
155 
156     /**
157      * Gets damping ratio.
158      * @return damping ratio
159      */
160     public double getDamping() {
161         return damping;
162     }
163 
164     /**
165      * Sets damping ratio.
166      * @param damping damping ratio
167      */
168     public void setDamping(final double damping) {
169         this.damping = damping;
170     }
171 
172     /**
173      * Gets the number of iterations performed by the last conversion.
174      * @return number of iterations
175      */
176     public int getIterationsNb() {
177         return iterationsNb;
178     }
179 
180     /** {@inheritDoc}
181      *  Uses a fixed-point algorithm.
182      */
183     @Override
184     public Orbit convertToMean(final Orbit osculating) {
185 
186         // sanity check
187         if (osculating.getA() < theory.getReferenceRadius()) {
188             throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE,
189                                       osculating.getA());
190         }
191 
192         // Get equinoctial osculating parameters
193         final Orbit equinoctial = theory.preprocessing(osculating);
194         double sma = equinoctial.getA();
195         double ex  = equinoctial.getEquinoctialEx();
196         double ey  = equinoctial.getEquinoctialEy();
197         double hx  = equinoctial.getHx();
198         double hy  = equinoctial.getHy();
199         double lv  = equinoctial.getLv();
200 
201         // Set threshold for each parameter
202         final double thresholdA  = threshold * FastMath.abs(sma);
203         final double thresholdE  = threshold * (1 + FastMath.hypot(ex, ey));
204         final double thresholdH  = threshold * (1 + FastMath.hypot(hx, hy));
205         final double thresholdLv = threshold * FastMath.PI;
206 
207         // Rough initialization of the mean parameters
208         Orbit mean = theory.initialize(equinoctial);
209 
210         int i = 0;
211         while (i++ < maxIterations) {
212 
213             // Update osculating parameters from current mean parameters
214             final Orbit updated = theory.meanToOsculating(mean);
215 
216             // Updated parameters residuals
217             final double deltaA  = equinoctial.getA() - updated.getA();
218             final double deltaEx = equinoctial.getEquinoctialEx() - updated.getEquinoctialEx();
219             final double deltaEy = equinoctial.getEquinoctialEy() - updated.getEquinoctialEy();
220             final double deltaHx = equinoctial.getHx() - updated.getHx();
221             final double deltaHy = equinoctial.getHy() - updated.getHy();
222             final double deltaLv = MathUtils.normalizeAngle(equinoctial.getLv() - updated.getLv(), 0.0);
223 
224             // Check convergence
225             if (FastMath.abs(deltaA)  < thresholdA &&
226                 FastMath.abs(deltaEx) < thresholdE &&
227                 FastMath.abs(deltaEy) < thresholdE &&
228                 FastMath.abs(deltaHx) < thresholdH &&
229                 FastMath.abs(deltaHy) < thresholdH &&
230                 FastMath.abs(deltaLv) < thresholdLv) {
231                 // Records number of iterations performed
232                 iterationsNb = i;
233                 // Returns the mean orbit
234                 return theory.postprocessing(osculating, mean);
235             }
236 
237             // Update mean parameters
238             sma += damping * deltaA;
239             ex  += damping * deltaEx;
240             ey  += damping * deltaEy;
241             hx  += damping * deltaHx;
242             hy  += damping * deltaHy;
243             lv  += damping * deltaLv;
244 
245             // Update mean orbit
246             mean = new EquinoctialOrbit(sma, ex, ey, hx, hy, lv,
247                                         PositionAngleType.TRUE,
248                                         equinoctial.getFrame(),
249                                         equinoctial.getDate(),
250                                         equinoctial.getMu());
251         }
252         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_MEAN_PARAMETERS, theory.getTheoryName(), i);
253     }
254 
255     /** {@inheritDoc}
256      *  Uses a fixed-point algorithm.
257      */
258     @Override
259     public <T extends CalculusFieldElement<T>> FieldOrbit<T> convertToMean(final FieldOrbit<T> osculating) {
260 
261         // Sanity check
262         if (osculating.getA().getReal() < theory.getReferenceRadius()) {
263             throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE,
264                                            osculating.getA().getReal());
265         }
266 
267         // Get field
268         final FieldAbsoluteDate<T> date = osculating.getDate();
269         final Field<T> field = date.getField();
270         final T zero = field.getZero();
271         final T pi   = zero.getPi();
272 
273         // Get equinoctial parameters
274         final FieldOrbit<T> equinoctial = theory.preprocessing(osculating);
275         T sma = equinoctial.getA();
276         T ex  = equinoctial.getEquinoctialEx();
277         T ey  = equinoctial.getEquinoctialEy();
278         T hx  = equinoctial.getHx();
279         T hy  = equinoctial.getHy();
280         T lv  = equinoctial.getLv();
281 
282         // Set threshold for each parameter
283         final T thresholdA  = sma.abs().multiply(threshold);
284         final T thresholdE  = FastMath.hypot(ex, ey).add(1).multiply(threshold);
285         final T thresholdH  = FastMath.hypot(hx, hy).add(1).multiply(threshold);
286         final T thresholdLv = pi.multiply(threshold);
287 
288         // Rough initialization of the mean parameters
289         FieldOrbit<T> mean = theory.initialize(equinoctial);
290 
291         int i = 0;
292         while (i++ < maxIterations) {
293 
294             // recompute the osculating parameters from the current mean parameters
295             final FieldOrbit<T> updated = theory.meanToOsculating(mean);
296 
297             // Updated parameters residuals
298             final T deltaA  = equinoctial.getA().subtract(updated.getA());
299             final T deltaEx = equinoctial.getEquinoctialEx().subtract(updated.getEquinoctialEx());
300             final T deltaEy = equinoctial.getEquinoctialEy().subtract(updated.getEquinoctialEy());
301             final T deltaHx = equinoctial.getHx().subtract(updated.getHx());
302             final T deltaHy = equinoctial.getHy().subtract(updated.getHy());
303             final T deltaLv = MathUtils.normalizeAngle(equinoctial.getLv().subtract(updated.getLv()), zero);
304 
305             // Check convergence
306             if (FastMath.abs(deltaA.getReal())  < thresholdA.getReal() &&
307                 FastMath.abs(deltaEx.getReal()) < thresholdE.getReal() &&
308                 FastMath.abs(deltaEy.getReal()) < thresholdE.getReal() &&
309                 FastMath.abs(deltaHx.getReal()) < thresholdH.getReal() &&
310                 FastMath.abs(deltaHy.getReal()) < thresholdH.getReal() &&
311                 FastMath.abs(deltaLv.getReal()) < thresholdLv.getReal()) {
312                 // Records number of iterations performed
313                 iterationsNb = i;
314                 // Returns the mean orbit
315                 return theory.postprocessing(osculating, mean);
316             }
317 
318             // Update mean parameters
319             sma = sma.add(deltaA.multiply(damping));
320             ex  = ex.add(deltaEx.multiply(damping));
321             ey  = ey.add(deltaEy.multiply(damping));
322             hx  = hx.add(deltaHx.multiply(damping));
323             hy  = hy.add(deltaHy.multiply(damping));
324             lv  = lv.add(deltaLv.multiply(damping));
325 
326             // Update mean orbit
327             mean = new FieldEquinoctialOrbit<>(sma, ex, ey, hx, hy, lv,
328                                                PositionAngleType.TRUE,
329                                                equinoctial.getFrame(),
330                                                equinoctial.getDate(),
331                                                equinoctial.getMu());
332         }
333         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_MEAN_PARAMETERS, theory.getTheoryName(), i);
334     }
335 }