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.propagation;
18  
19  
20  import java.io.ByteArrayInputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.IOException;
23  import java.io.ObjectInputStream;
24  import java.io.ObjectOutputStream;
25  import java.text.ParseException;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.hipparchus.analysis.polynomials.PolynomialFunction;
32  import org.hipparchus.exception.LocalizedCoreFormats;
33  import org.hipparchus.exception.MathIllegalStateException;
34  import org.hipparchus.geometry.euclidean.threed.Rotation;
35  import org.hipparchus.geometry.euclidean.threed.Vector3D;
36  import org.hipparchus.ode.ODEIntegrator;
37  import org.hipparchus.ode.events.Action;
38  import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
39  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
40  import org.hipparchus.util.FastMath;
41  import org.hipparchus.util.Precision;
42  import org.junit.After;
43  import org.junit.Assert;
44  import org.junit.Before;
45  import org.junit.Test;
46  import org.orekit.Utils;
47  import org.orekit.attitudes.Attitude;
48  import org.orekit.attitudes.AttitudeProvider;
49  import org.orekit.attitudes.BodyCenterPointing;
50  import org.orekit.bodies.OneAxisEllipsoid;
51  import org.orekit.errors.OrekitException;
52  import org.orekit.errors.OrekitMessages;
53  import org.orekit.frames.FramesFactory;
54  import org.orekit.frames.Transform;
55  import org.orekit.orbits.KeplerianOrbit;
56  import org.orekit.orbits.Orbit;
57  import org.orekit.orbits.PositionAngle;
58  import org.orekit.propagation.analytical.EcksteinHechlerPropagator;
59  import org.orekit.propagation.analytical.KeplerianPropagator;
60  import org.orekit.propagation.events.DateDetector;
61  import org.orekit.propagation.events.handlers.EventHandler;
62  import org.orekit.propagation.numerical.NumericalPropagator;
63  import org.orekit.time.AbsoluteDate;
64  import org.orekit.time.DateComponents;
65  import org.orekit.time.TimeComponents;
66  import org.orekit.time.TimeScalesFactory;
67  import org.orekit.utils.AbsolutePVCoordinates;
68  import org.orekit.utils.Constants;
69  import org.orekit.utils.DoubleArrayDictionary;
70  import org.orekit.utils.IERSConventions;
71  import org.orekit.utils.PVCoordinates;
72  
73  
74  public class SpacecraftStateTest {
75  
76      @Test
77      public void testShiftVsEcksteinHechlerError()
78          throws ParseException, OrekitException {
79  
80  
81          // polynomial models for interpolation error in position, velocity, acceleration and attitude
82          // these models grow as follows
83          //   interpolation time (s)    position error (m)   velocity error (m/s)   acceleration error (m/s²)  attitude error (°)
84          //           60                        2                    0.07                  0.002               0.00002
85          //          120                       12                    0.3                   0.005               0.00009
86          //          300                      170                    1.6                   0.012               0.0009
87          //          600                     1200                    5.7                   0.024               0.006
88          //          900                     3600                   10.6                   0.034               0.02
89          // the expected maximum residuals with respect to these models are about 0.4m, 0.5mm/s, 8μm/s² and 3e-6°
90          PolynomialFunction pModel = new PolynomialFunction(new double[] {
91              1.5664070631933846e-01,  7.5504722733047560e-03, -8.2460562451009510e-05,
92              6.9546332080305580e-06, -1.7045365367533077e-09, -4.2187860791066264e-13
93          });
94          PolynomialFunction vModel = new PolynomialFunction(new double[] {
95             -3.5472364019908720e-04,  1.6568103861124980e-05,  1.9637913327830596e-05,
96             -3.4248792843039766e-09, -5.6565135131014254e-12,  1.4730170946808630e-15
97          });
98          PolynomialFunction aModel = new PolynomialFunction(new double[] {
99              3.0731707577766896e-06,  3.9770746399850350e-05,  1.9779039254538660e-09,
100             8.0263328220724900e-12, -1.5600835252366078e-14,  1.1785257001549687e-18
101         });
102         PolynomialFunction rModel = new PolynomialFunction(new double[] {
103            -2.7689062063188115e-06,  1.7406542538258334e-07,  2.5109795349592287e-09,
104             2.0399322661074575e-11,  9.9126348912426750e-15, -3.5015638905729510e-18
105         });
106 
107         AbsoluteDate centerDate = orbit.getDate().shiftedBy(100.0);
108         SpacecraftState centerState = propagator.propagate(centerDate);
109         double maxResidualP = 0;
110         double maxResidualV = 0;
111         double maxResidualA = 0;
112         double maxResidualR = 0;
113         for (double dt = 0; dt < 900.0; dt += 5) {
114             SpacecraftState shifted = centerState.shiftedBy(dt);
115             SpacecraftState propagated = propagator.propagate(centerDate.shiftedBy(dt));
116             PVCoordinates dpv = new PVCoordinates(propagated.getPVCoordinates(), shifted.getPVCoordinates());
117             double residualP = pModel.value(dt) - dpv.getPosition().getNorm();
118             double residualV = vModel.value(dt) - dpv.getVelocity().getNorm();
119             double residualA = aModel.value(dt) - dpv.getAcceleration().getNorm();
120             double residualR = rModel.value(dt) -
121                                FastMath.toDegrees(Rotation.distance(shifted.getAttitude().getRotation(),
122                                                                     propagated.getAttitude().getRotation()));
123             maxResidualP = FastMath.max(maxResidualP, FastMath.abs(residualP));
124             maxResidualV = FastMath.max(maxResidualV, FastMath.abs(residualV));
125             maxResidualA = FastMath.max(maxResidualA, FastMath.abs(residualA));
126             maxResidualR = FastMath.max(maxResidualR, FastMath.abs(residualR));
127 
128         }
129 
130         Assert.assertEquals(0.40,   maxResidualP, 0.01);
131         Assert.assertEquals(4.9e-4, maxResidualV, 1.0e-5);
132         Assert.assertEquals(7.7e-6, maxResidualA, 1.0e-7);
133         Assert.assertEquals(2.8e-6, maxResidualR, 1.0e-1);
134 
135     }
136 
137     @Test
138     public void testInterpolation()
139         throws ParseException, OrekitException {
140         checkInterpolationError( 2,  106.46533, 0.40709287, 169847806.33e-9, 0.0, 450 * 450, 450 * 450);
141         checkInterpolationError( 3,    0.00353, 0.00003250,    189886.01e-9, 0.0, 0.0, 0.0);
142         checkInterpolationError( 4,    0.00002, 0.00000023,       232.25e-9, 0.0, 0.0, 0.0);
143     }
144 
145     private void checkInterpolationError(int n, double expectedErrorP, double expectedErrorV,
146                                          double expectedErrorA, double expectedErrorM,
147                                          double expectedErrorQ, double expectedErrorD) {
148         AbsoluteDate centerDate = orbit.getDate().shiftedBy(100.0);
149         SpacecraftState centerState = propagator.propagate(centerDate).
150                                       addAdditionalState("quadratic", 0).
151                                       addAdditionalStateDerivative("quadratic-dot", 0);
152         List<SpacecraftState> sample = new ArrayList<SpacecraftState>();
153         for (int i = 0; i < n; ++i) {
154             double dt = i * 900.0 / (n - 1);
155             SpacecraftState state = propagator.propagate(centerDate.shiftedBy(dt));
156             state = state.
157                     addAdditionalState("quadratic", dt * dt).
158                     addAdditionalStateDerivative("quadratic-dot", dt * dt);
159             sample.add(state);
160         }
161         double maxErrorP = 0;
162         double maxErrorV = 0;
163         double maxErrorA = 0;
164         double maxErrorM = 0;
165         double maxErrorQ = 0;
166         double maxErrorD = 0;
167         for (double dt = 0; dt < 900.0; dt += 5) {
168             SpacecraftState interpolated = centerState.interpolate(centerDate.shiftedBy(dt), sample);
169             SpacecraftState propagated = propagator.propagate(centerDate.shiftedBy(dt));
170             PVCoordinates dpv = new PVCoordinates(propagated.getPVCoordinates(), interpolated.getPVCoordinates());
171             maxErrorP = FastMath.max(maxErrorP, dpv.getPosition().getNorm());
172             maxErrorV = FastMath.max(maxErrorV, dpv.getVelocity().getNorm());
173             maxErrorA = FastMath.max(maxErrorA, FastMath.toDegrees(Rotation.distance(interpolated.getAttitude().getRotation(),
174                                                                                                   propagated.getAttitude().getRotation())));
175             maxErrorM = FastMath.max(maxErrorM, FastMath.abs(interpolated.getMass() - propagated.getMass()));
176             maxErrorQ = FastMath.max(maxErrorQ, FastMath.abs(interpolated.getAdditionalState("quadratic")[0] - dt * dt));
177             maxErrorD = FastMath.max(maxErrorD, FastMath.abs(interpolated.getAdditionalStateDerivative("quadratic-dot")[0] - dt * dt));
178         }
179         Assert.assertEquals(expectedErrorP, maxErrorP, 1.0e-3);
180         Assert.assertEquals(expectedErrorV, maxErrorV, 1.0e-6);
181         Assert.assertEquals(expectedErrorA, maxErrorA, 4.0e-10);
182         Assert.assertEquals(expectedErrorM, maxErrorM, 1.0e-15);
183         Assert.assertEquals(expectedErrorQ, maxErrorQ, 2.0e-10);
184         Assert.assertEquals(expectedErrorD, maxErrorD, 2.0e-10);
185     }
186 
187     @Test(expected=IllegalArgumentException.class)
188     public void testDatesConsistency() {
189         new SpacecraftState(orbit, attitudeLaw.getAttitude(orbit.shiftedBy(10.0),
190                                                            orbit.getDate().shiftedBy(10.0), orbit.getFrame()));
191     }
192 
193     /**
194      * Check orbit and attitude dates can be off by a few ulps. I see this when using
195      * FixedRate attitude provider.
196      */
197     @Test
198     public void testDateConsistencyClose() {
199         //setup
200         Orbit orbit10Shifts = orbit;
201         for (int i = 0; i < 10; i++) {
202             orbit10Shifts = orbit10Shifts.shiftedBy(0.1);
203         }
204         final Orbit orbit1Shift = orbit.shiftedBy(1);
205         Attitude shiftedAttitude = attitudeLaw
206                 .getAttitude(orbit1Shift, orbit1Shift.getDate(), orbit.getFrame());
207 
208         //verify dates are very close, but not equal
209         Assert.assertNotEquals(shiftedAttitude.getDate(), orbit10Shifts.getDate());
210         Assert.assertEquals(
211                 shiftedAttitude.getDate().durationFrom(orbit10Shifts.getDate()),
212                 0, Precision.EPSILON);
213 
214         //action + verify no exception is thrown
215         new SpacecraftState(orbit10Shifts, shiftedAttitude);
216     }
217 
218     @Test(expected=IllegalArgumentException.class)
219     public void testFramesConsistency() {
220         new SpacecraftState(orbit,
221                             new Attitude(orbit.getDate(),
222                                          FramesFactory.getGCRF(),
223                                          Rotation.IDENTITY, Vector3D.ZERO, Vector3D.ZERO));
224     }
225 
226     @Test
227     public void testTransform()
228         throws ParseException, OrekitException {
229 
230         double maxDP = 0;
231         double maxDV = 0;
232         double maxDA = 0;
233         for (double t = 0; t < orbit.getKeplerianPeriod(); t += 60) {
234             final SpacecraftState state = propagator.propagate(orbit.getDate().shiftedBy(t));
235             final Transform transform = state.toTransform().getInverse();
236             PVCoordinates pv = transform.transformPVCoordinates(PVCoordinates.ZERO);
237             PVCoordinates dPV = new PVCoordinates(pv, state.getPVCoordinates());
238             Vector3D mZDirection = transform.transformVector(Vector3D.MINUS_K);
239             double alpha = Vector3D.angle(mZDirection, state.getPVCoordinates().getPosition());
240             maxDP = FastMath.max(maxDP, dPV.getPosition().getNorm());
241             maxDV = FastMath.max(maxDV, dPV.getVelocity().getNorm());
242             maxDA = FastMath.max(maxDA, FastMath.toDegrees(alpha));
243         }
244         Assert.assertEquals(0.0, maxDP, 1.0e-6);
245         Assert.assertEquals(0.0, maxDV, 1.0e-9);
246         Assert.assertEquals(0.0, maxDA, 1.0e-12);
247 
248     }
249 
250     @Test
251     public void testAdditionalStates() {
252         final SpacecraftState state = propagator.propagate(orbit.getDate().shiftedBy(60));
253         final SpacecraftState extended =
254                         state.
255                         addAdditionalState("test-1", new double[] { 1.0, 2.0 }).
256                         addAdditionalState("test-2", 42.0);
257         Assert.assertEquals(0, state.getAdditionalStatesValues().size());
258         Assert.assertFalse(state.hasAdditionalState("test-1"));
259         try {
260             state.getAdditionalState("test-1");
261             Assert.fail("an exception should have been thrown");
262         } catch (OrekitException oe) {
263             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
264             Assert.assertEquals(oe.getParts()[0], "test-1");
265         }
266         try {
267             state.ensureCompatibleAdditionalStates(extended);
268             Assert.fail("an exception should have been thrown");
269         } catch (OrekitException oe) {
270             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
271             Assert.assertTrue(oe.getParts()[0].toString().startsWith("test-"));
272         }
273         try {
274             extended.ensureCompatibleAdditionalStates(state);
275             Assert.fail("an exception should have been thrown");
276         } catch (OrekitException oe) {
277             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
278             Assert.assertTrue(oe.getParts()[0].toString().startsWith("test-"));
279         }
280         try {
281             extended.ensureCompatibleAdditionalStates(extended.addAdditionalState("test-2", new double[7]));
282             Assert.fail("an exception should have been thrown");
283         } catch (MathIllegalStateException mise) {
284             Assert.assertEquals(LocalizedCoreFormats.DIMENSIONS_MISMATCH, mise.getSpecifier());
285             Assert.assertEquals(7, ((Integer) mise.getParts()[0]).intValue());
286         }
287         Assert.assertEquals(2, extended.getAdditionalStatesValues().getData().size());
288         Assert.assertTrue(extended.hasAdditionalState("test-1"));
289         Assert.assertTrue(extended.hasAdditionalState("test-2"));
290         Assert.assertEquals( 1.0, extended.getAdditionalState("test-1")[0], 1.0e-15);
291         Assert.assertEquals( 2.0, extended.getAdditionalState("test-1")[1], 1.0e-15);
292         Assert.assertEquals(42.0, extended.getAdditionalState("test-2")[0], 1.0e-15);
293 
294         // test various constructors
295         DoubleArrayDictionary dictionary = new DoubleArrayDictionary();
296         dictionary.put("test-3", new double[] { -6.0 });
297         SpacecraftState sO = new SpacecraftState(state.getOrbit(), dictionary);
298         Assert.assertEquals(-6.0, sO.getAdditionalState("test-3")[0], 1.0e-15);
299         SpacecraftState sOA = new SpacecraftState(state.getOrbit(), state.getAttitude(), dictionary);
300         Assert.assertEquals(-6.0, sOA.getAdditionalState("test-3")[0], 1.0e-15);
301         SpacecraftState sOM = new SpacecraftState(state.getOrbit(), state.getMass(), dictionary);
302         Assert.assertEquals(-6.0, sOM.getAdditionalState("test-3")[0], 1.0e-15);
303         SpacecraftState sOAM = new SpacecraftState(state.getOrbit(), state.getAttitude(), state.getMass(), dictionary);
304         Assert.assertEquals(-6.0, sOAM.getAdditionalState("test-3")[0], 1.0e-15);
305 
306     }
307 
308     @Deprecated
309     @Test
310     public void testDeprecatedOrbit() {
311         final SpacecraftState state = propagator.propagate(orbit.getDate().shiftedBy(60));
312         // test various deprecated constructors
313         Map<String, double[]> map = new HashMap<String, double[]>();
314         map.put("test-3", new double[] { -6.0 });
315         SpacecraftState sO = new SpacecraftState(state.getOrbit(), map);
316         Assert.assertEquals(1, sO.getAdditionalStates().size());
317         Assert.assertEquals(-6.0, sO.getAdditionalState("test-3")[0], 1.0e-15);
318         SpacecraftState sOA = new SpacecraftState(state.getOrbit(), state.getAttitude(), map);
319         Assert.assertEquals(-6.0, sOA.getAdditionalState("test-3")[0], 1.0e-15);
320         SpacecraftState sOM = new SpacecraftState(state.getOrbit(), state.getMass(), map);
321         Assert.assertEquals(-6.0, sOM.getAdditionalState("test-3")[0], 1.0e-15);
322         SpacecraftState sOAM = new SpacecraftState(state.getOrbit(), state.getAttitude(), state.getMass(), map);
323         Assert.assertEquals(-6.0, sOAM.getAdditionalState("test-3")[0], 1.0e-15);
324     }
325 
326     @Test
327     public void testAdditionalStatesDerivatives() {
328         final SpacecraftState state = propagator.propagate(orbit.getDate().shiftedBy(60));
329         final SpacecraftState extended =
330                         state.
331                         addAdditionalStateDerivative("test-1", new double[] { 1.0, 2.0 }).
332                         addAdditionalStateDerivative("test-2", 42.0);
333         Assert.assertEquals(0, state.getAdditionalStatesDerivatives().size());
334         Assert.assertFalse(state.hasAdditionalStateDerivative("test-1"));
335         try {
336             state.getAdditionalStateDerivative("test-1");
337             Assert.fail("an exception should have been thrown");
338         } catch (OrekitException oe) {
339             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
340             Assert.assertEquals(oe.getParts()[0], "test-1");
341         }
342         try {
343             state.ensureCompatibleAdditionalStates(extended);
344             Assert.fail("an exception should have been thrown");
345         } catch (OrekitException oe) {
346             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
347             Assert.assertTrue(oe.getParts()[0].toString().startsWith("test-"));
348         }
349         try {
350             extended.ensureCompatibleAdditionalStates(state);
351             Assert.fail("an exception should have been thrown");
352         } catch (OrekitException oe) {
353             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
354             Assert.assertTrue(oe.getParts()[0].toString().startsWith("test-"));
355         }
356         try {
357             extended.ensureCompatibleAdditionalStates(extended.addAdditionalStateDerivative("test-2", new double[7]));
358             Assert.fail("an exception should have been thrown");
359         } catch (MathIllegalStateException mise) {
360             Assert.assertEquals(LocalizedCoreFormats.DIMENSIONS_MISMATCH, mise.getSpecifier());
361             Assert.assertEquals(7, ((Integer) mise.getParts()[0]).intValue());
362         }
363         Assert.assertEquals(2, extended.getAdditionalStatesDerivatives().size());
364         Assert.assertTrue(extended.hasAdditionalStateDerivative("test-1"));
365         Assert.assertTrue(extended.hasAdditionalStateDerivative("test-2"));
366         Assert.assertEquals( 1.0, extended.getAdditionalStateDerivative("test-1")[0], 1.0e-15);
367         Assert.assertEquals( 2.0, extended.getAdditionalStateDerivative("test-1")[1], 1.0e-15);
368         Assert.assertEquals(42.0, extended.getAdditionalStateDerivative("test-2")[0], 1.0e-15);
369 
370         // test most complete constructor
371         DoubleArrayDictionary dictionary = new DoubleArrayDictionary();
372         dictionary.put("test-3", new double[] { -6.0 });
373         SpacecraftState s = new SpacecraftState(state.getOrbit(), state.getAttitude(), state.getMass(), null, dictionary);
374         Assert.assertFalse(s.hasAdditionalState("test-3"));
375         Assert.assertEquals(-6.0, s.getAdditionalStateDerivative("test-3")[0], 1.0e-15);
376 
377     }
378 
379     @Test
380     public void testAdditionalTestResetOnEventAnalytical() {
381 
382         // Build orbit
383         AbsoluteDate date0 = new AbsoluteDate(2000, 1, 1, TimeScalesFactory.getUTC());
384         Orbit orbit = new KeplerianOrbit(7.1E6, 0, 0, 0, 0, 0,
385                                          PositionAngle.TRUE, FramesFactory.getGCRF(), date0,
386                                          Constants.WGS84_EARTH_MU);
387 
388         // Build propagator
389         KeplerianPropagator propagator = new KeplerianPropagator(orbit);
390 
391         // Create initial state with one additional state and add it to the propagator
392         final String name = "A";
393         SpacecraftState initialState = new SpacecraftState(orbit).
394                                        addAdditionalState(name, new double[] { -1 });
395 
396         propagator.resetInitialState(initialState);
397 
398         // Create date detector and handler
399         AbsoluteDate changeDate = date0.shiftedBy(3);
400         DateDetector dateDetector = new DateDetector(changeDate).
401                                     withHandler(new EventHandler<DateDetector>() {
402 
403             @Override
404             public Action eventOccurred(SpacecraftState s, DateDetector detector, boolean increasing) {
405               return Action.RESET_STATE;
406             }
407 
408             @Override
409             public SpacecraftState resetState(DateDetector detector, SpacecraftState oldState) {
410                 return oldState.addAdditionalState(name, new double[] { +1 });
411             }
412 
413         });
414 
415         propagator.addEventDetector(dateDetector);
416         propagator.setStepHandler(0.125, s -> {
417             if (s.getDate().durationFrom(changeDate) < -0.001) {
418                 Assert.assertEquals(-1, s.getAdditionalState(name)[0], 1.0e-15);
419             } else if (s.getDate().durationFrom(changeDate) > +0.001) {
420                 Assert.assertEquals(+1, s.getAdditionalState(name)[0], 1.0e-15);
421             }
422         });
423         SpacecraftState finalState = propagator.propagate(date0, date0.shiftedBy(5));
424         Assert.assertEquals(+1, finalState.getAdditionalState(name)[0], 1.0e-15);
425 
426     }
427 
428     @Test
429     public void testAdditionalTestResetOnEventNumerical() {
430 
431         // Build orbit
432         AbsoluteDate date0 = new AbsoluteDate(2000, 1, 1, TimeScalesFactory.getUTC());
433         Orbit orbit = new KeplerianOrbit(7.1E6, 0, 0, 0, 0, 0,
434                                          PositionAngle.TRUE, FramesFactory.getGCRF(), date0,
435                                          Constants.WGS84_EARTH_MU);
436 
437         // Build propagator
438         ODEIntegrator odeIntegrator = new DormandPrince853Integrator(1E-3, 1E3, 1E-6, 1E-6);
439         NumericalPropagator propagator = new NumericalPropagator(odeIntegrator);
440 
441         // Create initial state with one additional state and add it to the propagator
442         final String name = "A";
443         SpacecraftState initialState = new SpacecraftState(orbit).
444                         addAdditionalState(name, new double[] { -1 });
445 
446         propagator.setInitialState(initialState);
447 
448         // Create date detector and handler
449         AbsoluteDate changeDate = date0.shiftedBy(3);
450         DateDetector dateDetector = new DateDetector(changeDate).
451                                     withHandler(new EventHandler<DateDetector>() {
452 
453             @Override
454             public Action eventOccurred(SpacecraftState s, DateDetector detector, boolean increasing) {
455                 return Action.RESET_STATE;
456             }
457 
458             @Override
459             public SpacecraftState resetState(DateDetector detector, SpacecraftState oldState) {
460                 return oldState.addAdditionalState(name, new double[] { +1 });
461             }
462 
463         });
464 
465         propagator.addEventDetector(dateDetector);
466         propagator.setStepHandler(0.125, s -> {
467             if (s.getDate().durationFrom(changeDate) < -0.001) {
468                 Assert.assertEquals(-1, s.getAdditionalState(name)[0], 1.0e-15);
469             } else if (s.getDate().durationFrom(changeDate) > +0.001) {
470                 Assert.assertEquals(+1, s.getAdditionalState(name)[0], 1.0e-15);
471             }
472         });
473         SpacecraftState finalState = propagator.propagate(date0, date0.shiftedBy(5));
474         Assert.assertEquals(+1, finalState.getAdditionalState(name)[0], 1.0e-15);
475 
476     }
477 
478     @Test
479     public void testSerialization()
480             throws IOException, ClassNotFoundException, OrekitException {
481 
482         propagator.resetInitialState(propagator.getInitialState().
483                                      addAdditionalState("p1", 12.25).
484                                      addAdditionalState("p2", 1, 2, 3));
485         SpacecraftState state = propagator.propagate(orbit.getDate().shiftedBy(123.456));
486 
487         Assert.assertEquals(2, state.getAdditionalStatesValues().size());
488         Assert.assertEquals(1, state.getAdditionalState("p1").length);
489         Assert.assertEquals(12.25, state.getAdditionalState("p1")[0], 1.0e-15);
490         Assert.assertEquals(3, state.getAdditionalState("p2").length);
491         Assert.assertEquals(1.0, state.getAdditionalState("p2")[0], 1.0e-15);
492         Assert.assertEquals(2.0, state.getAdditionalState("p2")[1], 1.0e-15);
493         Assert.assertEquals(3.0, state.getAdditionalState("p2")[2], 1.0e-15);
494 
495         ByteArrayOutputStream bos = new ByteArrayOutputStream();
496         ObjectOutputStream    oos = new ObjectOutputStream(bos);
497         oos.writeObject(state);
498 
499         Assert.assertTrue(bos.size() >  900);
500         Assert.assertTrue(bos.size() < 1000);
501 
502         ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
503         ObjectInputStream     ois = new ObjectInputStream(bis);
504         SpacecraftState deserialized  = (SpacecraftState) ois.readObject();
505         Assert.assertEquals(0.0,
506                             Vector3D.distance(state.getPVCoordinates().getPosition(),
507                                               deserialized.getPVCoordinates().getPosition()),
508                             1.0e-10);
509         Assert.assertEquals(0.0,
510                             Vector3D.distance(state.getPVCoordinates().getVelocity(),
511                                               deserialized.getPVCoordinates().getVelocity()),
512                             1.0e-10);
513         Assert.assertEquals(0.0,
514                             Rotation.distance(state.getAttitude().getRotation(),
515                                               deserialized.getAttitude().getRotation()),
516                             1.0e-10);
517         Assert.assertEquals(0.0,
518                             Vector3D.distance(state.getAttitude().getSpin(),
519                                               deserialized.getAttitude().getSpin()),
520                             1.0e-10);
521         Assert.assertEquals(state.getDate(), deserialized.getDate());
522         Assert.assertEquals(state.getMu(), deserialized.getMu(), 1.0e-10);
523         Assert.assertEquals(state.getFrame().getName(), deserialized.getFrame().getName());
524         Assert.assertEquals(2, deserialized.getAdditionalStatesValues().size());
525         Assert.assertEquals(1, deserialized.getAdditionalState("p1").length);
526         Assert.assertEquals(12.25, deserialized.getAdditionalState("p1")[0], 1.0e-15);
527         Assert.assertEquals(3, deserialized.getAdditionalState("p2").length);
528         Assert.assertEquals(1.0, deserialized.getAdditionalState("p2")[0], 1.0e-15);
529         Assert.assertEquals(2.0, deserialized.getAdditionalState("p2")[1], 1.0e-15);
530         Assert.assertEquals(3.0, deserialized.getAdditionalState("p2")[2], 1.0e-15);
531 
532     }
533 
534     @Test
535     public void testSerializationWithAbsPV()
536             throws IOException, ClassNotFoundException, OrekitException {
537 
538         final NumericalPropagator numPropagator = new NumericalPropagator(new ClassicalRungeKuttaIntegrator(10.0));
539         final AbsolutePVCoordinates pva = new AbsolutePVCoordinates(orbit.getFrame(), orbit.getDate(),
540                                                                     orbit.getPVCoordinates().getPosition(),
541                                                                     orbit.getPVCoordinates().getVelocity());
542         numPropagator.setOrbitType(null);
543         numPropagator.setIgnoreCentralAttraction(true);
544         numPropagator.setInitialState(new SpacecraftState(pva).
545                                      addAdditionalState("p1", 12.25).
546                                      addAdditionalState("p2", 1, 2, 3));
547         SpacecraftState state = numPropagator.propagate(pva.getDate().shiftedBy(123.456));
548 
549         Assert.assertEquals(2, state.getAdditionalStatesValues().size());
550         Assert.assertEquals(1, state.getAdditionalState("p1").length);
551         Assert.assertEquals(12.25, state.getAdditionalState("p1")[0], 1.0e-15);
552         Assert.assertEquals(3, state.getAdditionalState("p2").length);
553         Assert.assertEquals(1.0, state.getAdditionalState("p2")[0], 1.0e-15);
554         Assert.assertEquals(2.0, state.getAdditionalState("p2")[1], 1.0e-15);
555         Assert.assertEquals(3.0, state.getAdditionalState("p2")[2], 1.0e-15);
556 
557         ByteArrayOutputStream bos = new ByteArrayOutputStream();
558         ObjectOutputStream    oos = new ObjectOutputStream(bos);
559         oos.writeObject(state);
560 
561         Assert.assertTrue(bos.size() >  900);
562         Assert.assertTrue(bos.size() < 1000);
563 
564         ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
565         ObjectInputStream     ois = new ObjectInputStream(bis);
566         SpacecraftState deserialized  = (SpacecraftState) ois.readObject();
567         Assert.assertEquals(0.0,
568                             Vector3D.distance(state.getPVCoordinates().getPosition(),
569                                               deserialized.getPVCoordinates().getPosition()),
570                             1.0e-10);
571         Assert.assertEquals(0.0,
572                             Vector3D.distance(state.getPVCoordinates().getVelocity(),
573                                               deserialized.getPVCoordinates().getVelocity()),
574                             1.0e-10);
575         Assert.assertEquals(0.0,
576                             Rotation.distance(state.getAttitude().getRotation(),
577                                               deserialized.getAttitude().getRotation()),
578                             1.0e-10);
579         Assert.assertEquals(0.0,
580                             Vector3D.distance(state.getAttitude().getSpin(),
581                                               deserialized.getAttitude().getSpin()),
582                             1.0e-10);
583         Assert.assertEquals(state.getDate(), deserialized.getDate());
584         Assert.assertEquals(state.getMu(), deserialized.getMu(), 1.0e-10);
585         Assert.assertEquals(state.getFrame().getName(), deserialized.getFrame().getName());
586         Assert.assertEquals(2, deserialized.getAdditionalStatesValues().size());
587         Assert.assertEquals(1, deserialized.getAdditionalState("p1").length);
588         Assert.assertEquals(12.25, deserialized.getAdditionalState("p1")[0], 1.0e-15);
589         Assert.assertEquals(3, deserialized.getAdditionalState("p2").length);
590         Assert.assertEquals(1.0, deserialized.getAdditionalState("p2")[0], 1.0e-15);
591         Assert.assertEquals(2.0, deserialized.getAdditionalState("p2")[1], 1.0e-15);
592         Assert.assertEquals(3.0, deserialized.getAdditionalState("p2")[2], 1.0e-15);
593 
594     }
595 
596     @Test
597     public void testAdditionalStatesAbsPV() {
598 
599         double x_f     = 0.8;
600         double y_f     = 0.2;
601         double z_f     = 0;
602         double vx_f    = 0;
603         double vy_f    = 0;
604         double vz_f    = 0.1;
605 
606         AbsoluteDate date = new AbsoluteDate(new DateComponents(2004, 01, 01),
607                                                             TimeComponents.H00,
608                                                             TimeScalesFactory.getUTC());
609 
610         PVCoordinates pva_f = new PVCoordinates(new Vector3D(x_f,y_f,z_f), new Vector3D(vx_f,vy_f,vz_f));
611 
612         AbsolutePVCoordinates absPV_f = new AbsolutePVCoordinates(FramesFactory.getEME2000(), date, pva_f);
613 
614         NumericalPropagator prop = new NumericalPropagator(new DormandPrince853Integrator(0.1, 500, 0.001, 0.001));
615         prop.setOrbitType(null);
616 
617         final SpacecraftState initialState = new SpacecraftState(absPV_f);
618 
619         prop.resetInitialState(initialState);
620 
621         final SpacecraftState state = prop.propagate(absPV_f.getDate().shiftedBy(60));
622         double[] add = new double[2];
623         add[0] = 1.;
624         add[1] = 2.;
625         final SpacecraftState extended =
626                 state.
627                  addAdditionalState("test-1", add).
628                   addAdditionalState("test-2", 42.0);
629         Assert.assertEquals(0, state.getAdditionalStatesValues().size());
630         Assert.assertFalse(state.hasAdditionalState("test-1"));
631         try {
632             state.getAdditionalState("test-1");
633             Assert.fail("an exception should have been thrown");
634         } catch (OrekitException oe) {
635             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
636             Assert.assertEquals(oe.getParts()[0], "test-1");
637         }
638         try {
639             state.ensureCompatibleAdditionalStates(extended);
640             Assert.fail("an exception should have been thrown");
641         } catch (OrekitException oe) {
642             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
643             Assert.assertTrue(oe.getParts()[0].toString().startsWith("test-"));
644         }
645         try {
646             extended.ensureCompatibleAdditionalStates(state);
647             Assert.fail("an exception should have been thrown");
648         } catch (OrekitException oe) {
649             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
650             Assert.assertTrue(oe.getParts()[0].toString().startsWith("test-"));
651         }
652         try {
653             double[] kk = new double[7];
654             extended.ensureCompatibleAdditionalStates(extended.addAdditionalState("test-2", kk));
655             Assert.fail("an exception should have been thrown");
656         } catch (MathIllegalStateException mise) {
657             Assert.assertEquals(LocalizedCoreFormats.DIMENSIONS_MISMATCH, mise.getSpecifier());
658             Assert.assertEquals(7, ((Integer) mise.getParts()[0]).intValue());
659         }
660         Assert.assertEquals(2, extended.getAdditionalStatesValues().size());
661         Assert.assertTrue(extended.hasAdditionalState("test-1"));
662         Assert.assertTrue(extended.hasAdditionalState("test-2"));
663         Assert.assertEquals( 1.0, extended.getAdditionalState("test-1")[0], 1.0e-15);
664         Assert.assertEquals( 2.0, extended.getAdditionalState("test-1")[1], 1.0e-15);
665         Assert.assertEquals(42.0, extended.getAdditionalState("test-2")[0], 1.0e-15);
666 
667         // test various constructors
668         double[] dd = new double[1];
669         dd[0] = -6.0;
670         DoubleArrayDictionary deprecated = new DoubleArrayDictionary();
671         deprecated.put("test-3", dd);
672         SpacecraftState sO = new SpacecraftState(state.getAbsPVA(), deprecated);
673         Assert.assertEquals(-6.0, sO.getAdditionalState("test-3")[0], 1.0e-15);
674         SpacecraftState sOA = new SpacecraftState(state.getAbsPVA(), state.getAttitude(), deprecated);
675         Assert.assertEquals(-6.0, sOA.getAdditionalState("test-3")[0], 1.0e-15);
676         SpacecraftState sOM = new SpacecraftState(state.getAbsPVA(), state.getMass(), deprecated);
677         Assert.assertEquals(-6.0, sOM.getAdditionalState("test-3")[0], 1.0e-15);
678         SpacecraftState sOAM = new SpacecraftState(state.getAbsPVA(), state.getAttitude(), state.getMass(), deprecated);
679         Assert.assertEquals(-6.0, sOAM.getAdditionalState("test-3")[0], 1.0e-15);
680 
681     }
682 
683     @Deprecated
684     @Test
685     public void testDeprecatedPVA() {
686         double x_f     = 0.8;
687         double y_f     = 0.2;
688         double z_f     = 0;
689         double vx_f    = 0;
690         double vy_f    = 0;
691         double vz_f    = 0.1;
692 
693         AbsoluteDate date = new AbsoluteDate(new DateComponents(2004, 01, 01),
694                                                             TimeComponents.H00,
695                                                             TimeScalesFactory.getUTC());
696 
697         PVCoordinates pva_f = new PVCoordinates(new Vector3D(x_f,y_f,z_f), new Vector3D(vx_f,vy_f,vz_f));
698 
699         AbsolutePVCoordinates absPV_f = new AbsolutePVCoordinates(FramesFactory.getEME2000(), date, pva_f);
700 
701         NumericalPropagator prop = new NumericalPropagator(new DormandPrince853Integrator(0.1, 500, 0.001, 0.001));
702         prop.setOrbitType(null);
703 
704         final SpacecraftState initialState = new SpacecraftState(absPV_f);
705 
706         prop.resetInitialState(initialState);
707 
708         final SpacecraftState state = prop.propagate(absPV_f.getDate().shiftedBy(60));
709         // test various constructors
710         double[] dd = new double[1];
711         dd[0] = -6.0;
712         Map<String, double[]> map = new HashMap<String, double[]>();
713         map.put("test-3", dd);
714         SpacecraftState sO = new SpacecraftState(state.getAbsPVA(), map);
715         Assert.assertEquals(-6.0, sO.getAdditionalState("test-3")[0], 1.0e-15);
716         SpacecraftState sOA = new SpacecraftState(state.getAbsPVA(), state.getAttitude(), map);
717         Assert.assertEquals(-6.0, sOA.getAdditionalState("test-3")[0], 1.0e-15);
718         SpacecraftState sOM = new SpacecraftState(state.getAbsPVA(), state.getMass(), map);
719         Assert.assertEquals(-6.0, sOM.getAdditionalState("test-3")[0], 1.0e-15);
720         SpacecraftState sOAM = new SpacecraftState(state.getAbsPVA(), state.getAttitude(), state.getMass(), map);
721         Assert.assertEquals(-6.0, sOAM.getAdditionalState("test-3")[0], 1.0e-15);
722 
723     }
724 
725     @Test
726     public void testAdditionalStatesDerivativesAbsPV() {
727 
728         double x_f     = 0.8;
729         double y_f     = 0.2;
730         double z_f     = 0;
731         double vx_f    = 0;
732         double vy_f    = 0;
733         double vz_f    = 0.1;
734 
735         AbsoluteDate date = new AbsoluteDate(new DateComponents(2004, 01, 01),
736                                                             TimeComponents.H00,
737                                                             TimeScalesFactory.getUTC());
738 
739         PVCoordinates pva_f = new PVCoordinates(new Vector3D(x_f,y_f,z_f), new Vector3D(vx_f,vy_f,vz_f));
740 
741         AbsolutePVCoordinates absPV_f = new AbsolutePVCoordinates(FramesFactory.getEME2000(), date, pva_f);
742 
743         NumericalPropagator prop = new NumericalPropagator(new DormandPrince853Integrator(0.1, 500, 0.001, 0.001));
744         prop.setOrbitType(null);
745 
746         final SpacecraftState initialState = new SpacecraftState(absPV_f);
747 
748         prop.resetInitialState(initialState);
749 
750         final SpacecraftState state = prop.propagate(absPV_f.getDate().shiftedBy(60));
751         double[] add = new double[2];
752         add[0] = 1.;
753         add[1] = 2.;
754         final SpacecraftState extended =
755                 state.
756                  addAdditionalStateDerivative("test-1", add).
757                   addAdditionalStateDerivative("test-2", 42.0);
758         Assert.assertEquals(0, state.getAdditionalStatesDerivatives().getData().size());
759         Assert.assertFalse(state.hasAdditionalStateDerivative("test-1"));
760         try {
761             state.getAdditionalStateDerivative("test-1");
762             Assert.fail("an exception should have been thrown");
763         } catch (OrekitException oe) {
764             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
765             Assert.assertEquals(oe.getParts()[0], "test-1");
766         }
767         try {
768             state.ensureCompatibleAdditionalStates(extended);
769             Assert.fail("an exception should have been thrown");
770         } catch (OrekitException oe) {
771             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
772             Assert.assertTrue(oe.getParts()[0].toString().startsWith("test-"));
773         }
774         try {
775             extended.ensureCompatibleAdditionalStates(state);
776             Assert.fail("an exception should have been thrown");
777         } catch (OrekitException oe) {
778             Assert.assertEquals(oe.getSpecifier(), OrekitMessages.UNKNOWN_ADDITIONAL_STATE);
779             Assert.assertTrue(oe.getParts()[0].toString().startsWith("test-"));
780         }
781         try {
782             double[] kk = new double[7];
783             extended.ensureCompatibleAdditionalStates(extended.addAdditionalStateDerivative("test-2", kk));
784             Assert.fail("an exception should have been thrown");
785         } catch (MathIllegalStateException mise) {
786             Assert.assertEquals(LocalizedCoreFormats.DIMENSIONS_MISMATCH, mise.getSpecifier());
787             Assert.assertEquals(7, ((Integer) mise.getParts()[0]).intValue());
788         }
789         Assert.assertEquals(2, extended.getAdditionalStatesDerivatives().getData().size());
790         Assert.assertTrue(extended.hasAdditionalStateDerivative("test-1"));
791         Assert.assertTrue(extended.hasAdditionalStateDerivative("test-2"));
792         Assert.assertEquals( 1.0, extended.getAdditionalStateDerivative("test-1")[0], 1.0e-15);
793         Assert.assertEquals( 2.0, extended.getAdditionalStateDerivative("test-1")[1], 1.0e-15);
794         Assert.assertEquals(42.0, extended.getAdditionalStateDerivative("test-2")[0], 1.0e-15);
795 
796         // test most complete constructor
797         double[] dd = new double[1];
798         dd[0] = -6.0;
799         DoubleArrayDictionary dict = new DoubleArrayDictionary();
800         dict.put("test-3", dd);
801         SpacecraftState s = new SpacecraftState(state.getAbsPVA(), state.getAttitude(), state.getMass(), null, dict);
802         Assert.assertEquals(-6.0, s.getAdditionalStateDerivative("test-3")[0], 1.0e-15);
803 
804     }
805 
806     @Before
807     public void setUp() {
808         try {
809         Utils.setDataRoot("regular-data");
810         double mu  = 3.9860047e14;
811         double ae  = 6.378137e6;
812         double c20 = -1.08263e-3;
813         double c30 = 2.54e-6;
814         double c40 = 1.62e-6;
815         double c50 = 2.3e-7;
816         double c60 = -5.5e-7;
817 
818         mass = 2500;
819         double a = 7187990.1979844316;
820         double e = 0.5e-4;
821         double i = 1.7105407051081795;
822         double omega = 1.9674147913622104;
823         double OMEGA = FastMath.toRadians(261);
824         double lv = 0;
825 
826         AbsoluteDate date = new AbsoluteDate(new DateComponents(2004, 01, 01),
827                                                  TimeComponents.H00,
828                                                  TimeScalesFactory.getUTC());
829         orbit = new KeplerianOrbit(a, e, i, omega, OMEGA, lv, PositionAngle.TRUE,
830                                    FramesFactory.getEME2000(), date, mu);
831         OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
832                                                       Constants.WGS84_EARTH_FLATTENING,
833                                                       FramesFactory.getITRF(IERSConventions.IERS_2010, true));
834         attitudeLaw = new BodyCenterPointing(orbit.getFrame(), earth);
835         propagator =
836             new EcksteinHechlerPropagator(orbit, attitudeLaw, mass,
837                                           ae, mu, c20, c30, c40, c50, c60);
838 
839         } catch (OrekitException oe) {
840             Assert.fail(oe.getLocalizedMessage());
841         }
842     }
843 
844     @After
845     public void tearDown() {
846         mass  = Double.NaN;
847         orbit = null;
848         attitudeLaw = null;
849         propagator = null;
850     }
851 
852     private double mass;
853     private Orbit orbit;
854     private AttitudeProvider attitudeLaw;
855     private Propagator propagator;
856 
857 }