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