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.orbits;
18  
19  import org.hipparchus.geometry.euclidean.threed.Rotation;
20  import org.hipparchus.geometry.euclidean.threed.Vector3D;
21  import org.hipparchus.linear.RealMatrix;
22  import org.hipparchus.ode.events.Action;
23  import org.hipparchus.ode.nonstiff.AdaptiveStepsizeIntegrator;
24  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
25  import org.hipparchus.util.FastMath;
26  import org.orekit.attitudes.FrameAlignedProvider;
27  import org.orekit.bodies.CR3BPSystem;
28  import org.orekit.errors.OrekitException;
29  import org.orekit.errors.OrekitMessages;
30  import org.orekit.propagation.SpacecraftState;
31  import org.orekit.propagation.events.HaloXZPlaneCrossingDetector;
32  import org.orekit.propagation.numerical.NumericalPropagator;
33  import org.orekit.propagation.numerical.cr3bp.CR3BPForceModel;
34  import org.orekit.propagation.numerical.cr3bp.STMEquations;
35  import org.orekit.time.AbsoluteDate;
36  import org.orekit.utils.AbsolutePVCoordinates;
37  import org.orekit.utils.PVCoordinates;
38  
39  
40  /**
41   * Class implementing the differential correction method for Halo or Lyapunov
42   * Orbits. It is not a simple differential correction, it uses higher order
43   * terms to be more accurate and meet orbits requirements.
44   * @see "Three-dimensional, periodic, Halo Orbits by Kathleen Connor Howell, Stanford University"
45   * @author Vincent Mouraux
46   * @since 10.2
47   */
48  public class CR3BPDifferentialCorrection {
49  
50      /** Maximum number of iterations. */
51      private static final int MAX_ITER = 30;
52  
53      /** Max check interval for crossing plane. */
54      private static final double CROSSING_MAX_CHECK = 3600.0;
55  
56      /** Convergence tolerance for plane crossing time. */
57      private static final double CROSSING_TOLERANCE = 1.0e-10;
58  
59      /** Arbitrary start date. */
60      private static final AbsoluteDate START_DATE = AbsoluteDate.ARBITRARY_EPOCH;
61  
62      /** Boolean return true if the propagated trajectory crosses the plane. */
63      private boolean cross;
64  
65      /** first guess PVCoordinates of the point to start differential correction. */
66      private final PVCoordinates firstGuess;
67  
68      /** CR3BP System considered. */
69      private final CR3BPSystem syst;
70  
71      /** orbitalPeriodApprox Orbital Period of the firstGuess. */
72      private final double orbitalPeriodApprox;
73  
74      /** orbitalPeriod Orbital Period of the required orbit. */
75      private double orbitalPeriod;
76  
77      /** Simple Constructor.
78       * <p> Standard constructor using DormandPrince853 integrator for the differential correction </p>
79       * @param firstguess first guess PVCoordinates of the point to start differential correction
80       * @param syst CR3BP System considered
81       * @param orbitalPeriod Orbital Period of the required orbit
82       */
83      public CR3BPDifferentialCorrection(final PVCoordinates firstguess,
84                                         final CR3BPSystem syst, final double orbitalPeriod) {
85          this.firstGuess = firstguess;
86          this.syst = syst;
87          this.orbitalPeriodApprox = orbitalPeriod;
88  
89      }
90  
91      /** Build the propagator.
92       * @return propagator
93       * @since 11.1
94       */
95      private NumericalPropagator buildPropagator() {
96  
97          // Adaptive stepsize boundaries
98          final double minStep = 1E-12;
99          final double maxstep = 0.001;
100 
101         // Integrator tolerances
102         final double positionTolerance = 1E-5;
103         final double velocityTolerance = 1E-5;
104         final double massTolerance = 1.0e-6;
105         final double[] vecAbsoluteTolerances = {positionTolerance, positionTolerance, positionTolerance, velocityTolerance, velocityTolerance, velocityTolerance, massTolerance};
106         final double[] vecRelativeTolerances = new double[vecAbsoluteTolerances.length];
107 
108         // Integrator definition
109         final AdaptiveStepsizeIntegrator integrator = new DormandPrince853Integrator(minStep, maxstep,
110                                                                                      vecAbsoluteTolerances,
111                                                                                      vecRelativeTolerances);
112 
113         // Propagator definition
114         final NumericalPropagator propagator =
115                         new NumericalPropagator(integrator, new FrameAlignedProvider(Rotation.IDENTITY, syst.getRotatingFrame()));
116 
117         // CR3BP has no defined orbit type
118         propagator.setOrbitType(null);
119 
120         // CR3BP has central Attraction
121         propagator.setIgnoreCentralAttraction(true);
122 
123         // Add CR3BP Force Model to the propagator
124         propagator.addForceModel(new CR3BPForceModel(syst));
125 
126         // Add event detector for crossing plane
127         propagator.addEventDetector(new HaloXZPlaneCrossingDetector(CROSSING_MAX_CHECK, CROSSING_TOLERANCE).
128                                     withHandler((state, detector, increasing) -> {
129                                         cross = true;
130                                         return Action.STOP;
131                                     }));
132 
133         return propagator;
134 
135     }
136 
137     /**
138      * Return the real starting PVCoordinates on the Libration orbit type
139      * after differential correction from a first guess.
140      * @param type libration orbit type
141      * @return pv Position-Velocity of the starting point on the Halo Orbit
142      */
143     public PVCoordinates compute(final LibrationOrbitType type) {
144         return type == LibrationOrbitType.HALO ? computeHalo() : computeLyapunov();
145     }
146 
147     /** Return the real starting PVCoordinates on the Halo orbit after differential correction from a first guess.
148      * @return pv Position-Velocity of the starting point on the Halo Orbit
149      */
150     private PVCoordinates computeHalo() {
151 
152         // Initializing PVCoordinates with first guess
153         PVCoordinates pvHalo = firstGuess;
154 
155         // Start a new differentially corrected propagation until it converges to a Halo Orbit
156         // Converge within 1E-8 tolerance and under 5 iterations
157         for (int iHalo = 0; iHalo < MAX_ITER; ++iHalo) {
158 
159             // SpacecraftState initialization
160             final AbsolutePVCoordinates initialAbsPVHalo = new AbsolutePVCoordinates(syst.getRotatingFrame(), START_DATE, pvHalo);
161             final SpacecraftState       initialStateHalo = new SpacecraftState(initialAbsPVHalo);
162 
163             // setup propagator
164             final NumericalPropagator propagator = buildPropagator();
165             final STMEquations        stm        = new STMEquations(syst);
166             propagator.addAdditionalDerivativesProvider(stm);
167             propagator.setInitialState(stm.setInitialPhi(initialStateHalo));
168 
169             // boolean changed to true by crossing XZ plane during propagation. Has to be true for the differential correction to converge
170             cross = false;
171 
172             // Propagate until trajectory crosses XZ Plane
173             final SpacecraftState finalStateHalo =
174                 propagator.propagate(START_DATE.shiftedBy(orbitalPeriodApprox));
175 
176             // Stops computation if trajectory did not cross XZ Plane after one full orbital period
177             if (cross == false) {
178                 throw new OrekitException(OrekitMessages.TRAJECTORY_NOT_CROSSING_XZPLANE);
179             }
180 
181             // Get State Transition Matrix phi
182             final RealMatrix phiHalo = stm.getStateTransitionMatrix(finalStateHalo);
183 
184             // Gap from desired X and Z axis velocity value ()
185             final double dvxf = -finalStateHalo.getPVCoordinates().getVelocity().getX();
186             final double dvzf = -finalStateHalo.getPVCoordinates().getVelocity().getZ();
187 
188             orbitalPeriod = 2 * finalStateHalo.getDate().durationFrom(START_DATE);
189 
190             if (FastMath.abs(dvxf) <= 1E-8 && FastMath.abs(dvzf) <= 1E-8) {
191                 break;
192             }
193 
194             // Y axis velocity
195             final double vy = finalStateHalo.getPVCoordinates().getVelocity().getY();
196 
197             // Spacecraft acceleration
198             final Vector3D acc  = finalStateHalo.getPVCoordinates().getAcceleration();
199             final double   accx = acc.getX();
200             final double   accz = acc.getZ();
201 
202             // Compute A coefficients
203             final double a11 = phiHalo.getEntry(3, 0) - accx * phiHalo.getEntry(1, 0) / vy;
204             final double a12 = phiHalo.getEntry(3, 4) - accx * phiHalo.getEntry(1, 4) / vy;
205             final double a21 = phiHalo.getEntry(5, 0) - accz * phiHalo.getEntry(1, 0) / vy;
206             final double a22 = phiHalo.getEntry(5, 4) - accz * phiHalo.getEntry(1, 4) / vy;
207 
208             // A determinant used for matrix inversion
209             final double aDet = a11 * a22 - a21 * a12;
210 
211             // Correction to apply to initial conditions
212             final double deltax0  = (a22 * dvxf - a12 * dvzf) / aDet;
213             final double deltavy0 = (-a21 * dvxf + a11 * dvzf) / aDet;
214 
215             // Computation of the corrected initial PVCoordinates
216             final double newx  = pvHalo.getPosition().getX() + deltax0;
217             final double newvy = pvHalo.getVelocity().getY() + deltavy0;
218 
219             pvHalo = new PVCoordinates(new Vector3D(newx,
220                                                     pvHalo.getPosition().getY(),
221                                                     pvHalo.getPosition().getZ()),
222                                        new Vector3D(pvHalo.getVelocity().getX(),
223                                                     newvy,
224                                                     pvHalo.getVelocity().getZ()));
225         }
226 
227         // Return
228         return pvHalo;
229     }
230 
231     /** Return the real starting PVCoordinates on the Lyapunov orbit after differential correction from a first guess.
232      * @return pv Position-Velocity of the starting point on the Lyapunov Orbit
233      */
234     public PVCoordinates computeLyapunov() {
235 
236         // Initializing PVCoordinates with first guess
237         PVCoordinates pvLyapunov = firstGuess;
238 
239         // Start a new differentially corrected propagation until it converges to a Halo Orbit
240         // Converge within 1E-8 tolerance and under 5 iterations
241         for (int iLyapunov = 0; iLyapunov < MAX_ITER; ++iLyapunov) {
242 
243             // SpacecraftState initialization
244             final AbsolutePVCoordinates initialAbsPVLyapunov = new AbsolutePVCoordinates(syst.getRotatingFrame(), START_DATE, pvLyapunov);
245             final SpacecraftState       initialStateLyapunov = new SpacecraftState(initialAbsPVLyapunov);
246 
247             // setup propagator
248             final NumericalPropagator propagator = buildPropagator();
249             final STMEquations        stm        = new STMEquations(syst);
250             propagator.addAdditionalDerivativesProvider(stm);
251             propagator.setInitialState(stm.setInitialPhi(initialStateLyapunov));
252 
253             // boolean changed to true by crossing XZ plane during propagation. Has to be true for the differential correction to converge
254             cross = false;
255 
256             // Propagate until trajectory crosses XZ Plane
257             final SpacecraftState finalStateLyapunov =
258                 propagator.propagate(START_DATE.shiftedBy(orbitalPeriodApprox));
259 
260             // Stops computation if trajectory did not cross XZ Plane after one full orbital period
261             if (cross == false) {
262                 throw new OrekitException(OrekitMessages.TRAJECTORY_NOT_CROSSING_XZPLANE);
263             }
264 
265             // Get State Transition Matrix phi
266             final RealMatrix phi = stm.getStateTransitionMatrix(finalStateLyapunov);
267 
268             // Gap from desired y position and x velocity value ()
269             final double dvxf = -finalStateLyapunov.getPVCoordinates().getVelocity().getX();
270 
271             orbitalPeriod = 2 * finalStateLyapunov.getDate().durationFrom(START_DATE);
272 
273             if (FastMath.abs(dvxf) <= 1E-14) {
274                 break;
275             }
276 
277             // Y axis velocity
278             final double vy = finalStateLyapunov.getPVCoordinates().getVelocity().getY();
279 
280             // Spacecraft acceleration
281             final double accy = finalStateLyapunov.getPVCoordinates().getAcceleration().getY();
282 
283             // Compute A coefficients
284             final double deltavy0 = dvxf / (phi.getEntry(3, 4) - accy * phi.getEntry(1, 4) / vy);
285 
286             // Computation of the corrected initial PVCoordinates
287             final double newvy = pvLyapunov.getVelocity().getY() + deltavy0;
288 
289             pvLyapunov = new PVCoordinates(new Vector3D(pvLyapunov.getPosition().getX(),
290                                                         pvLyapunov.getPosition().getY(),
291                                                         0),
292                                            new Vector3D(pvLyapunov.getVelocity().getX(),
293                                                         newvy,
294                                                         0));
295 
296         }
297 
298         // Return
299         return pvLyapunov;
300     }
301 
302     /** Get the orbital period of the required orbit.
303      * @return the orbitalPeriod
304      */
305     public double getOrbitalPeriod() {
306         return orbitalPeriod;
307     }
308 
309 }