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.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<>(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         Assertions.assertEquals(2, stateInterpolator.getInternalNbInterpolationPoints());
591     }
592 
593     @Test
594     void testGetNbInterpolationsWithMultipleSubInterpolatorsDeprecated() {
595         // GIVEN
596         // Create mock interpolators
597         final Frame frame = Mockito.mock(Frame.class);
598 
599         @SuppressWarnings("unchecked")
600         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolator =
601                 Mockito.mock(FieldOrbitHermiteInterpolator.class);
602         @SuppressWarnings("unchecked")
603         final FieldTimeInterpolator<FieldAbsolutePVCoordinates<Binary64>, Binary64> absPVAInterpolator =
604                 Mockito.mock(FieldAbsolutePVCoordinatesHermiteInterpolator.class);
605         @SuppressWarnings("unchecked")
606         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> massInterpolator =
607                 Mockito.mock(TimeStampedFieldHermiteInterpolator.class);
608         @SuppressWarnings("unchecked")
609         final FieldTimeInterpolator<FieldAttitude<Binary64>, Binary64> attitudeInterpolator =
610                 Mockito.mock(FieldAttitudeInterpolator.class);
611         @SuppressWarnings("unchecked")
612         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> additionalStateInterpolator =
613                 Mockito.mock(TimeStampedFieldHermiteInterpolator.class);
614 
615         // Implement mocks behaviours
616         final int orbitNbInterpolationPoints           = 2;
617         final int absPVANbInterpolationPoints          = 3;
618         final int massNbInterpolationPoints            = 4;
619         final int AttitudeNbInterpolationPoints        = 5;
620         final int AdditionalStateNbInterpolationPoints = 6;
621 
622         Mockito.when(orbitInterpolator.getNbInterpolationPoints()).thenReturn(orbitNbInterpolationPoints);
623         Mockito.when(absPVAInterpolator.getNbInterpolationPoints()).thenReturn(absPVANbInterpolationPoints);
624         Mockito.when(massInterpolator.getNbInterpolationPoints()).thenReturn(massNbInterpolationPoints);
625         Mockito.when(attitudeInterpolator.getNbInterpolationPoints()).thenReturn(AttitudeNbInterpolationPoints);
626         Mockito.when(additionalStateInterpolator.getNbInterpolationPoints()).thenReturn(AdditionalStateNbInterpolationPoints);
627 
628         Mockito.when(orbitInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(orbitInterpolator));
629         Mockito.when(absPVAInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(absPVAInterpolator));
630         Mockito.when(massInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(massInterpolator));
631         Mockito.when(attitudeInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(attitudeInterpolator));
632         Mockito.when(additionalStateInterpolator.getSubInterpolators()).thenReturn(Collections.singletonList(additionalStateInterpolator));
633 
634         final FieldSpacecraftStateInterpolator<Binary64> stateInterpolator =
635                 new FieldSpacecraftStateInterpolator<>(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
636                                                        AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
637                                                        frame, orbitInterpolator, absPVAInterpolator, massInterpolator,
638                                                        attitudeInterpolator, additionalStateInterpolator);
639 
640         // WHEN
641         final int returnedNbInterpolationPoints = stateInterpolator.getNbInterpolationPoints();
642 
643         // THEN
644         Assertions.assertEquals(AdditionalStateNbInterpolationPoints, returnedNbInterpolationPoints);
645     }
646 
647     @Test
648     @DisplayName("test error thrown when using different state definition")
649     void testErrorThrownWhenUsingDifferentStateDefinition() {
650         // Given
651         final Field<Binary64>             field             = Binary64Field.getInstance();
652         final FieldAbsoluteDate<Binary64> interpolationDate = new FieldAbsoluteDate<>(field);
653 
654         @SuppressWarnings("unchecked")
655         final FieldSpacecraftState<Binary64> orbitDefinedFieldState = Mockito.mock(FieldSpacecraftState.class);
656         final SpacecraftState orbitDefinedState = Mockito.mock(SpacecraftState.class);
657 
658         Mockito.when(orbitDefinedState.isOrbitDefined()).thenReturn(true);
659         Mockito.when(orbitDefinedFieldState.toSpacecraftState()).thenReturn(orbitDefinedState);
660 
661         @SuppressWarnings("unchecked")
662         final FieldSpacecraftState<Binary64> absPVDefinedFieldState = Mockito.mock(FieldSpacecraftState.class);
663         final SpacecraftState absPVDefinedState = Mockito.mock(SpacecraftState.class);
664 
665         Mockito.when(absPVDefinedState.isOrbitDefined()).thenReturn(false);
666         Mockito.when(absPVDefinedFieldState.toSpacecraftState()).thenReturn(absPVDefinedState);
667 
668         final List<FieldSpacecraftState<Binary64>> states = new ArrayList<>();
669         states.add(orbitDefinedFieldState);
670         states.add(absPVDefinedFieldState);
671 
672         // Create interpolator
673         final Frame inertialFrameMock = Mockito.mock(Frame.class);
674         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
675 
676         final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> stateInterpolator =
677                 new FieldSpacecraftStateInterpolator<>(inertialFrameMock);
678 
679         // When & Then
680         Exception thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class,
681                                                    () -> stateInterpolator.interpolate(interpolationDate, states));
682 
683         Assertions.assertEquals(
684                 "one state is defined using an orbit while the other is defined using an absolute position-velocity-acceleration",
685                 thrown.getMessage());
686     }
687 
688     @Test
689     @DisplayName("test error thrown when using no interpolator for state")
690     void testErrorThrownWhenGivingNoInterpolatorForState() {
691         // Given
692         final Frame inertialFrameMock = Mockito.mock(Frame.class);
693         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
694 
695         // When & Then
696         Exception thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class,
697                                                    () -> new FieldSpacecraftStateInterpolator<>(2, 1.0e-3, inertialFrameMock,
698                                                                                                 null, null, null, null,
699                                                                                                 null));
700 
701         Assertions.assertEquals("creating a spacecraft state interpolator requires at least one orbit interpolator or an "
702                                         + "absolute position-velocity-acceleration interpolator", thrown.getMessage());
703     }
704 
705     @Test
706     @DisplayName("test error thrown when using no interpolator for state")
707     void testErrorThrownWhenGivingNoInterpolatorForStateDeprecated() {
708         // Given
709         final Frame inertialFrameMock = Mockito.mock(Frame.class);
710         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
711 
712         // When & Then
713         Exception thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class,
714                                                    () -> new FieldSpacecraftStateInterpolator<>(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
715                                                                                                 AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
716                                                                                                 inertialFrameMock,
717                                                                                                 null, null, null, null,
718                                                                                                 null));
719 
720         Assertions.assertEquals("creating a spacecraft state interpolator requires at least one orbit interpolator or an "
721                                         + "absolute position-velocity-acceleration interpolator", thrown.getMessage());
722     }
723 
724     @Test
725     @DisplayName("test error thrown when giving empty sample")
726     void testErrorThrownWhenGivingEmptySample() {
727         // Given
728 
729         @SuppressWarnings("unchecked")
730         final FieldAbsoluteDate<Binary64> interpolationDate = Mockito.mock(FieldAbsoluteDate.class);
731 
732         final Frame inertialFrame = FramesFactory.getEME2000();
733 
734         final List<FieldSpacecraftState<Binary64>> states = new ArrayList<>();
735 
736         // Create interpolator
737         @SuppressWarnings("unchecked")
738         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolatorMock =
739                 Mockito.mock(FieldTimeInterpolator.class);
740 
741         final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> interpolator =
742                 new FieldSpacecraftStateInterpolator<>(2, 1.0e-3, inertialFrame, orbitInterpolatorMock, null, null, null, null);
743 
744         // When & Then
745         OrekitIllegalArgumentException thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class, () ->
746                                                                         interpolator.interpolate(interpolationDate, states));
747 
748         Assertions.assertEquals(OrekitMessages.NOT_ENOUGH_DATA, thrown.getSpecifier());
749         Assertions.assertEquals(0, ((Integer) thrown.getParts()[0]).intValue());
750 
751     }
752 
753     @Test
754     @DisplayName("test error thrown when giving empty sample")
755     void testErrorThrownWhenGivingEmptySampleDeprecated() {
756         // Given
757 
758         @SuppressWarnings("unchecked")
759         final FieldAbsoluteDate<Binary64> interpolationDate = Mockito.mock(FieldAbsoluteDate.class);
760 
761         final Frame inertialFrame = FramesFactory.getEME2000();
762 
763         final List<FieldSpacecraftState<Binary64>> states = new ArrayList<>();
764 
765         // Create interpolator
766         @SuppressWarnings("unchecked")
767         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolatorMock =
768                 Mockito.mock(FieldTimeInterpolator.class);
769 
770         final FieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64> interpolator =
771                 new FieldSpacecraftStateInterpolator<>(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
772                                                        AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
773                                                        inertialFrame, orbitInterpolatorMock,
774                                         null, null, null, null);
775 
776         // When & Then
777         OrekitIllegalArgumentException thrown = Assertions.assertThrows(OrekitIllegalArgumentException.class, () ->
778                                                                         interpolator.interpolate(interpolationDate, states));
779 
780         Assertions.assertEquals(OrekitMessages.NOT_ENOUGH_DATA, thrown.getSpecifier());
781         Assertions.assertEquals(0, ((Integer) thrown.getParts()[0]).intValue());
782 
783     }
784 
785     @Test
786     void testFieldSpacecraftStateInterpolatorCreation() {
787         // Given
788         final Frame inertialFrameMock = Mockito.mock(Frame.class);
789         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
790 
791         @SuppressWarnings("unchecked")
792         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolatorMock =
793                 Mockito.mock(FieldTimeInterpolator.class);
794 
795         @SuppressWarnings("unchecked")
796         final FieldTimeInterpolator<FieldAbsolutePVCoordinates<Binary64>, Binary64> absPVInterpolatorMock =
797                 Mockito.mock(FieldTimeInterpolator.class);
798 
799         @SuppressWarnings("unchecked")
800         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> massInterpolatorMock =
801                 Mockito.mock(FieldTimeInterpolator.class);
802 
803         @SuppressWarnings("unchecked")
804         final FieldTimeInterpolator<FieldAttitude<Binary64>, Binary64> attitudeInterpolatorMock =
805                 Mockito.mock(FieldTimeInterpolator.class);
806 
807         @SuppressWarnings("unchecked")
808         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> additionalInterpolatorMock =
809                 Mockito.mock(FieldTimeInterpolator.class);
810 
811         // When
812         final FieldSpacecraftStateInterpolator<Binary64> interpolator =
813                 new FieldSpacecraftStateInterpolator<>(2, 1.0e-3, inertialFrameMock, orbitInterpolatorMock, absPVInterpolatorMock,
814                                                        massInterpolatorMock, attitudeInterpolatorMock,
815                                                        additionalInterpolatorMock);
816 
817         // Then
818         Assertions.assertEquals(inertialFrameMock, interpolator.getOutputFrame());
819         Assertions.assertEquals(orbitInterpolatorMock, interpolator.getOrbitInterpolator().get());
820         Assertions.assertEquals(absPVInterpolatorMock, interpolator.getAbsPVAInterpolator().get());
821         Assertions.assertEquals(massInterpolatorMock, interpolator.getMassInterpolator().get());
822         Assertions.assertEquals(attitudeInterpolatorMock, interpolator.getAttitudeInterpolator().get());
823         Assertions.assertEquals(additionalInterpolatorMock, interpolator.getAdditionalStateInterpolator().get());
824 
825     }
826 
827     @Test
828     void testFieldSpacecraftStateInterpolatorCreationDeprecated() {
829         // Given
830         final Frame inertialFrameMock = Mockito.mock(Frame.class);
831         Mockito.when(inertialFrameMock.isPseudoInertial()).thenReturn(true);
832 
833         @SuppressWarnings("unchecked")
834         final FieldTimeInterpolator<FieldOrbit<Binary64>, Binary64> orbitInterpolatorMock =
835                 Mockito.mock(FieldTimeInterpolator.class);
836 
837         @SuppressWarnings("unchecked")
838         final FieldTimeInterpolator<FieldAbsolutePVCoordinates<Binary64>, Binary64> absPVInterpolatorMock =
839                 Mockito.mock(FieldTimeInterpolator.class);
840 
841         @SuppressWarnings("unchecked")
842         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> massInterpolatorMock =
843                 Mockito.mock(FieldTimeInterpolator.class);
844 
845         @SuppressWarnings("unchecked")
846         final FieldTimeInterpolator<FieldAttitude<Binary64>, Binary64> attitudeInterpolatorMock =
847                 Mockito.mock(FieldTimeInterpolator.class);
848 
849         @SuppressWarnings("unchecked")
850         final FieldTimeInterpolator<TimeStampedField<Binary64>, Binary64> additionalInterpolatorMock =
851                 Mockito.mock(FieldTimeInterpolator.class);
852 
853         // When
854         final FieldSpacecraftStateInterpolator<Binary64> interpolator =
855                 new FieldSpacecraftStateInterpolator<>(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
856                                                        AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC,
857                                                        inertialFrameMock, orbitInterpolatorMock, absPVInterpolatorMock,
858                                                        massInterpolatorMock, attitudeInterpolatorMock,
859                                                        additionalInterpolatorMock);
860 
861         // Then
862         Assertions.assertEquals(inertialFrameMock, interpolator.getOutputFrame());
863         Assertions.assertEquals(orbitInterpolatorMock, interpolator.getOrbitInterpolator().get());
864         Assertions.assertEquals(absPVInterpolatorMock, interpolator.getAbsPVAInterpolator().get());
865         Assertions.assertEquals(massInterpolatorMock, interpolator.getMassInterpolator().get());
866         Assertions.assertEquals(attitudeInterpolatorMock, interpolator.getAttitudeInterpolator().get());
867         Assertions.assertEquals(additionalInterpolatorMock, interpolator.getAdditionalStateInterpolator().get());
868 
869     }
870 
871     @Test
872     @DisplayName("Test error thrown when sub interpolator is not present")
873     void testErrorThrownWhenSubInterpolatorIsNotPresent() {
874         // GIVEN
875         final FakeFieldStateInterpolator fakeStateInterpolator = new FakeFieldStateInterpolator();
876 
877         // WHEN & THEN
878         Assertions.assertThrows(OrekitInternalError.class, fakeStateInterpolator::getNbInterpolationPoints);
879     }
880 
881     @Test
882     @DisplayName("Test does not throw error when checking interpolator compatibility")
883     void testDoesNotThrowWhenCheckingInterpolatorCompatibility() {
884         // GIVEN
885         final FakeFieldStateInterpolator fakeStateInterpolator = new FakeFieldStateInterpolator();
886 
887         // WHEN & THEN
888         Assertions.assertThrows(OrekitInternalError.class, fakeStateInterpolator::getNbInterpolationPoints);
889     }
890 
891     private static class FakeFieldStateInterpolator extends AbstractFieldTimeInterpolator<FieldSpacecraftState<Binary64>,Binary64> {
892 
893         public FakeFieldStateInterpolator() {
894             super(AbstractTimeInterpolator.DEFAULT_INTERPOLATION_POINTS,
895                   AbstractTimeInterpolator.DEFAULT_EXTRAPOLATION_THRESHOLD_SEC);
896         }
897 
898         @Override
899         public List<FieldTimeInterpolator<? extends FieldTimeStamped<Binary64>, Binary64>> getSubInterpolators() {
900             return Collections.emptyList();
901         }
902 
903         @Override
904         protected FieldSpacecraftState<Binary64> interpolate(AbstractFieldTimeInterpolator<FieldSpacecraftState<Binary64>, Binary64>.InterpolationData interpolationData) {
905             return null;
906         }
907     }
908 }