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.bodies;
18  
19  import org.hipparchus.analysis.UnivariateFunction;
20  import org.hipparchus.analysis.solvers.AllowedSolution;
21  import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
22  import org.hipparchus.analysis.solvers.UnivariateSolverUtils;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.hipparchus.util.FastMath;
25  import org.orekit.frames.CR3BPRotatingFrame;
26  import org.orekit.frames.Frame;
27  import org.orekit.frames.Transform;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.utils.AbsolutePVCoordinates;
30  import org.orekit.utils.LagrangianPoints;
31  import org.orekit.utils.PVCoordinates;
32  
33  /**
34   * Class creating, from two different celestial bodies, the corresponding system
35   * with respect to the Circular Restricted Three Body problem hypotheses.
36   * @see "Dynamical systems, the three-body problem, and space mission design, Koon, Lo, Marsden, Ross"
37   * @author Vincent Mouraux
38   * @author William Desprats
39   * @since 10.2
40   */
41  public class CR3BPSystem {
42  
43      /** Relative accuracy on position for solver. */
44      private static final double RELATIVE_ACCURACY = 1e-14;
45  
46      /** Absolute accuracy on position for solver (1mm). */
47      private static final double ABSOLUTE_ACCURACY = 1e-3;
48  
49      /** Function value accuracy for solver (set to 0 so we rely only on position for convergence). */
50      private static final double FUNCTION_ACCURACY = 0;
51  
52      /** Maximal order for solver. */
53      private static final int MAX_ORDER = 5;
54  
55      /** Maximal number of evaluations for solver. */
56      private static final int MAX_EVALUATIONS = 10000;
57  
58      /** Mass ratio. */
59      private final double mu;
60  
61      /** Distance between the two primaries, meters. */
62      private final double dDim;
63  
64      /** Orbital Velocity of m1, m/s. */
65      private final double vDim;
66  
67      /** Orbital Period of m1 and m2, seconds. */
68      private final double tDim;
69  
70      /** CR3BP System name. */
71      private final String name;
72  
73      /** Rotating Frame for the system. */
74      private final Frame rotatingFrame;
75  
76      /** Primary body. */
77      private final CelestialBody primaryBody;
78  
79      /** Secondary body. */
80      private final CelestialBody secondaryBody;
81  
82      /** L1 Point position in the rotating frame. */
83      private Vector3D l1Position;
84  
85      /** L2 Point position in the rotating frame. */
86      private Vector3D l2Position;
87  
88      /** L3 Point position in the rotating frame. */
89      private Vector3D l3Position;
90  
91      /** L4 Point position in the rotating frame. */
92      private Vector3D l4Position;
93  
94      /** L5 Point position in the rotating frame. */
95      private Vector3D l5Position;
96  
97      /** Distance between a L1 and the secondaryBody. */
98      private final double gamma1;
99  
100     /** Distance between a L2 and the secondaryBody. */
101     private final double gamma2;
102 
103     /** Distance between a L3 and the primaryBody. */
104     private final double gamma3;
105 
106     /**
107      * Simple constructor.
108      * <p>
109      * Standard constructor to use to define a CR3BP System. Mass ratio is
110      * calculated from JPL data.
111      * </p>
112      * @param primaryBody Primary Body in the CR3BP System
113      * @param secondaryBody Secondary Body in the CR3BP System
114      * @param a Semi-Major Axis of the secondary body
115      */
116     public CR3BPSystem(final CelestialBody primaryBody, final CelestialBody secondaryBody, final double a) {
117         this(primaryBody, secondaryBody, a, secondaryBody.getGM() / (secondaryBody.getGM() + primaryBody.getGM()));
118     }
119 
120     /**
121      * Simple constructor.
122      * <p>
123      * This constructor can be used to define a CR3BP System if the user wants
124      * to use a specified mass ratio.
125      * </p>
126      * @param primaryBody Primary Body in the CR3BP System
127      * @param secondaryBody Secondary Body in the CR3BP System
128      * @param a Semi-Major Axis of the secondary body
129      * @param mu Mass ratio of the CR3BPSystem
130      */
131     public CR3BPSystem(final CelestialBody primaryBody, final CelestialBody secondaryBody, final double a, final double mu) {
132         this.primaryBody = primaryBody;
133         this.secondaryBody = secondaryBody;
134 
135         this.name = primaryBody.getName() + "_" + secondaryBody.getName();
136 
137         final double mu1 = primaryBody.getGM();
138 
139         this.mu = mu;
140         this.dDim = a;
141         this.vDim = FastMath.sqrt(mu1 / dDim);
142         this.tDim = 2 * FastMath.PI * dDim / vDim;
143         this.rotatingFrame = new CR3BPRotatingFrame(mu, primaryBody, secondaryBody);
144 
145         computeLagrangianPointsPosition();
146 
147         // Calculation of collinear points gamma
148 
149         // L1 Gamma
150         final double x1 = l1Position.getX();
151         final double dCP1 = 1 - mu;
152         this.gamma1 = dCP1 - x1;
153 
154         // L2 Gamma
155         final double x2 = l2Position.getX();
156         final double dCP2 = 1 - mu;
157         this.gamma2 = x2 - dCP2;
158 
159         // L3 Gamma
160         final double x3 = l3Position.getX();
161         final double dCP3 = -mu;
162         this.gamma3 = dCP3 - x3;
163 
164     }
165 
166     /** Calculation of Lagrangian Points position using CR3BP equations.
167      */
168     private void computeLagrangianPointsPosition() {
169         // Calculation of Lagrangian Points position using CR3BP equations
170 
171         // L1
172         final BracketingNthOrderBrentSolver solver =
173                         new BracketingNthOrderBrentSolver(RELATIVE_ACCURACY,
174                                                           ABSOLUTE_ACCURACY,
175                                                           FUNCTION_ACCURACY, MAX_ORDER);
176 
177         final double baseR1 = 1 - FastMath.cbrt(mu / 3);
178         final UnivariateFunction l1Equation = x -> {
179             final double leq1 =
180                             x * (x + mu) * (x + mu) * (x + mu - 1) * (x + mu - 1);
181             final double leq2 = (1 - mu) * (x + mu - 1) * (x + mu - 1);
182             final double leq3 = mu * (x + mu) * (x + mu);
183             return leq1 - leq2 + leq3;
184         };
185         final double[] searchInterval1 =
186                         UnivariateSolverUtils.bracket(l1Equation, baseR1, -mu,
187                                                       1 - mu, 1E-6, 1,
188                                                       MAX_EVALUATIONS);
189 
190         final double r1 =
191                         solver.solve(MAX_EVALUATIONS, l1Equation, searchInterval1[0],
192                                      searchInterval1[1], AllowedSolution.ANY_SIDE);
193 
194         this.l1Position = new Vector3D(r1, 0.0, 0.0);
195 
196         // L2
197         final double baseR2 = 1 + FastMath.cbrt(mu / 3);
198         final UnivariateFunction l2Equation = x -> {
199             final double leq21 =
200                             x * (x + mu) * (x + mu) * (x + mu - 1) * (x + mu - 1);
201             final double leq22 = (1 - mu) * (x + mu - 1) * (x + mu - 1);
202             final double leq23 = mu * (x + mu) * (x + mu);
203             return leq21 - leq22 - leq23;
204         };
205         final double[] searchInterval2 =
206                         UnivariateSolverUtils.bracket(l2Equation, baseR2, 1 - mu, 2, 1E-6,
207                                                       1, MAX_EVALUATIONS);
208 
209         final double r2 =
210                         solver.solve(MAX_EVALUATIONS, l2Equation, searchInterval2[0],
211                                      searchInterval2[1], AllowedSolution.ANY_SIDE);
212 
213         this.l2Position = new Vector3D(r2, 0.0, 0.0);
214 
215         // L3
216         final double baseR3 = -(1 + 5 * mu / 12);
217         final UnivariateFunction l3Equation = x -> {
218             final double leq31 =
219                             x * (x + mu) * (x + mu) * (x + mu - 1) * (x + mu - 1);
220             final double leq32 = (1 - mu) * (x + mu - 1) * (x + mu - 1);
221             final double leq33 = mu * (x + mu) * (x + mu);
222             return leq31 + leq32 + leq33;
223         };
224         final double[] searchInterval3 =
225                         UnivariateSolverUtils.bracket(l3Equation, baseR3, -2, -mu, 1E-6, 1,
226                                                       MAX_EVALUATIONS);
227 
228         final double r3 =
229                         solver.solve(MAX_EVALUATIONS, l3Equation, searchInterval3[0],
230                                      searchInterval3[1], AllowedSolution.ANY_SIDE);
231 
232         this.l3Position = new Vector3D(r3, 0.0, 0.0);
233 
234         // L4
235         this.l4Position = new Vector3D(0.5 - mu, FastMath.sqrt(3) / 2, 0);
236 
237         // L5
238         this.l5Position = new Vector3D(0.5 - mu, -FastMath.sqrt(3) / 2, 0);
239     }
240 
241     /** Get the CR3BP mass ratio of the system mu2/(mu1+mu2).
242      * @return CR3BP mass ratio of the system mu2/(mu1+mu2)
243      */
244     public double getMassRatio() {
245         return mu;
246     }
247 
248     /** Get the CR3BP distance between the two bodies.
249      * @return CR3BP distance between the two bodies(m)
250      */
251     public double getDdim() {
252         return dDim;
253     }
254 
255     /** Get the CR3BP orbital velocity of m2.
256      * @return CR3BP orbital velocity of m2(m/s)
257      */
258     public double getVdim() {
259         return vDim;
260     }
261 
262     /** Get the CR3BP orbital period of m2 around m1.
263      * @return CR3BP orbital period of m2 around m1(s)
264      */
265     public double getTdim() {
266         return tDim;
267     }
268 
269     /** Get the name of the CR3BP system.
270      * @return name of the CR3BP system
271      */
272     public String getName() {
273         return name;
274     }
275 
276     /** Get the primary CelestialBody.
277      * @return primary CelestialBody
278      */
279     public CelestialBody getPrimary() {
280         return primaryBody;
281     }
282 
283     /** Get the secondary CelestialBody.
284      * @return secondary CelestialBody
285      */
286     public CelestialBody getSecondary() {
287         return secondaryBody;
288     }
289 
290     /** Get the CR3BP Rotating Frame.
291      * @return CR3BP Rotating Frame
292      */
293     public Frame getRotatingFrame() {
294         return rotatingFrame;
295     }
296 
297     /** Get the position of the Lagrangian point in the CR3BP Rotating frame.
298      * @param lagrangianPoint Lagrangian Point to consider
299      * @return position of the Lagrangian point in the CR3BP Rotating frame (-)
300      */
301     public Vector3D getLPosition(final LagrangianPoints lagrangianPoint) {
302 
303         final Vector3D lPosition;
304 
305         switch (lagrangianPoint) {
306 
307             case L1:
308                 lPosition = l1Position;
309                 break;
310 
311             case L3:
312                 lPosition = l3Position;
313                 break;
314 
315             case L4:
316                 lPosition = l4Position;
317                 break;
318 
319             case L5:
320                 lPosition = l5Position;
321                 break;
322 
323             default:
324                 lPosition = l2Position;
325                 break;
326 
327         }
328         return lPosition;
329     }
330 
331     /**
332      * Get the position of the Lagrangian point in the CR3BP Rotating frame.
333      * @param lagrangianPoint Lagrangian Point to consider
334      * @return Distance between a Lagrangian Point and its closest primary.
335      */
336     public double getGamma(final LagrangianPoints lagrangianPoint) {
337 
338         final double gamma;
339 
340         switch (lagrangianPoint) {
341 
342             case L1:
343                 gamma = gamma1;
344                 break;
345 
346             case L2:
347                 gamma = gamma2;
348                 break;
349 
350             case L3:
351                 gamma = gamma3;
352                 break;
353 
354             default:
355                 gamma = 0;
356         }
357         return gamma;
358     }
359 
360     /** Get the PVCoordinates from normalized units to standard units in an output frame.
361      * @param pv0 Normalized PVCoordinates in the rotating frame
362      * @param date Date of the transform
363      * @param outputFrame Frame in which the output PVCoordinates will be
364      * @return PVCoordinates in the output frame [m,m/s]
365      */
366     private PVCoordinates getRealPV(final PVCoordinates pv0, final AbsoluteDate date, final Frame outputFrame) {
367         // 1.   Dimensionalize  the  primary-centered  rotating  state  using  the  instantaneously
368         //      defined characteristic quantities
369         // 2.   Apply the transformation to primary inertial frame
370         // 3.   Apply the transformation to output frame
371 
372         final Frame primaryInertialFrame = primaryBody.getInertiallyOrientedFrame();
373         final Vector3D pv21 = secondaryBody.getPosition(date, primaryInertialFrame);
374 
375         // Distance and Velocity to dimensionalize the state vector
376         final double dist12 = pv21.getNorm();
377         final double vCircular  = FastMath.sqrt(primaryBody.getGM() / dist12);
378 
379         // Dimensionalized state vector centered on primary body
380         final PVCoordinates pvDim = new PVCoordinates(pv0.getPosition().scalarMultiply(dist12),
381                                                       pv0.getVelocity().scalarMultiply(vCircular));
382 
383         // Transformation between rotating frame and the primary inertial
384         final Transform rotatingToPrimaryInertial = rotatingFrame.getTransformTo(primaryInertialFrame, date);
385 
386         // State vector in the primary inertial frame
387         final PVCoordinates pv2 = rotatingToPrimaryInertial.transformPVCoordinates(pvDim);
388 
389 
390         // Transformation between primary inertial frame and the output frame
391         final Transform primaryInertialToOutputFrame = primaryInertialFrame.getTransformTo(outputFrame, date);
392 
393         return primaryInertialToOutputFrame.transformPVCoordinates(pv2);
394     }
395 
396     /** Get the AbsolutePVCoordinates from normalized units to standard units in an output frame.
397      * This method ensure the constituency of the date of returned AbsolutePVCoordinate, especially
398      * when apv0 is the result of a propagation in CR3BP normalized model.
399      * @param apv0 Normalized AbsolutePVCoordinates in the rotating frame
400      * @param initialDate Date of the at the beginning of the propagation
401      * @param outputFrame Frame in which the output AbsolutePVCoordinates will be
402      * @return AbsolutePVCoordinates in the output frame [m,m/s]
403      */
404     public AbsolutePVCoordinates getRealAPV(final AbsolutePVCoordinates apv0, final AbsoluteDate initialDate, final Frame outputFrame) {
405 
406         final double duration = apv0.getDate().durationFrom(initialDate) * tDim / (2 * FastMath.PI);
407         final AbsoluteDate date = initialDate.shiftedBy(duration);
408 
409         // PVCoordinate in the output frame
410         final PVCoordinates pv3 = getRealPV(apv0.getPVCoordinates(), date, outputFrame);
411 
412         return new AbsolutePVCoordinates(outputFrame, date, pv3);
413     }
414 
415 }