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