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