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