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.geometry.euclidean.threed.Rotation;
20  import org.hipparchus.ode.ODEIntegrator;
21  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
22  import org.hipparchus.util.FastMath;
23  import org.junit.jupiter.api.*;
24  import org.mockito.Mockito;
25  import org.orekit.Utils;
26  import org.orekit.attitudes.Attitude;
27  import org.orekit.attitudes.AttitudeInterpolator;
28  import org.orekit.attitudes.AttitudeProvider;
29  import org.orekit.attitudes.BodyCenterPointing;
30  import org.orekit.bodies.CelestialBodyFactory;
31  import org.orekit.bodies.OneAxisEllipsoid;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitIllegalArgumentException;
34  import org.orekit.errors.OrekitInternalError;
35  import org.orekit.errors.OrekitMessages;
36  import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
37  import org.orekit.forces.gravity.SingleBodyAbsoluteAttraction;
38  import org.orekit.forces.gravity.potential.GravityFieldFactory;
39  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
40  import org.orekit.frames.Frame;
41  import org.orekit.frames.FramesFactory;
42  import org.orekit.orbits.KeplerianOrbit;
43  import org.orekit.orbits.Orbit;
44  import org.orekit.orbits.OrbitHermiteInterpolator;
45  import org.orekit.orbits.PositionAngleType;
46  import org.orekit.propagation.analytical.EcksteinHechlerPropagator;
47  import org.orekit.propagation.numerical.NumericalPropagator;
48  import org.orekit.time.*;
49  import org.orekit.utils.*;
50  
51  import java.util.ArrayList;
52  import java.util.Collections;
53  import java.util.List;
54  
55  class SpacecraftStateInterpolatorTest {
56  
57      private double                mass;
58      private Orbit                 orbit;
59      private AbsolutePVCoordinates absPV;
60      private AttitudeProvider      attitudeLaw;
61      private Propagator            orbitPropagator;
62      private Propagator            absPVPropagator;
63  
64      @BeforeEach
65      public void setUp() {
66          try {
67              Utils.setDataRoot("regular-data:potential/icgem-format");
68              double mu  = 3.9860047e14;
69              double ae  = 6.378137e6;
70              double c20 = -1.08263e-3;
71              double c30 = 2.54e-6;
72              double c40 = 1.62e-6;
73              double c50 = 2.3e-7;
74              double c60 = -5.5e-7;
75  
76              mass = 2500;
77              double a     = 7187990.1979844316;
78              double e     = 0.5e-4;
79              double i     = 1.7105407051081795;
80              double omega = 1.9674147913622104;
81              double OMEGA = FastMath.toRadians(261);
82              double lv    = 0;
83  
84              AbsoluteDate date = new AbsoluteDate(new DateComponents(2004, 1, 1),
85                      TimeComponents.H00,
86                      TimeScalesFactory.getUTC());
87              final Frame frame = FramesFactory.getEME2000();
88              orbit = new KeplerianOrbit(a, e, i, omega, OMEGA, lv, PositionAngleType.TRUE, frame, date, mu);
89              OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
90                      Constants.WGS84_EARTH_FLATTENING,
91                      FramesFactory.getITRF(IERSConventions.IERS_2010, true));
92  
93              absPV = new AbsolutePVCoordinates(frame, date, orbit.getPVCoordinates());
94  
95              attitudeLaw     = new BodyCenterPointing(orbit.getFrame(), earth);
96              orbitPropagator =
97                      new EcksteinHechlerPropagator(orbit, attitudeLaw, mass,
98                              ae, mu, c20, c30, c40, c50, c60);
99  
100             absPVPropagator = setUpNumericalPropagator();
101 
102         } catch (OrekitException oe) {
103             Assertions.fail(oe.getLocalizedMessage());
104         }
105     }
106 
107     @AfterEach
108     public void tearDown() {
109         mass            = Double.NaN;
110         orbit           = null;
111         attitudeLaw     = null;
112         orbitPropagator = null;
113     }
114 
115     @Test
116     public void testOrbitInterpolation()
117             throws OrekitException {
118 
119         // Given
120         final int interpolationPoints1 = 2;
121         final int interpolationPoints2 = 3;
122         final int interpolationPoints3 = 4;
123 
124         final Frame intertialFrame = FramesFactory.getEME2000();
125 
126         // When & Then
127         // Define state interpolators
128         final SpacecraftStateInterpolator interpolator1 =
129                 new SpacecraftStateInterpolator(interpolationPoints1, intertialFrame, intertialFrame);
130 
131         final SpacecraftStateInterpolator interpolator2 =
132                 new SpacecraftStateInterpolator(interpolationPoints2, SpacecraftStateInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, intertialFrame, intertialFrame);
133 
134         final SpacecraftStateInterpolator interpolator3 =
135                 new SpacecraftStateInterpolator(interpolationPoints3, SpacecraftStateInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, intertialFrame);
136 
137         // When & Then
138         checkInterpolationError(interpolationPoints1, 106.46533, 0.40709287, 169847806.33e-9, 0.0, 450 * 450, 450 * 450,
139                 interpolator1);
140         checkInterpolationError(interpolationPoints2, 0.00353, 0.00003250, 189886.01e-9, 0.0, 0.0, 0.0, interpolator2);
141         checkInterpolationError(interpolationPoints3, 0.00002, 0.00000023, 232.25e-9, 0.0, 0.0, 0.0, interpolator3);
142     }
143 
144     @Test
145     public void testAbsPVAInterpolation()
146             throws OrekitException {
147 
148         // Given
149         final int interpolationPoints1 = 2;
150         final int interpolationPoints2 = 3;
151         final int interpolationPoints3 = 4;
152 
153         final Frame intertialFrame = absPV.getFrame();
154 
155         // Create interpolator with different number of interpolation points and derivative filters (P/R, PV/RR, PVA/RRR)
156         final SpacecraftStateInterpolator[] interpolator1 =
157                 buildAllTypeOfInterpolator(interpolationPoints1, intertialFrame);
158         final SpacecraftStateInterpolator[] interpolator2 =
159                 buildAllTypeOfInterpolator(interpolationPoints2, intertialFrame);
160         final SpacecraftStateInterpolator[] interpolator3 =
161                 buildAllTypeOfInterpolator(interpolationPoints3, intertialFrame);
162 
163         // P and R
164         checkAbsPVInterpolationError(interpolationPoints1, 766704.6033758943, 3385.895505018284,
165                 9.503905101141868, 0.0, interpolator1[0]);
166         checkAbsPVInterpolationError(interpolationPoints2, 46190.78568215623, 531.3506621730367,
167                 0.5601906427491941, 0, interpolator2[0]);
168         checkAbsPVInterpolationError(interpolationPoints3, 2787.7069621834926, 55.5146607205871,
169                 0.03372344505743245, 0.0, interpolator3[0]);
170 
171         // PV and RR
172         checkAbsPVInterpolationError(interpolationPoints1, 14023.999059896296, 48.022197580401084,
173                 0.16984517369482555, 0.0, interpolator1[1]);
174         checkAbsPVInterpolationError(interpolationPoints2, 16.186825338590722, 0.13418685366189476,
175                 1.898961129289559E-4, 0, interpolator2[1]);
176         checkAbsPVInterpolationError(interpolationPoints3, 0.025110113133073413, 3.5069332429486154E-4,
177                 2.3306042475258594E-7, 0.0, interpolator3[1]);
178 
179         // PVA and RRR
180         checkAbsPVInterpolationError(interpolationPoints1, 108.13907262943746, 0.4134494277844817,
181                 0.001389170843175492, 0.0, interpolator1[2]);
182         checkAbsPVInterpolationError(interpolationPoints2, 0.002974408269435121, 2.6937387601886076E-5,
183                 2.051629855188969E-4, 0, interpolator2[2]);
184         checkAbsPVInterpolationError(interpolationPoints3, 0, 0,
185                 1.3779131041190534E-4, 0.0, interpolator3[2]);
186     }
187 
188     /**
189      * Set up a numerical propagator for spacecraft state defined by an absolute position-velocity-acceleration. It is
190      * designed to be similar to the EcksteinHechler propagator.
191      * <p>
192      * It has attraction towards Earth + 6x6 earth potential as forces.
193      *
194      * @return numerical propagator for spacecraft state defined by an absolute position-velocity-acceleration
195      */
196     private Propagator setUpNumericalPropagator() {
197 
198         final ODEIntegrator integrator = setUpIntegrator();
199 
200         final NumericalPropagator propagator = new NumericalPropagator(integrator);
201 
202         // Configure propagator
203         propagator.setOrbitType(null);
204 
205         // Add force models
206         final Frame                                itrf      = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
207         final NormalizedSphericalHarmonicsProvider provider  = GravityFieldFactory.getNormalizedProvider(6, 6);
208         final HolmesFeatherstoneAttractionModel    potential = new HolmesFeatherstoneAttractionModel(itrf, provider);
209 
210         propagator.addForceModel(potential);
211         propagator.addForceModel(new SingleBodyAbsoluteAttraction(CelestialBodyFactory.getEarth()));
212 
213         // Set initial state
214         final SpacecraftState initialState = new SpacecraftState(absPV);
215 
216         propagator.setInitialState(initialState);
217 
218         // Set attitude law
219         propagator.setAttitudeProvider(attitudeLaw);
220 
221         return propagator;
222     }
223 
224     /**
225      * Set up default integrator for numerical propagator
226      *
227      * @return default integrator for numerical propagator
228      */
229     private ODEIntegrator setUpIntegrator() {
230         final double     dP         = 1;
231         final double     minStep    = 0.001;
232         final double     maxStep    = 100;
233         final double[][] tolerances = ToleranceProvider.of(CartesianToleranceProvider.of(dP)).getTolerances(absPV);
234 
235         return new DormandPrince853Integrator(minStep, maxStep, tolerances[0], tolerances[1]);
236     }
237 
238     /**
239      * Build spacecraft state Hermite interpolators using all kind of position-velocity-acceleration derivatives and angular
240      * derivatives filter.
241      *
242      * @param interpolationPoints number of interpolation points
243      * @param inertialFrame       inertial frame
244      * @return array of spacecraft state Hermite interpolators containing all possible configuration (3 in total)
245      */
246     private SpacecraftStateInterpolator[] buildAllTypeOfInterpolator(final int interpolationPoints,
247                                                                      final Frame inertialFrame) {
248 
249         final CartesianDerivativesFilter[] pvaFilters     = CartesianDerivativesFilter.values();
250         final AngularDerivativesFilter[]   angularFilters = AngularDerivativesFilter.values();
251 
252         final int                           dim           = pvaFilters.length;
253         final SpacecraftStateInterpolator[] interpolators = new SpacecraftStateInterpolator[dim];
254 
255         for (int i = 0; i < dim; i++) {
256             interpolators[i] = new SpacecraftStateInterpolator(interpolationPoints,
257                     AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
258                     inertialFrame, inertialFrame,
259                     pvaFilters[i], angularFilters[i]);
260         }
261 
262         return interpolators;
263     }
264 
265     /**
266      * Check interpolation error for position, velocity , attitude, mass, additional state and associated derivatives. This
267      * method was designed to test interpolation on orbit defined spacecraft states.
268      *
269      * @param n              sample size
270      * @param expectedErrorP expected position error
271      * @param expectedErrorV expected velocity error
272      * @param expectedErrorA expected attitude error
273      * @param expectedErrorM expected mass error
274      * @param expectedErrorQ expected additional state error
275      * @param expectedErrorD expected additional state derivative error
276      * @param interpolator   state interpolator
277      */
278     private void checkInterpolationError(int n, double expectedErrorP, double expectedErrorV,
279                                          double expectedErrorA, double expectedErrorM,
280                                          double expectedErrorQ, double expectedErrorD,
281                                          final TimeInterpolator<SpacecraftState> interpolator) {
282         AbsoluteDate          centerDate = orbit.getDate().shiftedBy(100.0);
283         List<SpacecraftState> sample     = new ArrayList<>();
284         for (int i = 0; i < n; ++i) {
285             double          dt    = i * 900.0 / (n - 1);
286             SpacecraftState state = orbitPropagator.propagate(centerDate.shiftedBy(dt));
287             state = state.
288                     addAdditionalData("quadratic", dt * dt).
289                     addAdditionalStateDerivative("quadratic-dot", dt * dt);
290             sample.add(state);
291         }
292 
293         double maxErrorP = 0;
294         double maxErrorV = 0;
295         double maxErrorA = 0;
296         double maxErrorM = 0;
297         double maxErrorQ = 0;
298         double maxErrorD = 0;
299         for (double dt = 0; dt < 900.0; dt += 5) {
300             SpacecraftState interpolated = interpolator.interpolate(centerDate.shiftedBy(dt), sample);
301             SpacecraftState propagated   = orbitPropagator.propagate(centerDate.shiftedBy(dt));
302             PVCoordinates   dpv          = new PVCoordinates(propagated.getPVCoordinates(), interpolated.getPVCoordinates());
303             maxErrorP = FastMath.max(maxErrorP, dpv.getPosition().getNorm());
304             maxErrorV = FastMath.max(maxErrorV, dpv.getVelocity().getNorm());
305             maxErrorA =
306                     FastMath.max(maxErrorA, FastMath.toDegrees(Rotation.distance(interpolated.getAttitude().getRotation(),
307                             propagated.getAttitude().getRotation())));
308             maxErrorM = FastMath.max(maxErrorM, FastMath.abs(interpolated.getMass() - propagated.getMass()));
309             maxErrorQ = FastMath.max(maxErrorQ, FastMath.abs(interpolated.getAdditionalState("quadratic")[0] - dt * dt));
310             maxErrorD = FastMath.max(maxErrorD,
311                     FastMath.abs(interpolated.getAdditionalStateDerivative("quadratic-dot")[0] - dt * dt));
312         }
313         Assertions.assertEquals(expectedErrorP, maxErrorP, 1.0e-3);
314         Assertions.assertEquals(expectedErrorV, maxErrorV, 1.0e-6);
315         Assertions.assertEquals(expectedErrorA, maxErrorA, 4.0e-10);
316         Assertions.assertEquals(expectedErrorM, maxErrorM, 1.0e-15);
317         Assertions.assertEquals(expectedErrorQ, maxErrorQ, 2.0e-10);
318         Assertions.assertEquals(expectedErrorD, maxErrorD, 2.0e-10);
319     }
320 
321     /**
322      * Check interpolation error for position, velocity , attitude and mass only. This method was designed to test
323      * interpolation on spacecraft states defined by absolute position-velocity-acceleration errors for better code
324      * coverage.
325      *
326      * @param n              sample size
327      * @param expectedErrorP expected position error
328      * @param expectedErrorV expected velocity error
329      * @param expectedErrorA expected attitude error
330      * @param expectedErrorM expected mass error
331      * @param interpolator   state interpolator
332      */
333     private void checkAbsPVInterpolationError(int n, double expectedErrorP, double expectedErrorV,
334                                               double expectedErrorA, double expectedErrorM,
335                                               final TimeInterpolator<SpacecraftState> interpolator) {
336         AbsoluteDate          centerDate = absPV.getDate().shiftedBy(100.0);
337         List<SpacecraftState> sample     = new ArrayList<>();
338         for (int i = 0; i < n; ++i) {
339             double          dt    = i * 900.0 / (n - 1);
340             SpacecraftState state = absPVPropagator.propagate(centerDate.shiftedBy(dt));
341             state = state.
342                     addAdditionalData("quadratic", dt * dt).
343                     addAdditionalStateDerivative("quadratic-dot", dt * dt);
344             sample.add(state);
345         }
346 
347         double maxErrorP = 0;
348         double maxErrorV = 0;
349         double maxErrorA = 0;
350         double maxErrorM = 0;
351         for (double dt = 0; dt < 900.0; dt += 5) {
352             SpacecraftState interpolated = interpolator.interpolate(centerDate.shiftedBy(dt), sample);
353             SpacecraftState propagated   = absPVPropagator.propagate(centerDate.shiftedBy(dt));
354             PVCoordinates   dpv          = new PVCoordinates(propagated.getPVCoordinates(), interpolated.getPVCoordinates());
355             maxErrorP = FastMath.max(maxErrorP, dpv.getPosition().getNorm());
356             maxErrorV = FastMath.max(maxErrorV, dpv.getVelocity().getNorm());
357             maxErrorA =
358                     FastMath.max(maxErrorA, FastMath.toDegrees(Rotation.distance(interpolated.getAttitude().getRotation(),
359                             propagated.getAttitude().getRotation())));
360             maxErrorM = FastMath.max(maxErrorM, FastMath.abs(interpolated.getMass() - propagated.getMass()));
361         }
362         Assertions.assertEquals(expectedErrorP, maxErrorP, 1.0e-3);
363         Assertions.assertEquals(expectedErrorV, maxErrorV, 1.0e-6);
364         Assertions.assertEquals(expectedErrorA, maxErrorA, 4.0e-10);
365         Assertions.assertEquals(expectedErrorM, maxErrorM, 1.0e-15);
366     }
367 
368     @Test
369     @DisplayName("test error thrown when using different state definition")
370     void testErrorThrownWhenUsingDifferentStateDefinition() {
371         // Given
372         final AbsoluteDate interpolationDate = new AbsoluteDate();
373 
374         final SpacecraftState orbitDefinedState = Mockito.mock(SpacecraftState.class);
375         final SpacecraftState absPVDefinedState = Mockito.mock(SpacecraftState.class);
376 
377         Mockito.when(orbitDefinedState.isOrbitDefined()).thenReturn(true);
378         Mockito.when(absPVDefinedState.isOrbitDefined()).thenReturn(false);
379 
380         final List<SpacecraftState> states = new ArrayList<>();
381         states.add(orbitDefinedState);
382         states.add(absPVDefinedState);
383 
384         // Create interpolator
385         final Frame inertialFrameMock = Mockito.mock(Frame.class);
386         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
387 
388         final TimeInterpolator<SpacecraftState> stateInterpolator =
389                 new SpacecraftStateInterpolator(2, inertialFrameMock, inertialFrameMock);
390 
391         // When & Then
392         Exception thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class,
393                 () -> stateInterpolator.interpolate(interpolationDate, states));
394 
395         Assertions.assertEquals(
396                 "one state is defined using an orbit while the other is defined using an absolute position-velocity-acceleration",
397                 thrown.getMessage());
398     }
399 
400     @Test
401     @DisplayName("test error thrown when using different state definition")
402     void testErrorThrownWhenGivingNoInterpolatorForState() {
403         // Given
404         final Frame inertialFrameMock = Mockito.mock(Frame.class);
405         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
406 
407         // When & Then
408         Exception thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class,
409                 () -> new SpacecraftStateInterpolator(
410                         AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
411                         AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
412                         inertialFrameMock, null, null,
413                         null, null, null));
414 
415         Assertions.assertEquals("creating a spacecraft state interpolator requires at least one orbit interpolator or an "
416                 + "absolute position-velocity-acceleration interpolator", thrown.getMessage());
417     }
418 
419     @Test
420     @DisplayName("test error thrown when giving wrong sample type for interpolation")
421     void testErrorThrownWhenGivingWrongSampleTypeForInterpolator() {
422         // Given
423         final AbsoluteDate interpolationDate = new AbsoluteDate();
424 
425         final Frame inertialFrame = FramesFactory.getEME2000();
426 
427         final SpacecraftState absPVDefinedState1 = Mockito.mock(SpacecraftState.class);
428         final SpacecraftState absPVDefinedState2 = Mockito.mock(SpacecraftState.class);
429 
430         Mockito.when(absPVDefinedState1.isOrbitDefined()).thenReturn(false);
431         Mockito.when(absPVDefinedState2.isOrbitDefined()).thenReturn(false);
432 
433         Mockito.when(absPVDefinedState1.getDate()).thenReturn(interpolationDate);
434         Mockito.when(absPVDefinedState2.getDate()).thenReturn(interpolationDate.shiftedBy(1));
435 
436         final List<SpacecraftState> states = new ArrayList<>();
437         states.add(absPVDefinedState1);
438         states.add(absPVDefinedState2);
439 
440         // Create interpolator
441         @SuppressWarnings("unchecked") final TimeInterpolator<Orbit> orbitInterpolatorMock = Mockito.mock(TimeInterpolator.class);
442 
443         final TimeInterpolator<SpacecraftState> interpolator =
444                 new SpacecraftStateInterpolator(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
445                         AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
446                         inertialFrame, orbitInterpolatorMock, null, null, null, null);
447 
448         // When & Then
449         Exception thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class, () ->
450                 interpolator.interpolate(interpolationDate, states));
451 
452         Assertions.assertEquals("wrong interpolator defined for this spacecraft state type (orbit or absolute PV)",
453                 thrown.getMessage());
454     }
455 
456     @Test
457     void testGetNbInterpolationsWithMultipleSubInterpolators() {
458         // GIVEN
459         // Create mock interpolators
460         final Frame frame = Mockito.mock(Frame.class);
461 
462         final TimeInterpolator<Orbit> orbitInterpolator = Mockito.mock(OrbitHermiteInterpolator.class);
463 
464         final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolator =
465                 Mockito.mock(AbsolutePVCoordinatesHermiteInterpolator.class);
466 
467         final TimeInterpolator<TimeStampedDouble> massInterpolator =
468                 Mockito.mock(TimeStampedDoubleHermiteInterpolator.class);
469 
470         final TimeInterpolator<Attitude> attitudeInterpolator = Mockito.mock(AttitudeInterpolator.class);
471 
472         final TimeInterpolator<TimeStampedDouble> additionalStateInterpolator =
473                 Mockito.mock(TimeStampedDoubleHermiteInterpolator.class);
474 
475         // Implement mocks behaviours
476         final int orbitNbInterpolationPoints           = 2;
477         final int absPVANbInterpolationPoints          = 3;
478         final int massNbInterpolationPoints            = 4;
479         final int AttitudeNbInterpolationPoints        = 5;
480         final int AdditionalStateNbInterpolationPoints = 6;
481 
482         Mockito.when(orbitInterpolator.getNbInterpolationPoints()).thenReturn(orbitNbInterpolationPoints);
483         Mockito.when(absPVAInterpolator.getNbInterpolationPoints()).thenReturn(absPVANbInterpolationPoints);
484         Mockito.when(massInterpolator.getNbInterpolationPoints()).thenReturn(massNbInterpolationPoints);
485         Mockito.when(attitudeInterpolator.getNbInterpolationPoints()).thenReturn(AttitudeNbInterpolationPoints);
486         Mockito.when(additionalStateInterpolator.getNbInterpolationPoints()).thenReturn(AdditionalStateNbInterpolationPoints);
487 
488         Mockito.when(orbitInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(orbitInterpolator));
489         Mockito.when(absPVAInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(absPVAInterpolator));
490         Mockito.when(massInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(massInterpolator));
491         Mockito.when(attitudeInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(attitudeInterpolator));
492         Mockito.when(additionalStateInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(additionalStateInterpolator));
493 
494         final SpacecraftStateInterpolator stateInterpolator =
495                 new SpacecraftStateInterpolator(2, 1.0e-3, frame, orbitInterpolator, absPVAInterpolator, massInterpolator,
496                         attitudeInterpolator, additionalStateInterpolator);
497 
498         // WHEN
499         final int returnedNbInterpolationPoints = stateInterpolator.getNbInterpolationPoints();
500 
501         // THEN
502         Assertions.assertEquals(AdditionalStateNbInterpolationPoints, returnedNbInterpolationPoints);
503     }
504 
505     @SuppressWarnings("deprecation")
506     @Test
507     void testGetNbInterpolationsWithMultipleSubInterpolatorsDeprecated() {
508         // GIVEN
509         // Create mock interpolators
510         final Frame frame = Mockito.mock(Frame.class);
511 
512         final TimeInterpolator<Orbit> orbitInterpolator = Mockito.mock(OrbitHermiteInterpolator.class);
513 
514         final TimeInterpolator<AbsolutePVCoordinates> absPVAInterpolator =
515                 Mockito.mock(AbsolutePVCoordinatesHermiteInterpolator.class);
516 
517         final TimeInterpolator<TimeStampedDouble> massInterpolator =
518                 Mockito.mock(TimeStampedDoubleHermiteInterpolator.class);
519 
520         final TimeInterpolator<Attitude> attitudeInterpolator = Mockito.mock(AttitudeInterpolator.class);
521 
522         final TimeInterpolator<TimeStampedDouble> additionalStateInterpolator =
523                 Mockito.mock(TimeStampedDoubleHermiteInterpolator.class);
524 
525         // Implement mocks behaviours
526         final int orbitNbInterpolationPoints           = 2;
527         final int absPVANbInterpolationPoints          = 3;
528         final int massNbInterpolationPoints            = 4;
529         final int AttitudeNbInterpolationPoints        = 5;
530         final int AdditionalStateNbInterpolationPoints = 6;
531 
532         Mockito.when(orbitInterpolator.getNbInterpolationPoints()).thenReturn(orbitNbInterpolationPoints);
533         Mockito.when(absPVAInterpolator.getNbInterpolationPoints()).thenReturn(absPVANbInterpolationPoints);
534         Mockito.when(massInterpolator.getNbInterpolationPoints()).thenReturn(massNbInterpolationPoints);
535         Mockito.when(attitudeInterpolator.getNbInterpolationPoints()).thenReturn(AttitudeNbInterpolationPoints);
536         Mockito.when(additionalStateInterpolator.getNbInterpolationPoints()).thenReturn(AdditionalStateNbInterpolationPoints);
537 
538         Mockito.when(orbitInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(orbitInterpolator));
539         Mockito.when(absPVAInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(absPVAInterpolator));
540         Mockito.when(massInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(massInterpolator));
541         Mockito.when(attitudeInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(attitudeInterpolator));
542         Mockito.when(additionalStateInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(additionalStateInterpolator));
543 
544         final SpacecraftStateInterpolator stateInterpolator =
545                 new SpacecraftStateInterpolator(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
546                                                 AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
547                                                 frame, orbitInterpolator, absPVAInterpolator, massInterpolator,
548                                                 attitudeInterpolator, additionalStateInterpolator);
549 
550         // WHEN
551         final int returnedNbInterpolationPoints = stateInterpolator.getNbInterpolationPoints();
552 
553         // THEN
554         Assertions.assertEquals(AdditionalStateNbInterpolationPoints, returnedNbInterpolationPoints);
555     }
556 
557     @Test
558     @DisplayName("test error thrown when giving empty sample")
559     void testErrorThrownWhenGivingEmptySample() {
560         // Given
561         final AbsoluteDate interpolationDate = new AbsoluteDate();
562 
563         final Frame inertialFrame = FramesFactory.getEME2000();
564 
565         final List<SpacecraftState> states = new ArrayList<>();
566 
567         // Create interpolator
568         @SuppressWarnings("unchecked") final TimeInterpolator<Orbit> orbitInterpolatorMock = Mockito.mock(TimeInterpolator.class);
569 
570         final TimeInterpolator<SpacecraftState> interpolator =
571                 new SpacecraftStateInterpolator(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
572                         AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
573                         inertialFrame, orbitInterpolatorMock, null, null, null, null);
574 
575         // When & Then
576         OrekitIllegalArgumentException thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class, () ->
577                 interpolator.interpolate(interpolationDate, states));
578 
579         Assertions.assertEquals(OrekitMessages.NOT_ENOUGH_DATA, thrown.getSpecifier());
580         Assertions.assertEquals(0, ((Integer) thrown.getParts()[0]).intValue());
581 
582     }
583 
584     @Test
585     void testSpacecraftStateInterpolatorCreation() {
586         // Given
587         final Frame inertialFrameMock = Mockito.mock(Frame.class);
588         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
589 
590         @SuppressWarnings("unchecked") final TimeInterpolator<Orbit> orbitInterpolatorMock =
591                 Mockito.mock(TimeInterpolator.class);
592 
593         @SuppressWarnings("unchecked") final TimeInterpolator<AbsolutePVCoordinates> absPVInterpolatorMock =
594                 Mockito.mock(TimeInterpolator.class);
595 
596         @SuppressWarnings("unchecked") final TimeInterpolator<TimeStampedDouble> massInterpolatorMock =
597                 Mockito.mock(TimeInterpolator.class);
598 
599         @SuppressWarnings("unchecked") final TimeInterpolator<Attitude> attitudeInterpolatorMock =
600                 Mockito.mock(TimeInterpolator.class);
601 
602         @SuppressWarnings("unchecked") final TimeInterpolator<TimeStampedDouble> additionalInterpolatorMock =
603                 Mockito.mock(TimeInterpolator.class);
604 
605         // When
606         final SpacecraftStateInterpolator interpolator =
607                 new SpacecraftStateInterpolator(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
608                         AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
609                         inertialFrameMock, orbitInterpolatorMock, absPVInterpolatorMock,
610                         massInterpolatorMock, attitudeInterpolatorMock, additionalInterpolatorMock);
611 
612         // Then
613         Assertions.assertEquals(inertialFrameMock, interpolator.getOutputFrame());
614         Assertions.assertEquals(orbitInterpolatorMock, interpolator.getOrbitInterpolator().get());
615         Assertions.assertEquals(absPVInterpolatorMock, interpolator.getAbsPVAInterpolator().get());
616         Assertions.assertEquals(massInterpolatorMock, interpolator.getMassInterpolator().get());
617         Assertions.assertEquals(attitudeInterpolatorMock, interpolator.getAttitudeInterpolator().get());
618         Assertions.assertEquals(additionalInterpolatorMock, interpolator.getAdditionalStateInterpolator().get());
619 
620     }
621 
622     @Test
623     void testIssue1266() {
624         // Given
625         final Frame inertialFrameMock = Mockito.mock(Frame.class);
626         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
627         final int    interpolationPoints    = 3;
628         final double extrapolationThreshold = 10;
629 
630         // When
631         final SpacecraftStateInterpolator interpolator =
632                 new SpacecraftStateInterpolator(interpolationPoints, extrapolationThreshold,
633                         inertialFrameMock, inertialFrameMock);
634 
635         // Then
636         Assertions.assertEquals(extrapolationThreshold, interpolator.getExtrapolationThreshold());
637 
638     }
639 
640     @Test
641     @DisplayName("Test error thrown when sub interpolator is not present")
642     void testErrorThrownWhenSubInterpolatorIsNotPresent() {
643         // GIVEN
644         final FakeStateInterpolator fakeStateInterpolator = new FakeStateInterpolator();
645 
646         // WHEN & THEN
647         Assertions.assertThrows(OrekitInternalError.class, fakeStateInterpolator::getNbInterpolationPoints);
648     }
649 
650     private static class FakeStateInterpolator extends AbstractTimeInterpolator<SpacecraftState> {
651 
652         public FakeStateInterpolator() {
653             super(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
654                   AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC);
655         }
656 
657         @Override
658         protected SpacecraftState interpolate(AbstractTimeInterpolator<SpacecraftState>.InterpolationData interpolationData) {
659             return null;
660         }
661 
662         @Override
663         public List<TimeInterpolator<? extends TimeStamped>> getSubInterpolators() {
664             return Collections.emptyList();
665         }
666     }
667 }