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.Field;
20  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
21  import org.hipparchus.geometry.euclidean.threed.Rotation;
22  import org.hipparchus.ode.FieldODEIntegrator;
23  import org.hipparchus.ode.nonstiff.DormandPrince853FieldIntegrator;
24  import org.hipparchus.util.Binary64;
25  import org.hipparchus.util.Binary64Field;
26  import org.hipparchus.util.FastMath;
27  import org.junit.jupiter.api.AfterEach;
28  import org.junit.jupiter.api.Assertions;
29  import org.junit.jupiter.api.BeforeEach;
30  import org.junit.jupiter.api.DisplayName;
31  import org.junit.jupiter.api.Test;
32  import org.mockito.Mockito;
33  import org.orekit.Utils;
34  import org.orekit.attitudes.AttitudeProvider;
35  import org.orekit.attitudes.BodyCenterPointing;
36  import org.orekit.attitudes.FieldAttitude;
37  import org.orekit.attitudes.FieldAttitudeInterpolator;
38  import org.orekit.bodies.CelestialBodyFactory;
39  import org.orekit.bodies.OneAxisEllipsoid;
40  import org.orekit.errors.OrekitException;
41  import org.orekit.errors.OrekitIllegalArgumentException;
42  import org.orekit.errors.OrekitInternalError;
43  import org.orekit.errors.OrekitMessages;
44  import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
45  import org.orekit.forces.gravity.SingleBodyAbsoluteAttraction;
46  import org.orekit.forces.gravity.potential.GravityFieldFactory;
47  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
48  import org.orekit.frames.Frame;
49  import org.orekit.frames.FramesFactory;
50  import org.orekit.orbits.FieldKeplerianOrbit;
51  import org.orekit.orbits.FieldOrbit;
52  import org.orekit.orbits.FieldOrbitHermiteInterpolator;
53  import org.orekit.orbits.OrbitType;
54  import org.orekit.orbits.PositionAngleType;
55  import org.orekit.propagation.analytical.FieldEcksteinHechlerPropagator;
56  import org.orekit.propagation.numerical.FieldNumericalPropagator;
57  import org.orekit.time.AbsoluteDate;
58  import org.orekit.time.AbstractFieldTimeInterpolator;
59  import org.orekit.time.AbstractTimeInterpolator;
60  import org.orekit.time.DateComponents;
61  import org.orekit.time.FieldAbsoluteDate;
62  import org.orekit.time.FieldTimeInterpolator;
63  import org.orekit.time.FieldTimeStamped;
64  import org.orekit.time.TimeComponents;
65  import org.orekit.time.TimeScalesFactory;
66  import org.orekit.time.TimeStampedField;
67  import org.orekit.time.TimeStampedFieldHermiteInterpolator;
68  import org.orekit.utils.AngularDerivativesFilter;
69  import org.orekit.utils.CartesianDerivativesFilter;
70  import org.orekit.utils.Constants;
71  import org.orekit.utils.FieldAbsolutePVCoordinates;
72  import org.orekit.utils.FieldAbsolutePVCoordinatesHermiteInterpolator;
73  import org.orekit.utils.FieldPVCoordinates;
74  import org.orekit.utils.IERSConventions;
75  
76  import java.util.ArrayList;
77  import java.util.Collections;
78  import java.util.List;
79  
80  class FieldSpacecraftStateInterpolatorTest {
81  
82      final private Field<Binary64>                      field = Binary64Field.getInstance();
83      private       Binary64                             mass;
84      private       FieldOrbit<Binary64>                 orbit;
85      private       FieldAbsolutePVCoordinates<Binary64> absPV;
86      private       AttitudeProvider                     attitudeLaw;
87      private       FieldPropagator<Binary64>            analyticalPropagator;
88      private       FieldPropagator<Binary64>            absPVPropagator;
89  
90      @BeforeEach
91      public void setUp() {
92          try {
93              Utils.setDataRoot("regular-data:potential/icgem-format");
94              Binary64 mu  = new Binary64(3.9860047e14);
95              double   ae  = 6.378137e6;
96              double   c20 = -1.08263e-3;
97              double   c30 = 2.54e-6;
98              double   c40 = 1.62e-6;
99              double   c50 = 2.3e-7;
100             double   c60 = -5.5e-7;
101 
102             mass = new Binary64(2500);
103             Binary64 a     = new Binary64(7187990.1979844316);
104             Binary64 e     = new Binary64(0.5e-4);
105             Binary64 i     = new Binary64(1.7105407051081795);
106             Binary64 omega = new Binary64(1.9674147913622104);
107             Binary64 OMEGA = new Binary64(FastMath.toRadians(261));
108             Binary64 lv    = new Binary64(0);
109 
110             FieldAbsoluteDate<Binary64> date = new FieldAbsoluteDate<>(field, new DateComponents(2004, 1, 1),
111                                                                        TimeComponents.H00,
112                                                                        TimeScalesFactory.getUTC());
113             final Frame frame = FramesFactory.getEME2000();
114 
115             orbit = new FieldKeplerianOrbit<>(a, e, i, omega, OMEGA, lv, PositionAngleType.TRUE,
116                                               frame, date, mu);
117             OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
118                                                           Constants.WGS84_EARTH_FLATTENING,
119                                                           FramesFactory.getITRF(IERSConventions.IERS_2010, true));
120 
121             absPV = new FieldAbsolutePVCoordinates<>(frame, date, orbit.getPVCoordinates());
122 
123             attitudeLaw = new BodyCenterPointing(orbit.getFrame(), earth);
124 
125             analyticalPropagator =
126                     new FieldEcksteinHechlerPropagator<>(orbit, attitudeLaw, mass,
127                                                          ae, mu, c20, c30, c40, c50, c60);
128 
129             absPVPropagator = setUpNumericalPropagator();
130 
131         }
132         catch (OrekitException oe) {
133             Assertions.fail(oe.getLocalizedMessage());
134         }
135     }
136 
137     @AfterEach
138     public void tearDown() {
139         mass                 = org.hipparchus.util.Binary64.NAN;
140         orbit                = null;
141         attitudeLaw          = null;
142         analyticalPropagator = null;
143     }
144 
145     @Test
146     public void testOrbitInterpolation()
147             throws OrekitException {
148 
149         // Given
150         final Frame inertialFrame = orbit.getFrame();
151 
152         final FieldSpacecraftStateInterpolator<Binary64> interpolator1 =
153                 new FieldSpacecraftStateInterpolator<>(2, inertialFrame);
154 
155         final FieldSpacecraftStateInterpolator<Binary64> interpolator2 =
156                 new FieldSpacecraftStateInterpolator<>(3, SpacecraftStateInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, inertialFrame);
157 
158         final FieldSpacecraftStateInterpolator<Binary64> interpolator3 =
159                 new FieldSpacecraftStateInterpolator<>(4, SpacecraftStateInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, inertialFrame, inertialFrame);
160 
161         // When & Then
162         checkStandardInterpolationError(2, 106.46533, 0.40709287, 169847806.33e-9, 0.0, 450 * 450, 450 * 450, interpolator1);
163         checkStandardInterpolationError(3, 0.00353, 0.00003250, 189886.01e-9, 0.0, 0.0, 0.0, interpolator2);
164         checkStandardInterpolationError(4, 0.00002, 0.00000023, 232.25e-9, 0.0, 0.0, 0.0, interpolator3);
165 
166     }
167 
168     @Test
169     public void testErrorThrownWhenOneInterpolatorIsNotConsistentWithSampleSize() {
170         // GIVEN
171         final Frame outputFrame = Mockito.mock(Frame.class);
172 
173         @SuppressWarnings("unchecked")
174         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolator =
175                 Mockito.mock(FieldTimeInterpolator.class);
176         Mockito.when(orbitInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(orbitInterpolator));
177         Mockito.when(orbitInterpolator.getNbInterpolationPoints()).thenReturn(2);
178 
179         @SuppressWarnings("unchecked")
180         final FieldTimeInterpolator<FieldAbsolutePVCoordinates<Binary64>, Binary64> absPVInterpolator =
181                 Mockito.mock(FieldTimeInterpolator.class);
182         Mockito.when(absPVInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(absPVInterpolator));
183         Mockito.when(absPVInterpolator.getNbInterpolationPoints()).thenReturn(4);
184 
185         @SuppressWarnings("unchecked")
186         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> massInterpolator =
187                 Mockito.mock(FieldTimeInterpolator.class);
188         Mockito.when(massInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(massInterpolator));
189         Mockito.when(massInterpolator.getNbInterpolationPoints()).thenReturn(2);
190 
191         final FieldSpacecraftStateInterpolator<Binary64> stateInterpolator =
192                 new FieldSpacecraftStateInterpolator<>(2, 1.0e-3, outputFrame, orbitInterpolator, absPVInterpolator, massInterpolator,
193                                                        null, null);
194 
195         // WHEN & THEN
196         OrekitIllegalArgumentException thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class, () ->
197                 AbstractFieldTimeInterpolator.checkInterpolatorCompatibilityWithSampleSize(stateInterpolator, 2));
198 
199         Assertions.assertEquals(OrekitMessages.NOT_ENOUGH_DATA, thrown.getSpecifier());
200         Assertions.assertEquals(2, ((Integer) thrown.getParts()[0]).intValue());
201 
202     }
203 
204     @Test
205     public void testErrorThrownWhenOneInterpolatorIsNotConsistentWithSampleSizeDeprecated() {
206         // GIVEN
207         final Frame outputFrame = Mockito.mock(Frame.class);
208 
209         @SuppressWarnings("unchecked")
210         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolator =
211                 Mockito.mock(FieldTimeInterpolator.class);
212         Mockito.when(orbitInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(orbitInterpolator));
213         Mockito.when(orbitInterpolator.getNbInterpolationPoints()).thenReturn(2);
214 
215         @SuppressWarnings("unchecked")
216         final FieldTimeInterpolator<FieldAbsolutePVCoordinates<Binary64>, Binary64> absPVInterpolator =
217                 Mockito.mock(FieldTimeInterpolator.class);
218         Mockito.when(absPVInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(absPVInterpolator));
219         Mockito.when(absPVInterpolator.getNbInterpolationPoints()).thenReturn(4);
220 
221         @SuppressWarnings("unchecked")
222         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> massInterpolator =
223                 Mockito.mock(FieldTimeInterpolator.class);
224         Mockito.when(massInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(massInterpolator));
225         Mockito.when(massInterpolator.getNbInterpolationPoints()).thenReturn(2);
226 
227         final FieldSpacecraftStateInterpolator<Binary64> stateInterpolator =
228                 new FieldSpacecraftStateInterpolator<>(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
229                                                        AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
230                                                        outputFrame, orbitInterpolator, absPVInterpolator, massInterpolator,
231                                                        null, null);
232 
233         // WHEN & THEN
234         OrekitIllegalArgumentException thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class, () ->
235                 AbstractFieldTimeInterpolator.checkInterpolatorCompatibilityWithSampleSize(stateInterpolator, 2));
236 
237         Assertions.assertEquals(OrekitMessages.NOT_ENOUGH_DATA, thrown.getSpecifier());
238         Assertions.assertEquals(2, ((Integer) thrown.getParts()[0]).intValue());
239 
240     }
241 
242     @Test
243     public void testAbsPVAInterpolation()
244             throws OrekitException {
245 
246         // Given
247         final int interpolationPoints1 = 2;
248         final int interpolationPoints2 = 3;
249         final int interpolationPoints3 = 4;
250 
251         final Frame intertialFrame = absPV.getFrame();
252 
253         // Create interpolator with different number of interpolation points and derivative filters (P/R, PV/RR, PVA/RRR)
254         final FieldSpacecraftStateInterpolator<Binary64>[] interpolator1 =
255                 buildAllTypeOfInterpolator(interpolationPoints1, intertialFrame);
256         final FieldSpacecraftStateInterpolator<Binary64>[] interpolator2 =
257                 buildAllTypeOfInterpolator(interpolationPoints2, intertialFrame);
258         final FieldSpacecraftStateInterpolator<Binary64>[] interpolator3 =
259                 buildAllTypeOfInterpolator(interpolationPoints3, intertialFrame);
260 
261         // P and R
262         checkAbsPVInterpolationError(interpolationPoints1, 766704.6033758943, 3385.895505018284,
263                                      9.503905101141868, 0.0, interpolator1[0]);
264         checkAbsPVInterpolationError(interpolationPoints2, 46190.78568215623, 531.3506621730367,
265                                      0.5601906427491941, 0, interpolator2[0]);
266         checkAbsPVInterpolationError(interpolationPoints3, 2787.7069621834926, 55.5146607205871,
267                                      0.03372344505743245, 0.0, interpolator3[0]);
268 
269         // PV and RR
270         checkAbsPVInterpolationError(interpolationPoints1, 14023.999059896296, 48.022197580401084,
271                                      0.16984517369482555, 0.0, interpolator1[1]);
272         checkAbsPVInterpolationError(interpolationPoints2, 16.186825338590722, 0.13418685366189476,
273                                      1.898961129289559E-4, 0, interpolator2[1]);
274         checkAbsPVInterpolationError(interpolationPoints3, 0.025110113133073413, 3.5069332429486154E-4,
275                                      2.3306042475258594E-7, 0.0, interpolator3[1]);
276 
277         // PVA and RRR
278         checkAbsPVInterpolationError(interpolationPoints1, 108.13907262943746, 0.4134494277844817,
279                                      0.001389170843175492, 0.0, interpolator1[2]);
280         checkAbsPVInterpolationError(interpolationPoints2, 0.002974408269435121, 2.6937387601886076E-5,
281                                      2.051629855188969E-4, 0, interpolator2[2]);
282         checkAbsPVInterpolationError(interpolationPoints3, 0, 0, 1.3779131041190534E-4,
283                                      0.0, interpolator3[2]);
284     }
285 
286     @Test
287     public void testIssue775() {
288         final Field<Binary64> field = Binary64Field.getInstance();
289         final Binary64        zero  = field.getZero();
290 
291         // Conversion from double to Field
292         FieldAbsoluteDate<Binary64> initDate = new FieldAbsoluteDate<>(field, new AbsoluteDate(
293                 new DateComponents(2004, 1, 1), TimeComponents.H00, TimeScalesFactory.getUTC()));
294         FieldAbsoluteDate<Binary64> finalDate = new FieldAbsoluteDate<>(field, new AbsoluteDate(
295                 new DateComponents(2004, 1, 2), TimeComponents.H00, TimeScalesFactory.getUTC()));
296         Frame inertialFrame = FramesFactory.getEME2000();
297 
298         // Initial PV coordinates
299         final FieldVector3D<Binary64> position = new FieldVector3D<>(zero.add(-29536113.0),
300                                                                      zero.add(30329259.0),
301                                                                      zero.add(-100125.0));
302         final FieldVector3D<Binary64> velocity = new FieldVector3D<>(zero.add(-2194.0),
303                                                                      zero.add(-2141.0),
304                                                                      zero.add(-8.0));
305 
306         final FieldPVCoordinates<Binary64>         pv        = new FieldPVCoordinates<>(position, velocity);
307         final FieldAbsolutePVCoordinates<Binary64> initAbsPV = new FieldAbsolutePVCoordinates<>(inertialFrame, initDate, pv);
308 
309         // Input parameters
310         int      numberOfIntervals = 15;
311         Binary64 deltaT            = finalDate.durationFrom(initDate).divide(numberOfIntervals);
312 
313         // Build the list of spacecraft states
314         List<FieldSpacecraftState<Binary64>> states = new ArrayList<>(numberOfIntervals + 1);
315         for (int j = 0; j <= numberOfIntervals; j++) {
316             states.add(new FieldSpacecraftState<>(initAbsPV).shiftedBy(deltaT.multiply(j)));
317         }
318 
319         // Get initial state without orbit
320         FieldSpacecraftState<Binary64> withoutOrbit;
321 
322         // Create orbit Hermite interpolator
323         final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> interpolator =
324                 new FieldSpacecraftStateInterpolator<>(states.size(), inertialFrame);
325 
326         // Interpolation
327         withoutOrbit = interpolator.interpolate(states.get(10).getDate(), states);
328         Assertions.assertEquals(0.0, FieldVector3D.distance(withoutOrbit.getAbsPVA().getPosition(),
329                                                             states.get(10).getAbsPVA().getPosition()).getReal(), 1.0e-10);
330     }
331 
332     /**
333      * Set up a numerical propagator for spacecraft state defined by an absolute position-velocity-acceleration. It is
334      * designed to be similar to the EcksteinHechler propagator.
335      * <p>
336      * It has attraction towards Earth + 6x6 earth potential as forces.
337      *
338      * @return numerical propagator for spacecraft state defined by an absolute position-velocity-acceleration
339      */
340     private FieldPropagator<Binary64> setUpNumericalPropagator() {
341 
342         // Create propagator
343         final FieldODEIntegrator<Binary64> integrator = setUpDefaultIntegrator();
344 
345         final FieldNumericalPropagator<Binary64> propagator = new FieldNumericalPropagator<>(field, integrator);
346 
347         // Configure propagator
348         propagator.setOrbitType(null);
349 
350         // Add forces
351         final Frame                                itrf      = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
352         final NormalizedSphericalHarmonicsProvider provider  = GravityFieldFactory.getNormalizedProvider(6, 6);
353         final HolmesFeatherstoneAttractionModel    potential = new HolmesFeatherstoneAttractionModel(itrf, provider);
354 
355         propagator.addForceModel(potential);
356         propagator.addForceModel(new SingleBodyAbsoluteAttraction(CelestialBodyFactory.getEarth()));
357 
358         // Set initial state
359         final FieldSpacecraftState<Binary64> initialState = new FieldSpacecraftState<>(absPV);
360 
361         propagator.setInitialState(initialState);
362 
363         // Set attitude law
364         propagator.setAttitudeProvider(attitudeLaw);
365 
366         return propagator;
367     }
368 
369     /**
370      * Set up default integrator for numerical propagator
371      *
372      * @return default integrator for numerical propagator
373      */
374     private FieldODEIntegrator<Binary64> setUpDefaultIntegrator() {
375         final Binary64   dP         = new Binary64(1);
376         final double     minStep    = 0.001;
377         final double     maxStep    = 100;
378         final double[][] tolerances = ToleranceProvider.getDefaultToleranceProvider(dP.getReal()).getTolerances(orbit, OrbitType.CARTESIAN);
379 
380         return new DormandPrince853FieldIntegrator<>(field, minStep, maxStep, tolerances[0], tolerances[1]);
381     }
382 
383     /**
384      * Build spacecraft state Hermite interpolators using all kind of position-velocity-acceleration derivatives and angular
385      * derivatives filter.
386      *
387      * @param interpolationPoints number of interpolation points
388      * @param inertialFrame inertial frame
389      *
390      * @return array of spacecraft state Hermite interpolators containing all possible configuration (3 in total)
391      */
392     private FieldSpacecraftStateInterpolator<Binary64>[] buildAllTypeOfInterpolator(final int interpolationPoints,
393                                                                                     final Frame inertialFrame) {
394 
395         final CartesianDerivativesFilter[] pvaFilters     = CartesianDerivativesFilter.values();
396         final AngularDerivativesFilter[]   angularFilters = AngularDerivativesFilter.values();
397 
398         final int dim = pvaFilters.length;
399         @SuppressWarnings("unchecked")
400         final FieldSpacecraftStateInterpolator<Binary64>[] interpolators =
401                 new FieldSpacecraftStateInterpolator[dim];
402 
403         for (int i = 0; i < dim; i++) {
404             interpolators[i] =
405                     new FieldSpacecraftStateInterpolator<>(interpolationPoints,
406                                                            AbstractFieldTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
407                                                            inertialFrame, inertialFrame,
408                                                            pvaFilters[i], angularFilters[i]);
409         }
410 
411         return interpolators;
412     }
413 
414     /**
415      * Check interpolation error for position, velocity , attitude, mass, additional state and associated derivatives. This
416      * method was designed to test interpolation on orbit defined spacecraft states.
417      *
418      * @param n sample size
419      * @param expectedErrorP expected position error
420      * @param expectedErrorV expected velocity error
421      * @param expectedErrorA expected attitude error
422      * @param expectedErrorM expected mass error
423      * @param expectedErrorQ expected additional state error
424      * @param expectedErrorD expected additional state derivative error
425      * @param interpolator state interpolator
426      */
427     private void checkStandardInterpolationError(int n, double expectedErrorP, double expectedErrorV,
428                                                  double expectedErrorA, double expectedErrorM,
429                                                  double expectedErrorQ, double expectedErrorD,
430                                                  final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> interpolator) {
431         FieldAbsoluteDate<Binary64>          centerDate = orbit.getDate().shiftedBy(100.0);
432         List<FieldSpacecraftState<Binary64>> sample     = new ArrayList<>();
433         for (int i = 0; i < n; ++i) {
434             Binary64                       dt    = new Binary64(i * 900.0 / (n - 1));
435             FieldSpacecraftState<Binary64> state = analyticalPropagator.propagate(centerDate.shiftedBy(dt));
436             state = state.
437                     addAdditionalData("quadratic", dt.multiply(dt)).
438                     addAdditionalStateDerivative("quadratic-dot", dt.multiply(dt));
439             sample.add(state);
440         }
441 
442         double maxErrorP = 0;
443         double maxErrorV = 0;
444         double maxErrorA = 0;
445         double maxErrorM = 0;
446         double maxErrorQ = 0;
447         double maxErrorD = 0;
448         for (double dt = 0; dt < 900.0; dt += 5) {
449             FieldSpacecraftState<Binary64> interpolated = interpolator.interpolate(centerDate.shiftedBy(dt), sample);
450             FieldSpacecraftState<Binary64> propagated   = analyticalPropagator.propagate(centerDate.shiftedBy(dt));
451             FieldPVCoordinates<Binary64> dpv =
452                     new FieldPVCoordinates<>(propagated.getPVCoordinates(), interpolated.getPVCoordinates());
453             maxErrorP = FastMath.max(maxErrorP, dpv.getPosition().getNorm().getReal());
454             maxErrorV = FastMath.max(maxErrorV, dpv.getVelocity().getNorm().getReal());
455             maxErrorA =
456                     FastMath.max(maxErrorA,
457                                  FastMath.toDegrees(Rotation.distance(interpolated.getAttitude().getRotation().toRotation(),
458                                                                       propagated.getAttitude().getRotation().toRotation())));
459             maxErrorM =
460                     FastMath.max(maxErrorM, FastMath.abs(interpolated.getMass().getReal() - propagated.getMass().getReal()));
461             maxErrorQ = FastMath.max(maxErrorQ,
462                                      FastMath.abs(interpolated.getAdditionalState("quadratic")[0].getReal() - dt * dt));
463             maxErrorD =
464                     FastMath.max(maxErrorD,
465                                  FastMath.abs(interpolated.getAdditionalStateDerivative("quadratic-dot")[0].getReal()
466                                                       - dt * dt));
467         }
468         Assertions.assertEquals(expectedErrorP, maxErrorP, 1.0e-3);
469         Assertions.assertEquals(expectedErrorV, maxErrorV, 1.0e-6);
470         Assertions.assertEquals(expectedErrorA, maxErrorA, 4.0e-10);
471         Assertions.assertEquals(expectedErrorM, maxErrorM, 1.0e-15);
472         Assertions.assertEquals(expectedErrorQ, maxErrorQ, 2.0e-10);
473         Assertions.assertEquals(expectedErrorD, maxErrorD, 2.0e-10);
474     }
475 
476     /**
477      * Check interpolation error for position, velocity , attitude and mass only. This method was designed to test
478      * interpolation on spacecraft states defined by absolute position-velocity-acceleration errors for better code
479      * coverage.
480      *
481      * @param n sample size
482      * @param expectedErrorP expected position error
483      * @param expectedErrorV expected velocity error
484      * @param expectedErrorA expected attitude error
485      * @param expectedErrorM expected mass error
486      * @param interpolator state interpolator
487      */
488     private void checkAbsPVInterpolationError(int n, double expectedErrorP, double expectedErrorV,
489                                               double expectedErrorA, double expectedErrorM,
490                                               final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> interpolator) {
491         FieldAbsoluteDate<Binary64>          centerDate = absPV.getDate().shiftedBy(100.0);
492         List<FieldSpacecraftState<Binary64>> sample     = new ArrayList<>();
493         for (int i = 0; i < n; ++i) {
494             Binary64                       dt    = new Binary64(i * 900.0 / (n - 1));
495             FieldSpacecraftState<Binary64> state = absPVPropagator.propagate(centerDate.shiftedBy(dt));
496             sample.add(state);
497         }
498 
499         double maxErrorP = 0;
500         double maxErrorV = 0;
501         double maxErrorA = 0;
502         double maxErrorM = 0;
503         for (double dt = 0; dt < 900.0; dt += 5) {
504             FieldSpacecraftState<Binary64> interpolated = interpolator.interpolate(centerDate.shiftedBy(dt), sample);
505             FieldSpacecraftState<Binary64> propagated   = absPVPropagator.propagate(centerDate.shiftedBy(dt));
506             FieldPVCoordinates<Binary64> dpv =
507                     new FieldPVCoordinates<>(propagated.getPVCoordinates(), interpolated.getPVCoordinates());
508             maxErrorP = FastMath.max(maxErrorP, dpv.getPosition().getNorm().getReal());
509             maxErrorV = FastMath.max(maxErrorV, dpv.getVelocity().getNorm().getReal());
510             maxErrorA =
511                     FastMath.max(maxErrorA,
512                                  FastMath.toDegrees(Rotation.distance(interpolated.getAttitude().getRotation().toRotation(),
513                                                                       propagated.getAttitude().getRotation().toRotation())));
514             maxErrorM =
515                     FastMath.max(maxErrorM, FastMath.abs(interpolated.getMass().getReal() - propagated.getMass().getReal()));
516         }
517         Assertions.assertEquals(expectedErrorP, maxErrorP, 1.0e-3);
518         Assertions.assertEquals(expectedErrorV, maxErrorV, 1.0e-6);
519         Assertions.assertEquals(expectedErrorA, maxErrorA, 4.0e-10);
520         Assertions.assertEquals(expectedErrorM, maxErrorM, 1.0e-15);
521     }
522 
523     @Test
524     void testErrorThrownWhenInterpolatingWithNonFieldDateAndEmptySample() {
525         // GIVEN
526         final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> interpolator =
527                 new FieldSpacecraftStateInterpolator<>(FramesFactory.getEME2000());
528 
529         final List<FieldSpacecraftState<Binary64>> states = new ArrayList<>();
530 
531         // WHEN & THEN
532         OrekitIllegalArgumentException thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class,
533                                                                         () -> interpolator.interpolate(new AbsoluteDate(), states.stream()));
534 
535         Assertions.assertEquals(OrekitMessages.NOT_ENOUGH_DATA, thrown.getSpecifier());
536         Assertions.assertEquals(0, ((Integer) thrown.getParts()[0]).intValue());
537 
538     }
539 
540     @Test
541     void testGetNbInterpolationsWithMultipleSubInterpolators() {
542         // GIVEN
543         // Create mock interpolators
544         final Frame frame = Mockito.mock(Frame.class);
545 
546         @SuppressWarnings("unchecked")
547         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolator =
548                 Mockito.mock(FieldOrbitHermiteInterpolator.class);
549         @SuppressWarnings("unchecked")
550         final FieldTimeInterpolator<FieldAbsolutePVCoordinates<Binary64>, Binary64> absPVAInterpolator =
551                 Mockito.mock(FieldAbsolutePVCoordinatesHermiteInterpolator.class);
552         @SuppressWarnings("unchecked")
553         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> massInterpolator =
554                 Mockito.mock(TimeStampedFieldHermiteInterpolator.class);
555         @SuppressWarnings("unchecked")
556         final FieldTimeInterpolator<FieldAttitude<Binary64>, Binary64> attitudeInterpolator =
557                 Mockito.mock(FieldAttitudeInterpolator.class);
558         @SuppressWarnings("unchecked")
559         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> additionalStateInterpolator =
560                 Mockito.mock(TimeStampedFieldHermiteInterpolator.class);
561 
562         // Implement mocks behaviours
563         final int orbitNbInterpolationPoints           = 2;
564         final int absPVANbInterpolationPoints          = 3;
565         final int massNbInterpolationPoints            = 4;
566         final int AttitudeNbInterpolationPoints        = 5;
567         final int AdditionalStateNbInterpolationPoints = 6;
568 
569         Mockito.when(orbitInterpolator.getNbInterpolationPoints()).thenReturn(orbitNbInterpolationPoints);
570         Mockito.when(absPVAInterpolator.getNbInterpolationPoints()).thenReturn(absPVANbInterpolationPoints);
571         Mockito.when(massInterpolator.getNbInterpolationPoints()).thenReturn(massNbInterpolationPoints);
572         Mockito.when(attitudeInterpolator.getNbInterpolationPoints()).thenReturn(AttitudeNbInterpolationPoints);
573         Mockito.when(additionalStateInterpolator.getNbInterpolationPoints()).thenReturn(AdditionalStateNbInterpolationPoints);
574 
575         Mockito.when(orbitInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(orbitInterpolator));
576         Mockito.when(absPVAInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(absPVAInterpolator));
577         Mockito.when(massInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(massInterpolator));
578         Mockito.when(attitudeInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(attitudeInterpolator));
579         Mockito.when(additionalStateInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(additionalStateInterpolator));
580 
581         final FieldSpacecraftStateInterpolator<Binary64> stateInterpolator =
582                 new FieldSpacecraftStateInterpolator<>(2, 1.0e-3, frame, orbitInterpolator, absPVAInterpolator, massInterpolator,
583                                                        attitudeInterpolator, additionalStateInterpolator);
584 
585         // WHEN
586         final int returnedNbInterpolationPoints = stateInterpolator.getNbInterpolationPoints();
587 
588         // THEN
589         Assertions.assertEquals(AdditionalStateNbInterpolationPoints, returnedNbInterpolationPoints);
590     }
591 
592     @Test
593     void testGetNbInterpolationsWithMultipleSubInterpolatorsDeprecated() {
594         // GIVEN
595         // Create mock interpolators
596         final Frame frame = Mockito.mock(Frame.class);
597 
598         @SuppressWarnings("unchecked")
599         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolator =
600                 Mockito.mock(FieldOrbitHermiteInterpolator.class);
601         @SuppressWarnings("unchecked")
602         final FieldTimeInterpolator<FieldAbsolutePVCoordinates<Binary64>, Binary64> absPVAInterpolator =
603                 Mockito.mock(FieldAbsolutePVCoordinatesHermiteInterpolator.class);
604         @SuppressWarnings("unchecked")
605         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> massInterpolator =
606                 Mockito.mock(TimeStampedFieldHermiteInterpolator.class);
607         @SuppressWarnings("unchecked")
608         final FieldTimeInterpolator<FieldAttitude<Binary64>, Binary64> attitudeInterpolator =
609                 Mockito.mock(FieldAttitudeInterpolator.class);
610         @SuppressWarnings("unchecked")
611         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> additionalStateInterpolator =
612                 Mockito.mock(TimeStampedFieldHermiteInterpolator.class);
613 
614         // Implement mocks behaviours
615         final int orbitNbInterpolationPoints           = 2;
616         final int absPVANbInterpolationPoints          = 3;
617         final int massNbInterpolationPoints            = 4;
618         final int AttitudeNbInterpolationPoints        = 5;
619         final int AdditionalStateNbInterpolationPoints = 6;
620 
621         Mockito.when(orbitInterpolator.getNbInterpolationPoints()).thenReturn(orbitNbInterpolationPoints);
622         Mockito.when(absPVAInterpolator.getNbInterpolationPoints()).thenReturn(absPVANbInterpolationPoints);
623         Mockito.when(massInterpolator.getNbInterpolationPoints()).thenReturn(massNbInterpolationPoints);
624         Mockito.when(attitudeInterpolator.getNbInterpolationPoints()).thenReturn(AttitudeNbInterpolationPoints);
625         Mockito.when(additionalStateInterpolator.getNbInterpolationPoints()).thenReturn(AdditionalStateNbInterpolationPoints);
626 
627         Mockito.when(orbitInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(orbitInterpolator));
628         Mockito.when(absPVAInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(absPVAInterpolator));
629         Mockito.when(massInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(massInterpolator));
630         Mockito.when(attitudeInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(attitudeInterpolator));
631         Mockito.when(additionalStateInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(additionalStateInterpolator));
632 
633         final FieldSpacecraftStateInterpolator<Binary64> stateInterpolator =
634                 new FieldSpacecraftStateInterpolator<>(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
635                                                        AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
636                                                        frame, orbitInterpolator, absPVAInterpolator, massInterpolator,
637                                                        attitudeInterpolator, additionalStateInterpolator);
638 
639         // WHEN
640         final int returnedNbInterpolationPoints = stateInterpolator.getNbInterpolationPoints();
641 
642         // THEN
643         Assertions.assertEquals(AdditionalStateNbInterpolationPoints, returnedNbInterpolationPoints);
644     }
645 
646     @Test
647     @DisplayName("test error thrown when using different state definition")
648     void testErrorThrownWhenUsingDifferentStateDefinition() {
649         // Given
650         final Field<Binary64>             field             = Binary64Field.getInstance();
651         final FieldAbsoluteDate<Binary64> interpolationDate = new FieldAbsoluteDate<>(field);
652 
653         @SuppressWarnings("unchecked")
654         final FieldSpacecraftState<Binary64> orbitDefinedFieldState = Mockito.mock(FieldSpacecraftState.class);
655         final SpacecraftState orbitDefinedState = Mockito.mock(SpacecraftState.class);
656 
657         Mockito.when(orbitDefinedState.isOrbitDefined()).thenReturn(true);
658         Mockito.when(orbitDefinedFieldState.toSpacecraftState()).thenReturn(orbitDefinedState);
659 
660         @SuppressWarnings("unchecked")
661         final FieldSpacecraftState<Binary64> absPVDefinedFieldState = Mockito.mock(FieldSpacecraftState.class);
662         final SpacecraftState absPVDefinedState = Mockito.mock(SpacecraftState.class);
663 
664         Mockito.when(absPVDefinedState.isOrbitDefined()).thenReturn(false);
665         Mockito.when(absPVDefinedFieldState.toSpacecraftState()).thenReturn(absPVDefinedState);
666 
667         final List<FieldSpacecraftState<Binary64>> states = new ArrayList<>();
668         states.add(orbitDefinedFieldState);
669         states.add(absPVDefinedFieldState);
670 
671         // Create interpolator
672         final Frame inertialFrameMock = Mockito.mock(Frame.class);
673         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
674 
675         final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> stateInterpolator =
676                 new FieldSpacecraftStateInterpolator<>(inertialFrameMock);
677 
678         // When & Then
679         Exception thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class,
680                                                    () -> stateInterpolator.interpolate(interpolationDate, states));
681 
682         Assertions.assertEquals(
683                 "one state is defined using an orbit while the other is defined using an absolute position-velocity-acceleration",
684                 thrown.getMessage());
685     }
686 
687     @Test
688     @DisplayName("test error thrown when using no interpolator for state")
689     void testErrorThrownWhenGivingNoInterpolatorForState() {
690         // Given
691         final Frame inertialFrameMock = Mockito.mock(Frame.class);
692         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
693 
694         // When & Then
695         Exception thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class,
696                                                    () -> new FieldSpacecraftStateInterpolator<>(2, 1.0e-3, inertialFrameMock,
697                                                                                                 null, null, null, null,
698                                                                                                 null));
699 
700         Assertions.assertEquals("creating a spacecraft state interpolator requires at least one orbit interpolator or an "
701                                         + "absolute position-velocity-acceleration interpolator", thrown.getMessage());
702     }
703 
704     @Test
705     @DisplayName("test error thrown when using no interpolator for state")
706     void testErrorThrownWhenGivingNoInterpolatorForStateDeprecated() {
707         // Given
708         final Frame inertialFrameMock = Mockito.mock(Frame.class);
709         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
710 
711         // When & Then
712         Exception thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class,
713                                                    () -> new FieldSpacecraftStateInterpolator<>(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
714                                                                                                 AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
715                                                                                                 inertialFrameMock,
716                                                                                                 null, null, null, null,
717                                                                                                 null));
718 
719         Assertions.assertEquals("creating a spacecraft state interpolator requires at least one orbit interpolator or an "
720                                         + "absolute position-velocity-acceleration interpolator", thrown.getMessage());
721     }
722 
723     @Test
724     @DisplayName("test error thrown when giving empty sample")
725     void testErrorThrownWhenGivingEmptySample() {
726         // Given
727 
728         @SuppressWarnings("unchecked")
729         final FieldAbsoluteDate<Binary64> interpolationDate = Mockito.mock(FieldAbsoluteDate.class);
730 
731         final Frame inertialFrame = FramesFactory.getEME2000();
732 
733         final List<FieldSpacecraftState<Binary64>> states = new ArrayList<>();
734 
735         // Create interpolator
736         @SuppressWarnings("unchecked")
737         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolatorMock =
738                 Mockito.mock(FieldTimeInterpolator.class);
739 
740         final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> interpolator =
741                 new FieldSpacecraftStateInterpolator<>(2, 1.0e-3, inertialFrame, orbitInterpolatorMock, null, null, null, null);
742 
743         // When & Then
744         OrekitIllegalArgumentException thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class, () ->
745                                                                         interpolator.interpolate(interpolationDate, states));
746 
747         Assertions.assertEquals(OrekitMessages.NOT_ENOUGH_DATA, thrown.getSpecifier());
748         Assertions.assertEquals(0, ((Integer) thrown.getParts()[0]).intValue());
749 
750     }
751 
752     @Test
753     @DisplayName("test error thrown when giving empty sample")
754     void testErrorThrownWhenGivingEmptySampleDeprecated() {
755         // Given
756 
757         @SuppressWarnings("unchecked")
758         final FieldAbsoluteDate<Binary64> interpolationDate = Mockito.mock(FieldAbsoluteDate.class);
759 
760         final Frame inertialFrame = FramesFactory.getEME2000();
761 
762         final List<FieldSpacecraftState<Binary64>> states = new ArrayList<>();
763 
764         // Create interpolator
765         @SuppressWarnings("unchecked")
766         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolatorMock =
767                 Mockito.mock(FieldTimeInterpolator.class);
768 
769         final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> interpolator =
770                 new FieldSpacecraftStateInterpolator<>(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
771                                                        AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
772                                                        inertialFrame, orbitInterpolatorMock,
773                                         null, null, null, null);
774 
775         // When & Then
776         OrekitIllegalArgumentException thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class, () ->
777                                                                         interpolator.interpolate(interpolationDate, states));
778 
779         Assertions.assertEquals(OrekitMessages.NOT_ENOUGH_DATA, thrown.getSpecifier());
780         Assertions.assertEquals(0, ((Integer) thrown.getParts()[0]).intValue());
781 
782     }
783 
784     @Test
785     void testFieldSpacecraftStateInterpolatorCreation() {
786         // Given
787         final Frame inertialFrameMock = Mockito.mock(Frame.class);
788         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
789 
790         @SuppressWarnings("unchecked")
791         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolatorMock =
792                 Mockito.mock(FieldTimeInterpolator.class);
793 
794         @SuppressWarnings("unchecked")
795         final FieldTimeInterpolator<FieldAbsolutePVCoordinates<Binary64>, Binary64> absPVInterpolatorMock =
796                 Mockito.mock(FieldTimeInterpolator.class);
797 
798         @SuppressWarnings("unchecked")
799         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> massInterpolatorMock =
800                 Mockito.mock(FieldTimeInterpolator.class);
801 
802         @SuppressWarnings("unchecked")
803         final FieldTimeInterpolator<FieldAttitude<Binary64>, Binary64> attitudeInterpolatorMock =
804                 Mockito.mock(FieldTimeInterpolator.class);
805 
806         @SuppressWarnings("unchecked")
807         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> additionalInterpolatorMock =
808                 Mockito.mock(FieldTimeInterpolator.class);
809 
810         // When
811         final FieldSpacecraftStateInterpolator<Binary64> interpolator =
812                 new FieldSpacecraftStateInterpolator<>(2, 1.0e-3, inertialFrameMock, orbitInterpolatorMock, absPVInterpolatorMock,
813                                                        massInterpolatorMock, attitudeInterpolatorMock,
814                                                        additionalInterpolatorMock);
815 
816         // Then
817         Assertions.assertEquals(inertialFrameMock, interpolator.getOutputFrame());
818         Assertions.assertEquals(orbitInterpolatorMock, interpolator.getOrbitInterpolator().get());
819         Assertions.assertEquals(absPVInterpolatorMock, interpolator.getAbsPVAInterpolator().get());
820         Assertions.assertEquals(massInterpolatorMock, interpolator.getMassInterpolator().get());
821         Assertions.assertEquals(attitudeInterpolatorMock, interpolator.getAttitudeInterpolator().get());
822         Assertions.assertEquals(additionalInterpolatorMock, interpolator.getAdditionalStateInterpolator().get());
823 
824     }
825 
826     @Test
827     void testFieldSpacecraftStateInterpolatorCreationDeprecated() {
828         // Given
829         final Frame inertialFrameMock = Mockito.mock(Frame.class);
830         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
831 
832         @SuppressWarnings("unchecked")
833         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolatorMock =
834                 Mockito.mock(FieldTimeInterpolator.class);
835 
836         @SuppressWarnings("unchecked")
837         final FieldTimeInterpolator<FieldAbsolutePVCoordinates<Binary64>, Binary64> absPVInterpolatorMock =
838                 Mockito.mock(FieldTimeInterpolator.class);
839 
840         @SuppressWarnings("unchecked")
841         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> massInterpolatorMock =
842                 Mockito.mock(FieldTimeInterpolator.class);
843 
844         @SuppressWarnings("unchecked")
845         final FieldTimeInterpolator<FieldAttitude<Binary64>, Binary64> attitudeInterpolatorMock =
846                 Mockito.mock(FieldTimeInterpolator.class);
847 
848         @SuppressWarnings("unchecked")
849         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> additionalInterpolatorMock =
850                 Mockito.mock(FieldTimeInterpolator.class);
851 
852         // When
853         final FieldSpacecraftStateInterpolator<Binary64> interpolator =
854                 new FieldSpacecraftStateInterpolator<>(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
855                                                        AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
856                                                        inertialFrameMock, orbitInterpolatorMock, absPVInterpolatorMock,
857                                                        massInterpolatorMock, attitudeInterpolatorMock,
858                                                        additionalInterpolatorMock);
859 
860         // Then
861         Assertions.assertEquals(inertialFrameMock, interpolator.getOutputFrame());
862         Assertions.assertEquals(orbitInterpolatorMock, interpolator.getOrbitInterpolator().get());
863         Assertions.assertEquals(absPVInterpolatorMock, interpolator.getAbsPVAInterpolator().get());
864         Assertions.assertEquals(massInterpolatorMock, interpolator.getMassInterpolator().get());
865         Assertions.assertEquals(attitudeInterpolatorMock, interpolator.getAttitudeInterpolator().get());
866         Assertions.assertEquals(additionalInterpolatorMock, interpolator.getAdditionalStateInterpolator().get());
867 
868     }
869 
870     @Test
871     @DisplayName("Test error thrown when sub interpolator is not present")
872     void testErrorThrownWhenSubInterpolatorIsNotPresent() {
873         // GIVEN
874         final FakeFieldStateInterpolator fakeStateInterpolator = new FakeFieldStateInterpolator();
875 
876         // WHEN & THEN
877         Assertions.assertThrows(OrekitInternalError.class, fakeStateInterpolator::getNbInterpolationPoints);
878     }
879 
880     @Test
881     @DisplayName("Test does not throw error when checking interpolator compatibility")
882     void testDoesNotThrowWhenCheckingInterpolatorCompatibility() {
883         // GIVEN
884         final FakeFieldStateInterpolator fakeStateInterpolator = new FakeFieldStateInterpolator();
885 
886         // WHEN & THEN
887         Assertions.assertThrows(OrekitInternalError.class, fakeStateInterpolator::getNbInterpolationPoints);
888     }
889 
890     private static class FakeFieldStateInterpolator extends AbstractFieldTimeInterpolator<FieldSpacecraftState<Binary64>,Binary64> {
891 
892         public FakeFieldStateInterpolator() {
893             super(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
894                   AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC);
895         }
896 
897         @Override
898         public List<FieldTimeInterpolator<? extends FieldTimeStamped<Binary64>, Binary64>> getSubInterpolators() {
899             return Collections.emptyList();
900         }
901 
902         @Override
903         protected FieldSpacecraftState<Binary64> interpolate(AbstractFieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64>.InterpolationData interpolationData) {
904             return null;
905         }
906     }
907 }