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