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.attitudes;
18  
19  
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.hipparchus.Field;
24  import org.hipparchus.CalculusFieldElement;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Rotation;
27  import org.hipparchus.geometry.euclidean.threed.RotationOrder;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.hipparchus.ode.events.Action;
30  import org.hipparchus.ode.nonstiff.AdaptiveStepsizeIntegrator;
31  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
32  import org.hipparchus.util.Decimal64Field;
33  import org.hipparchus.util.FastMath;
34  import org.junit.Assert;
35  import org.junit.Before;
36  import org.junit.Test;
37  import org.orekit.Utils;
38  import org.orekit.bodies.CelestialBodyFactory;
39  import org.orekit.bodies.GeodeticPoint;
40  import org.orekit.bodies.OneAxisEllipsoid;
41  import org.orekit.errors.OrekitException;
42  import org.orekit.errors.OrekitMessages;
43  import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
44  import org.orekit.forces.gravity.potential.GravityFieldFactory;
45  import org.orekit.forces.gravity.potential.ICGEMFormatReader;
46  import org.orekit.frames.FramesFactory;
47  import org.orekit.frames.LOFType;
48  import org.orekit.frames.TopocentricFrame;
49  import org.orekit.orbits.FieldKeplerianOrbit;
50  import org.orekit.orbits.FieldOrbit;
51  import org.orekit.orbits.KeplerianOrbit;
52  import org.orekit.orbits.Orbit;
53  import org.orekit.propagation.FieldPropagator;
54  import org.orekit.propagation.FieldSpacecraftState;
55  import org.orekit.propagation.Propagator;
56  import org.orekit.propagation.SpacecraftState;
57  import org.orekit.propagation.analytical.EcksteinHechlerPropagator;
58  import org.orekit.propagation.analytical.FieldEcksteinHechlerPropagator;
59  import org.orekit.propagation.analytical.KeplerianPropagator;
60  import org.orekit.propagation.events.DateDetector;
61  import org.orekit.propagation.events.EclipseDetector;
62  import org.orekit.propagation.events.ElevationDetector;
63  import org.orekit.propagation.events.EventDetector;
64  import org.orekit.propagation.events.EventsLogger;
65  import org.orekit.propagation.events.handlers.ContinueOnEvent;
66  import org.orekit.propagation.numerical.NumericalPropagator;
67  import org.orekit.propagation.sampling.FieldOrekitFixedStepHandler;
68  import org.orekit.propagation.sampling.OrekitFixedStepHandler;
69  import org.orekit.time.AbsoluteDate;
70  import org.orekit.time.FieldAbsoluteDate;
71  import org.orekit.time.TimeScalesFactory;
72  import org.orekit.utils.AngularDerivativesFilter;
73  import org.orekit.utils.Constants;
74  import org.orekit.utils.FieldPVCoordinates;
75  import org.orekit.utils.IERSConventions;
76  import org.orekit.utils.PVCoordinates;
77  import org.orekit.utils.PVCoordinatesProvider;
78  
79  public class AttitudesSequenceTest {
80  
81      private AbsoluteDate lastChange;
82      private boolean inEclipse;
83  
84      @Test
85      public void testDayNightSwitch() {
86          //  Initial state definition : date, orbit
87          final AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, TimeScalesFactory.getUTC());
88          final Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
89          final Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
90          final Orbit initialOrbit = new KeplerianOrbit(new PVCoordinates(position, velocity),
91                                                        FramesFactory.getEME2000(), initialDate,
92                                                        Constants.EIGEN5C_EARTH_MU);
93  
94          final
95  
96          // Attitudes sequence definition
97          EventsLogger logger = new EventsLogger();
98          final AttitudesSequence attitudesSequence = new AttitudesSequence();
99          final AttitudeProvider dayObservationLaw = new LofOffset(initialOrbit.getFrame(), LOFType.LVLH_CCSDS,
100                                                                  RotationOrder.XYZ, FastMath.toRadians(20), FastMath.toRadians(40), 0);
101         final AttitudeProvider nightRestingLaw   = new LofOffset(initialOrbit.getFrame(), LOFType.LVLH_CCSDS);
102         final PVCoordinatesProvider sun = CelestialBodyFactory.getSun();
103         final EclipseDetector ed =
104                         new EclipseDetector(sun, 696000000.,
105                                             new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
106                                                                  0.0,
107                                                                  FramesFactory.getGTOD(IERSConventions.IERS_2010, true))).
108                 withHandler(new ContinueOnEvent<EclipseDetector>() {
109                     public Action eventOccurred(final SpacecraftState s, final EclipseDetector d, final boolean increasing) {
110                         setInEclipse(s.getDate(), !increasing);
111                         return Action.RESET_STATE;
112                     }
113                 });
114         final EventDetector monitored = logger.monitorDetector(ed);
115         final Handler dayToNightHandler = new Handler(dayObservationLaw, nightRestingLaw);
116         final Handler nightToDayHandler = new Handler(nightRestingLaw, dayObservationLaw);
117         attitudesSequence.addSwitchingCondition(dayObservationLaw, nightRestingLaw,
118                                                 monitored, false, true, 300.0,
119                                                 AngularDerivativesFilter.USE_RRA, dayToNightHandler);
120         attitudesSequence.addSwitchingCondition(nightRestingLaw, dayObservationLaw,
121                                                 monitored, true, false, 300.0,
122                                                 AngularDerivativesFilter.USE_RRA, nightToDayHandler);
123         SpacecraftState initialState = new SpacecraftState(initialOrbit);
124         initialState = initialState.addAdditionalState("fortyTwo", 42.0);
125         if (ed.g(initialState) >= 0) {
126             // initial position is in daytime
127             setInEclipse(initialDate, false);
128             attitudesSequence.resetActiveProvider(dayObservationLaw);
129         } else {
130             // initial position is in nighttime
131             setInEclipse(initialDate, true);
132             attitudesSequence.resetActiveProvider(nightRestingLaw);
133         }
134 
135         // Propagator : consider the analytical Eckstein-Hechler model
136         final Propagator propagator = new EcksteinHechlerPropagator(initialOrbit, attitudesSequence,
137                                                                     Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
138                                                                     Constants.EIGEN5C_EARTH_MU,  Constants.EIGEN5C_EARTH_C20,
139                                                                     Constants.EIGEN5C_EARTH_C30, Constants.EIGEN5C_EARTH_C40,
140                                                                     Constants.EIGEN5C_EARTH_C50, Constants.EIGEN5C_EARTH_C60);
141 
142         // Register the switching events to the propagator
143         attitudesSequence.registerSwitchEvents(propagator);
144 
145         propagator.setStepHandler(60.0, new OrekitFixedStepHandler() {
146             public void handleStep(SpacecraftState currentState) {
147                 // the Earth position in spacecraft frame should be along spacecraft Z axis
148                 // during night time and away from it during day time due to roll and pitch offsets
149                 final Vector3D earth = currentState.toTransform().transformPosition(Vector3D.ZERO);
150                 final double pointingOffset = Vector3D.angle(earth, Vector3D.PLUS_K);
151 
152                 // the g function is the eclipse indicator, its an angle between Sun and Earth limb,
153                 // positive when Sun is outside of Earth limb, negative when Sun is hidden by Earth limb
154                 final double eclipseAngle = ed.g(currentState);
155 
156                 if (currentState.getDate().durationFrom(lastChange) > 300) {
157                     if (inEclipse) {
158                         Assert.assertTrue(eclipseAngle <= 0);
159                         Assert.assertEquals(0.0, pointingOffset, 1.0e-6);
160                     } else {
161                         Assert.assertTrue(eclipseAngle >= 0);
162                         Assert.assertEquals(0.767215, pointingOffset, 1.0e-6);
163                     }
164                 } else {
165                     // we are in transition
166                     Assert.assertTrue(pointingOffset + " " + (0.767215 - pointingOffset),
167                                       pointingOffset <= 0.7672155);
168                 }
169             }
170         });
171 
172         // Propagate from the initial date for the fixed duration
173         propagator.propagate(initialDate.shiftedBy(12600.));
174 
175         // as we have 2 switch events (even if they share the same underlying event detector),
176         // and these events are triggered at both eclipse entry and exit, we get 8
177         // raw events on 2 orbits
178         Assert.assertEquals(8, logger.getLoggedEvents().size());
179 
180         // we have 4 attitudes switch on 2 orbits, 2 of each type
181         Assert.assertEquals(2, dayToNightHandler.dates.size());
182         Assert.assertEquals(2, nightToDayHandler.dates.size());
183 
184     }
185 
186     @Test
187     public void testDayNightSwitchField() {
188         doTestDayNightSwitchField(Decimal64Field.getInstance());
189     }
190 
191     private <T extends CalculusFieldElement<T>> void doTestDayNightSwitchField(final Field<T> field)
192         {
193 
194         //  Initial state definition : date, orbit
195         final FieldAbsoluteDate<T> initialDate = new FieldAbsoluteDate<>(field, 2004, 01, 01, 23, 30, 00.000, TimeScalesFactory.getUTC());
196         final FieldVector3D<T> position  = new FieldVector3D<>(field,
197                                                                new Vector3D(-6142438.668, 3492467.560, -25767.25680));
198         final FieldVector3D<T> velocity  = new FieldVector3D<>(field,
199                                                                new Vector3D(505.8479685, 942.7809215, 7435.922231));
200         final FieldOrbit<T> initialOrbit = new FieldKeplerianOrbit<>(new FieldPVCoordinates<>(position, velocity),
201                                                                      FramesFactory.getEME2000(), initialDate,
202                                                                      field.getZero().add(Constants.EIGEN5C_EARTH_MU));
203 
204         // Attitudes sequence definition
205         EventsLogger logger = new EventsLogger();
206         final AttitudesSequence attitudesSequence = new AttitudesSequence();
207         final AttitudeProvider dayObservationLaw = new LofOffset(initialOrbit.getFrame(), LOFType.LVLH_CCSDS,
208                                                                  RotationOrder.XYZ, FastMath.toRadians(20), FastMath.toRadians(40), 0);
209         final AttitudeProvider nightRestingLaw   = new LofOffset(initialOrbit.getFrame(), LOFType.LVLH_CCSDS);
210         final PVCoordinatesProvider sun = CelestialBodyFactory.getSun();
211         final EclipseDetector ed =
212                 new EclipseDetector(sun, 696000000.,
213                                     new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
214                                                          0.0,
215                                                          FramesFactory.getGTOD(IERSConventions.IERS_2010, true))).
216                 withHandler(new ContinueOnEvent<EclipseDetector>() {
217                     int count = 0;
218                     public Action eventOccurred(final SpacecraftState s,
219                                                              final EclipseDetector d,
220                                                              final boolean increasing) {
221                         setInEclipse(s.getDate(), !increasing);
222                         if (count++ == 7) {
223                             return Action.STOP;
224                         } else {
225                             switch (count % 3) {
226                                 case 0 :
227                                     return Action.CONTINUE;
228                                 case 1 :
229                                     return Action.RESET_DERIVATIVES;
230                                 default :
231                                     return Action.RESET_STATE;
232                             }
233                         }
234                     }
235                 });
236         final EventDetector monitored = logger.monitorDetector(ed);
237         final Handler dayToNightHandler = new Handler(dayObservationLaw, nightRestingLaw);
238         final Handler nightToDayHandler = new Handler(nightRestingLaw, dayObservationLaw);
239         attitudesSequence.addSwitchingCondition(dayObservationLaw, nightRestingLaw,
240                                                 monitored, false, true, 300.0,
241                                                 AngularDerivativesFilter.USE_RRA, dayToNightHandler);
242         attitudesSequence.addSwitchingCondition(nightRestingLaw, dayObservationLaw,
243                                                 monitored, true, false, 300.0,
244                                                 AngularDerivativesFilter.USE_RRA, nightToDayHandler);
245         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(initialOrbit);
246         initialState = initialState.addAdditionalState("fortyTwo", field.getZero().add(42.0));
247         if (ed.g(initialState.toSpacecraftState()) >= 0) {
248             // initial position is in daytime
249             setInEclipse(initialDate.toAbsoluteDate(), false);
250             attitudesSequence.resetActiveProvider(dayObservationLaw);
251         } else {
252             // initial position is in nighttime
253             setInEclipse(initialDate.toAbsoluteDate(), true);
254             attitudesSequence.resetActiveProvider(nightRestingLaw);
255         }
256 
257         // Propagator : consider the analytical Eckstein-Hechler model
258         final FieldPropagator<T> propagator = new FieldEcksteinHechlerPropagator<T>(initialOrbit, attitudesSequence,
259                                                                                     Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
260                                                                                     field.getZero().add(Constants.EIGEN5C_EARTH_MU), 
261                                                                                     Constants.EIGEN5C_EARTH_C20,
262                                                                                     Constants.EIGEN5C_EARTH_C30, Constants.EIGEN5C_EARTH_C40,
263                                                                                     Constants.EIGEN5C_EARTH_C50, Constants.EIGEN5C_EARTH_C60);
264 
265         // Register the switching events to the propagator
266         attitudesSequence.registerSwitchEvents(field, propagator);
267 
268         propagator.setStepHandler(field.getZero().add(60.0), new FieldOrekitFixedStepHandler<T>() {
269             public void handleStep(FieldSpacecraftState<T> currentState) {
270                 // the Earth position in spacecraft frame should be along spacecraft Z axis
271                 // during night time and away from it during day time due to roll and pitch offsets
272                 final FieldVector3D<T> earth = currentState.toTransform().transformPosition(Vector3D.ZERO);
273                 final T pointingOffset = FieldVector3D.angle(earth, Vector3D.PLUS_K);
274 
275                 // the g function is the eclipse indicator, its an angle between Sun and Earth limb,
276                 // positive when Sun is outside of Earth limb, negative when Sun is hidden by Earth limb
277                 final double eclipseAngle = ed.g(currentState.toSpacecraftState());
278 
279                 if (currentState.getDate().durationFrom(lastChange).getReal() > 300) {
280                     if (inEclipse) {
281                         Assert.assertTrue(eclipseAngle <= 0);
282                         Assert.assertEquals(0.0, pointingOffset.getReal(), 1.0e-6);
283                     } else {
284                         Assert.assertTrue(eclipseAngle >= 0);
285                         Assert.assertEquals(0.767215, pointingOffset.getReal(), 1.0e-6);
286                     }
287                 } else {
288                     // we are in transition
289                     Assert.assertTrue(pointingOffset.getReal() + " " + (0.767215 - pointingOffset.getReal()),
290                                       pointingOffset.getReal() <= 0.7672155);
291                 }
292             }
293         });
294 
295         // Propagate from the initial date for the fixed duration
296         propagator.propagate(initialDate.shiftedBy(12600.));
297 
298         // as we have 2 switch events (even if they share the same underlying event detector),
299         // and these events are triggered at both eclipse entry and exit, we get 8
300         // raw events on 2 orbits
301         Assert.assertEquals(8, logger.getLoggedEvents().size());
302 
303         // we have 4 attitudes switch on 2 orbits, 2 of each type
304         Assert.assertEquals(2, dayToNightHandler.dates.size());
305         Assert.assertEquals(2, nightToDayHandler.dates.size());
306 
307     }
308 
309     @Test
310     public void testBackwardPropagation() {
311 
312         //  Initial state definition : date, orbit
313         final AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, TimeScalesFactory.getUTC());
314         final Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
315         final Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
316         final Orbit initialOrbit = new KeplerianOrbit(new PVCoordinates(position, velocity),
317                                                       FramesFactory.getEME2000(), initialDate,
318                                                       Constants.EIGEN5C_EARTH_MU);
319 
320         final AttitudesSequence attitudesSequence = new AttitudesSequence();
321         final AttitudeProvider past    = new InertialProvider(Rotation.IDENTITY);
322         final AttitudeProvider current = new InertialProvider(Rotation.IDENTITY);
323         final AttitudeProvider future  = new InertialProvider(Rotation.IDENTITY);
324         final Handler handler = new Handler(current, past);
325         attitudesSequence.addSwitchingCondition(past, current,
326                                                 new DateDetector(initialDate.shiftedBy(-500.0)),
327                                                 true, false, 10.0, AngularDerivativesFilter.USE_R, handler);
328         attitudesSequence.addSwitchingCondition(current, future,
329                                                 new DateDetector(initialDate.shiftedBy(+500.0)),
330                                                 true, false, 10.0, AngularDerivativesFilter.USE_R, null);
331         attitudesSequence.resetActiveProvider(current);
332 
333         SpacecraftState initialState = new SpacecraftState(initialOrbit);
334         initialState = initialState.addAdditionalState("fortyTwo", 42.0);
335         final Propagator propagator = new EcksteinHechlerPropagator(initialOrbit, attitudesSequence,
336                                                                     Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
337                                                                     Constants.EIGEN5C_EARTH_MU,  Constants.EIGEN5C_EARTH_C20,
338                                                                     Constants.EIGEN5C_EARTH_C30, Constants.EIGEN5C_EARTH_C40,
339                                                                     Constants.EIGEN5C_EARTH_C50, Constants.EIGEN5C_EARTH_C60);
340         propagator.resetInitialState(initialState);
341         Assert.assertEquals(42.0, propagator.getInitialState().getAdditionalState("fortyTwo")[0], 1.0e-10);
342 
343         // Register the switching events to the propagator
344         attitudesSequence.registerSwitchEvents(propagator);
345 
346         SpacecraftState finalState = propagator.propagate(initialDate.shiftedBy(-10000.0));
347         Assert.assertEquals(42.0, finalState.getAdditionalState("fortyTwo")[0], 1.0e-10);
348         Assert.assertEquals(1, handler.dates.size());
349         Assert.assertEquals(-500.0, handler.dates.get(0).durationFrom(initialDate), 1.0e-3);
350         Assert.assertEquals(-490.0, finalState.getDate().durationFrom(initialDate), 1.0e-3);
351 
352     }
353 
354     @Test
355     public void testTooShortTransition() {
356         double threshold      = 1.5;
357         double transitionTime = 0.5;
358         try {
359             new AttitudesSequence().addSwitchingCondition(new InertialProvider(Rotation.IDENTITY),
360                                                           new InertialProvider(Rotation.IDENTITY),
361                                                           new DateDetector(1000.0, threshold,
362                                                                            AbsoluteDate.J2000_EPOCH),
363                                                           true, false, transitionTime,
364                                                           AngularDerivativesFilter.USE_R, null);
365             Assert.fail("an exception should have been thrown");
366         } catch (OrekitException oe) {
367             Assert.assertEquals(OrekitMessages.TOO_SHORT_TRANSITION_TIME_FOR_ATTITUDES_SWITCH,
368                                 oe.getSpecifier());
369             Assert.assertEquals(transitionTime, ((Double) oe.getParts()[0]).doubleValue(), 1.0e-10);
370             Assert.assertEquals(threshold,      ((Double) oe.getParts()[1]).doubleValue(), 1.0e-10);
371         }
372     }
373 
374     @Test
375     public void testOutOfSyncCalls() {
376         //  Initial state definition : date, orbit
377         final AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, TimeScalesFactory.getUTC());
378         final Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
379         final Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
380         final Orbit initialOrbit = new KeplerianOrbit(new PVCoordinates(position, velocity),
381                                                       FramesFactory.getEME2000(), initialDate,
382                                                       Constants.EIGEN5C_EARTH_MU);
383 
384         final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
385                                                             Constants.WGS84_EARTH_FLATTENING,
386                                                             FramesFactory.getITRF(IERSConventions.IERS_2010, true));
387         final TopocentricFrame volgograd = new TopocentricFrame(earth,
388                                                                 new GeodeticPoint(FastMath.toRadians(48.7),
389                                                                                   FastMath.toRadians(44.5),
390                                                                                   24.0),
391                                                              "Волгоград");
392         final AttitudesSequence attitudesSequence = new AttitudesSequence();
393         final double            transitionTime    = 250.0;
394         final AttitudeProvider  nadirPointing     = new NadirPointing(initialOrbit.getFrame(), earth);
395         final AttitudeProvider  targetPointing    = new TargetPointing(initialOrbit.getFrame(), volgograd.getPoint(), earth);
396         final ElevationDetector eventDetector     = new ElevationDetector(volgograd).
397                                                     withConstantElevation(FastMath.toRadians(5.0)).
398                                                     withHandler(new ContinueOnEvent<>());
399         final Handler nadirToTarget =  new Handler(nadirPointing, targetPointing);
400         attitudesSequence.addSwitchingCondition(nadirPointing, targetPointing, eventDetector,
401                                                 true, false, transitionTime, AngularDerivativesFilter.USE_RR,
402                                                 nadirToTarget);
403         final Handler targetToNadir =  new Handler(targetPointing, nadirPointing);
404         attitudesSequence.addSwitchingCondition(targetPointing, nadirPointing, eventDetector,
405                                                 false, true, transitionTime, AngularDerivativesFilter.USE_RR,
406                                                 targetToNadir);
407         final double[][] tolerance = NumericalPropagator.tolerances(10.0, initialOrbit, initialOrbit.getType());
408         final AdaptiveStepsizeIntegrator integrator = new DormandPrince853Integrator(0.001, 300.0, tolerance[0], tolerance[1]);
409         final NumericalPropagator propagator = new NumericalPropagator(integrator);
410         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("g007_eigen_05c_coef", false));
411         propagator.addForceModel(new HolmesFeatherstoneAttractionModel(earth.getBodyFrame(),
412                                                                        GravityFieldFactory.getNormalizedProvider(8, 8)));
413         propagator.setInitialState(new SpacecraftState(initialOrbit,
414                                                        nadirPointing.getAttitude(initialOrbit,
415                                                                                  initialOrbit.getDate(),
416                                                                                  initialOrbit.getFrame())));
417         propagator.setAttitudeProvider(attitudesSequence);
418         attitudesSequence.registerSwitchEvents(propagator);
419         propagator.setStepHandler(10, state -> {
420 
421             Attitude nadirAttitude  = nadirPointing.getAttitude(state.getOrbit(), state.getDate(), state.getFrame());
422             Attitude targetAttitude = targetPointing.getAttitude(state.getOrbit(), state.getDate(), state.getFrame());
423             Attitude stateAttitude  = state.getAttitude();
424 
425             if (nadirToTarget.dates.isEmpty() || state.getDate().durationFrom(nadirToTarget.dates.get(0)) < 0) {
426                 // we are stabilized in nadir pointing, before first switch
427                 checkEqualAttitudes(nadirAttitude, stateAttitude);
428             } else if (state.getDate().durationFrom(nadirToTarget.dates.get(0)) <= transitionTime) {
429                 // we are in transition from nadir to target
430                 checkBetweenAttitudes(nadirAttitude, targetAttitude, stateAttitude);
431             } else if (targetToNadir.dates.isEmpty() || state.getDate().durationFrom(targetToNadir.dates.get(0)) < 0) {
432                 // we are stabilized in target pointing between the two switches
433                 checkEqualAttitudes(targetAttitude, stateAttitude);
434             } else if (state.getDate().durationFrom(targetToNadir.dates.get(0)) <= transitionTime) {
435                 // we are in transition from target to nadir
436                 checkBetweenAttitudes(targetAttitude, nadirAttitude, stateAttitude);
437             } else {
438                 // we are stabilized back in nadir pointing, after second switch
439                 checkEqualAttitudes(nadirAttitude, stateAttitude);
440             }
441 
442         });
443         propagator.propagate(initialDate.shiftedBy(6000));
444 
445     }
446 
447     /**
448      * this test have been completed to test the issue 552 fix
449      */
450     @Test
451     public void testResetDuringTransitionForward() {
452         //  Initial state definition : date, orbit
453         final AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, TimeScalesFactory.getUTC());
454         final Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
455         final Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
456         final Orbit initialOrbit = new KeplerianOrbit(new PVCoordinates(position, velocity),
457                                                       FramesFactory.getEME2000(), initialDate,
458                                                       Constants.EIGEN5C_EARTH_MU);
459 
460         final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
461                                                             Constants.WGS84_EARTH_FLATTENING,
462                                                             FramesFactory.getITRF(IERSConventions.IERS_2010, true));
463         final TopocentricFrame volgograd = new TopocentricFrame(earth,
464                                                                 new GeodeticPoint(FastMath.toRadians(48.7),
465                                                                                   FastMath.toRadians(44.5),
466                                                                                   24.0),
467                                                              "Волгоград");
468         final AttitudesSequence attitudesSequence = new AttitudesSequence();
469         final double            transitionTime    = 250.0;
470         final AttitudeProvider  nadirPointing     = new NadirPointing(initialOrbit.getFrame(), earth);
471         final AttitudeProvider  targetPointing    = new TargetPointing(initialOrbit.getFrame(), volgograd.getPoint(), earth);
472         final ElevationDetector eventDetector     = new ElevationDetector(volgograd).
473                                                     withConstantElevation(FastMath.toRadians(5.0)).
474                                                     withHandler(new ContinueOnEvent<>());
475         final List<AbsoluteDate> nadirToTarget = new ArrayList<>();
476         attitudesSequence.addSwitchingCondition(nadirPointing, targetPointing, eventDetector,
477                                                 true, false, transitionTime, AngularDerivativesFilter.USE_RR,
478                                                 (previous, next, state) -> nadirToTarget.add(state.getDate()));
479         final double[][] tolerance = NumericalPropagator.tolerances(10.0, initialOrbit, initialOrbit.getType());
480         final AdaptiveStepsizeIntegrator integrator = new DormandPrince853Integrator(0.001, 300.0, tolerance[0], tolerance[1]);
481         final NumericalPropagator propagator = new NumericalPropagator(integrator);
482         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("g007_eigen_05c_coef", false));
483         propagator.addForceModel(new HolmesFeatherstoneAttractionModel(earth.getBodyFrame(),
484                                                                        GravityFieldFactory.getNormalizedProvider(8, 8)));
485         propagator.setInitialState(new SpacecraftState(initialOrbit,
486                                                        nadirPointing.getAttitude(initialOrbit,
487                                                                                  initialOrbit.getDate(),
488                                                                                  initialOrbit.getFrame())));
489         propagator.setAttitudeProvider(attitudesSequence);
490         attitudesSequence.registerSwitchEvents(propagator);
491         propagator.propagate(initialDate.shiftedBy(6000));
492 
493         // check that if we restart a forward propagation from an intermediate state
494         // we properly get an interpolated attitude despite we missed the event trigger
495 
496         final AbsoluteDate midTransition = nadirToTarget.get(0).shiftedBy(0.5 * transitionTime);
497         SpacecraftState state   = propagator.propagate(midTransition.shiftedBy(-60), midTransition);
498         Rotation nadirR  = nadirPointing.getAttitude(state.getOrbit(), state.getDate(), state.getFrame()).getRotation();
499         Rotation targetR = targetPointing.getAttitude(state.getOrbit(), state.getDate(), state.getFrame()).getRotation();
500         final double reorientationAngle = Rotation.distance(nadirR, targetR);
501         Assert.assertEquals(0.5 * reorientationAngle,
502                             Rotation.distance(state.getAttitude().getRotation(), nadirR),
503                             0.03 * reorientationAngle);
504         
505         // check that if we restart a forward propagation from an intermediate state
506         // we properly get the "after" attitude law despite we missed the event trigger
507         // This check have been added to the test after the issue #552 fix
508         
509         final AbsoluteDate afterTransition = midTransition.shiftedBy(transitionTime);
510         state = propagator.propagate(midTransition, afterTransition);
511         targetR = targetPointing.getAttitude(state.getOrbit(), state.getDate(), state.getFrame()).getRotation();
512 
513         Assert.assertEquals(targetR.getQ0(),
514         					state.getAttitude().getRotation().getQ0(),
515         					1.0e-16);
516         Assert.assertEquals(targetR.getQ1(),
517 							state.getAttitude().getRotation().getQ1(),
518 							1.0e-16);
519         Assert.assertEquals(targetR.getQ2(),
520 							state.getAttitude().getRotation().getQ2(),
521 							1.0e-16);
522         Assert.assertEquals(targetR.getQ3(),
523 							state.getAttitude().getRotation().getQ3(),
524 							1.0e-16);
525     }
526 
527     @Test
528     public void testResetDuringTransitionBackward() {
529         //  Initial state definition : date, orbit
530         final AbsoluteDate initialDate = new AbsoluteDate(2004, 01, 01, 23, 30, 00.000, TimeScalesFactory.getUTC());
531         final Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
532         final Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
533         final Orbit initialOrbit = new KeplerianOrbit(new PVCoordinates(position, velocity),
534                                                       FramesFactory.getEME2000(), initialDate,
535                                                       Constants.EIGEN5C_EARTH_MU);
536 
537         final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
538                                                             Constants.WGS84_EARTH_FLATTENING,
539                                                             FramesFactory.getITRF(IERSConventions.IERS_2010, true));
540         final TopocentricFrame volgograd = new TopocentricFrame(earth,
541                                                                 new GeodeticPoint(FastMath.toRadians(48.7),
542                                                                                   FastMath.toRadians(44.5),
543                                                                                   24.0),
544                                                              "Волгоград");
545         final AttitudesSequence attitudesSequence = new AttitudesSequence();
546         final double            transitionTime    = 250.0;
547         final AttitudeProvider  nadirPointing     = new NadirPointing(initialOrbit.getFrame(), earth);
548         final AttitudeProvider  targetPointing    = new TargetPointing(initialOrbit.getFrame(), volgograd.getPoint(), earth);
549         final ElevationDetector eventDetector     = new ElevationDetector(volgograd).
550                                                     withConstantElevation(FastMath.toRadians(5.0)).
551                                                     withHandler(new ContinueOnEvent<>());
552         final List<AbsoluteDate> nadirToTarget = new ArrayList<>();
553         attitudesSequence.addSwitchingCondition(nadirPointing, targetPointing, eventDetector,
554                                                 true, false, transitionTime, AngularDerivativesFilter.USE_RR,
555                                                 (previous, next, state) -> nadirToTarget.add(state.getDate()));
556         final double[][] tolerance = NumericalPropagator.tolerances(10.0, initialOrbit, initialOrbit.getType());
557         final AdaptiveStepsizeIntegrator integrator = new DormandPrince853Integrator(0.001, 300.0, tolerance[0], tolerance[1]);
558         final NumericalPropagator propagator = new NumericalPropagator(integrator);
559         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("g007_eigen_05c_coef", false));
560         propagator.addForceModel(new HolmesFeatherstoneAttractionModel(earth.getBodyFrame(),
561                                                                        GravityFieldFactory.getNormalizedProvider(8, 8)));
562         propagator.setInitialState(new SpacecraftState(initialOrbit,
563                                                        nadirPointing.getAttitude(initialOrbit,
564                                                                                  initialOrbit.getDate(),
565                                                                                  initialOrbit.getFrame())));
566         propagator.setAttitudeProvider(attitudesSequence);
567         attitudesSequence.registerSwitchEvents(propagator);
568         propagator.propagate(initialDate.shiftedBy(6000));
569 
570         // check that if we restart a backward propagation from an intermediate state
571         // we properly get an interpolated attitude despite we missed the event trigger
572         final AbsoluteDate midTransition = nadirToTarget.get(0).shiftedBy(0.5 * transitionTime);
573         SpacecraftState state   = propagator.propagate(midTransition.shiftedBy(+60), midTransition);
574         Rotation nadirR  = nadirPointing.getAttitude(state.getOrbit(), state.getDate(), state.getFrame()).getRotation();
575         Rotation targetR = targetPointing.getAttitude(state.getOrbit(), state.getDate(), state.getFrame()).getRotation();
576         final double reorientationAngle = Rotation.distance(nadirR, targetR);
577         Assert.assertEquals(0.5 * reorientationAngle,
578                             Rotation.distance(state.getAttitude().getRotation(), targetR),
579                             0.03 * reorientationAngle);
580         
581 
582     }
583     
584     /**
585      * Test for the issue 551 fix
586      */
587     @Test 
588     public void testAnalyticalPropagatorTransition() {
589     	// Define initial state
590         final AbsoluteDate initialDate = new AbsoluteDate(2017, 03, 27, 0, 0, 00.000, TimeScalesFactory.getUTC());
591         final Vector3D position  = new Vector3D(-39098981.4866597, -15784239.3610601, 78908.2289853595);
592         final Vector3D velocity  = new Vector3D(1151.00321021175, -2851.14864755189, -2.02133248357321);
593         final Orbit initialOrbit = new KeplerianOrbit(new PVCoordinates(position, velocity),
594                                                       FramesFactory.getGCRF(), initialDate,
595                                                       Constants.WGS84_EARTH_MU);    
596         
597         // Define attitude laws
598         final AttitudeProvider before = new InertialProvider(new Rotation(0, 0, 0, 1, false));
599         final AttitudeProvider current = new InertialProvider(Rotation.IDENTITY);
600         final AttitudeProvider after = new InertialProvider(new Rotation(0, 0, 0, -1, false));
601 
602         // Define attitude sequence
603         final AbsoluteDate forwardSwitchDate = initialDate.shiftedBy(600);
604         final AbsoluteDate backwardSwitchDate = initialDate.shiftedBy(-600);
605         final DateDetector forwardSwitchDetector = new DateDetector(forwardSwitchDate).withHandler(new ContinueOnEvent<DateDetector>());
606         final DateDetector backwardSwitchDetector = new DateDetector(backwardSwitchDate).withHandler(new ContinueOnEvent<DateDetector>());
607         
608         // Initialize the attitude sequence
609         final AttitudesSequence attitudeSequence = new AttitudesSequence();
610         attitudeSequence.resetActiveProvider(current);
611         attitudeSequence.addSwitchingCondition(before, current, backwardSwitchDetector, true, true, 60, AngularDerivativesFilter.USE_RR, null);
612         attitudeSequence.addSwitchingCondition(current, after, forwardSwitchDetector, true, true, 60, AngularDerivativesFilter.USE_RR, null);
613         
614         // Initialize analytical propagator
615         Propagator propagator = new KeplerianPropagator(initialOrbit);
616         propagator.setAttitudeProvider(attitudeSequence);
617         attitudeSequence.registerSwitchEvents(propagator);
618         
619         SpacecraftState stateAfter = propagator.propagate(initialDate, initialDate.shiftedBy(1200));
620         SpacecraftState stateBefore = propagator.propagate(initialDate, initialDate.shiftedBy(-1200));
621         
622         // Check that the dates are correct
623         Assert.assertEquals(1200, stateAfter.getDate().durationFrom(initialDate), 1.0E-3);
624         Assert.assertEquals(-1200, stateBefore.getDate().durationFrom(initialDate), 1.0E-3);
625         
626         // Check that the attitudes are correct
627         Assert.assertEquals(before.getAttitude(stateBefore.getOrbit(), stateBefore.getDate(), stateBefore.getFrame()).getRotation().getQ0(),
628         		stateBefore.getAttitude().getRotation().getQ0(),
629         		1.0E-16);
630         Assert.assertEquals(before.getAttitude(stateBefore.getOrbit(), stateBefore.getDate(), stateBefore.getFrame()).getRotation().getQ1(),
631         		stateBefore.getAttitude().getRotation().getQ1(),
632         		1.0E-16);
633         Assert.assertEquals(before.getAttitude(stateBefore.getOrbit(), stateBefore.getDate(), stateBefore.getFrame()).getRotation().getQ2(),
634         		stateBefore.getAttitude().getRotation().getQ2(),
635         		1.0E-16);
636         Assert.assertEquals(before.getAttitude(stateBefore.getOrbit(), stateBefore.getDate(), stateBefore.getFrame()).getRotation().getQ3(),
637         		stateBefore.getAttitude().getRotation().getQ3(),
638         		1.0E-16);
639 
640         Assert.assertEquals(after.getAttitude(stateAfter.getOrbit(), stateAfter.getDate(), stateAfter.getFrame()).getRotation().getQ0(),
641         		stateAfter.getAttitude().getRotation().getQ0(),
642         		1.0E-16);
643         Assert.assertEquals(after.getAttitude(stateAfter.getOrbit(), stateAfter.getDate(), stateAfter.getFrame()).getRotation().getQ1(),
644         		stateAfter.getAttitude().getRotation().getQ1(),
645         		1.0E-16);
646         Assert.assertEquals(after.getAttitude(stateAfter.getOrbit(), stateAfter.getDate(), stateAfter.getFrame()).getRotation().getQ2(),
647         		stateAfter.getAttitude().getRotation().getQ2(),
648         		1.0E-16);
649         Assert.assertEquals(after.getAttitude(stateAfter.getOrbit(), stateAfter.getDate(), stateAfter.getFrame()).getRotation().getQ3(),
650         		stateAfter.getAttitude().getRotation().getQ3(),
651         		1.0E-16);
652     }
653 
654     private static class Handler implements AttitudesSequence.SwitchHandler {
655 
656         private AttitudeProvider   expectedPrevious;
657         private AttitudeProvider   expectedNext;
658         private List<AbsoluteDate> dates;
659 
660         public Handler(final AttitudeProvider expectedPrevious, final AttitudeProvider expectedNext) {
661             this.expectedPrevious = expectedPrevious;
662             this.expectedNext     = expectedNext;
663             this.dates            = new ArrayList<AbsoluteDate>();
664         }
665 
666         @Override
667         public void switchOccurred(AttitudeProvider previous, AttitudeProvider next,
668                                    SpacecraftState state) {
669             Assert.assertTrue(previous == expectedPrevious);
670             Assert.assertTrue(next     == expectedNext);
671             dates.add(state.getDate());
672         }
673 
674     }
675 
676     private void setInEclipse(AbsoluteDate lastChange, boolean inEclipse) {
677         this.lastChange = lastChange;
678         this.inEclipse = inEclipse;
679     }
680 
681     private void checkEqualAttitudes(final Attitude expected, final Attitude tested) {
682         Assert.assertEquals(0.0,
683                             Rotation.distance(expected.getRotation(), tested.getRotation()),
684                             1.0e-14);
685         Assert.assertEquals(0.0,
686                             Vector3D.distance(expected.getSpin(), tested.getSpin()),
687                             1.0e-11);
688         Assert.assertEquals(0.0,
689                             Vector3D.distance(expected.getRotationAcceleration(), tested.getRotationAcceleration()),
690                             1.0e-9);
691     }
692 
693     private void checkBetweenAttitudes(final Attitude limit1, final Attitude limit2, final Attitude tested) {
694         final Rotation r1 = limit1.getRotation();
695         final Rotation r2 = limit2.getRotation();
696         final Rotation t  = tested.getRotation();
697         final double reorientationAngle = Rotation.distance(r1, r2);
698         Assert.assertTrue(Rotation.distance(t, r1) < reorientationAngle);
699         Assert.assertTrue(Rotation.distance(t, r2) < reorientationAngle);
700     }
701 
702     @Before
703     public void setUp() {
704         Utils.setDataRoot("regular-data:potential");
705     }
706 
707 }
708