1   /* Copyright 2002-2022 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.forces.drag;
18  
19  import org.hipparchus.Field;
20  import org.hipparchus.analysis.differentiation.DSFactory;
21  import org.hipparchus.analysis.differentiation.DerivativeStructure;
22  import org.hipparchus.analysis.differentiation.Gradient;
23  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
24  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaFieldIntegrator;
27  import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
28  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
29  import org.hipparchus.util.FastMath;
30  import org.junit.jupiter.api.Assertions;
31  import org.junit.jupiter.api.BeforeEach;
32  import org.junit.jupiter.api.Test;
33  import org.orekit.Utils;
34  import org.orekit.attitudes.AttitudeProvider;
35  import org.orekit.attitudes.LofOffset;
36  import org.orekit.bodies.CelestialBody;
37  import org.orekit.bodies.CelestialBodyFactory;
38  import org.orekit.bodies.OneAxisEllipsoid;
39  import org.orekit.errors.OrekitException;
40  import org.orekit.errors.OrekitMessages;
41  import org.orekit.forces.AbstractLegacyForceModelTest;
42  import org.orekit.forces.BoxAndSolarArraySpacecraft;
43  import org.orekit.forces.ForceModel;
44  import org.orekit.frames.Frame;
45  import org.orekit.frames.FramesFactory;
46  import org.orekit.frames.LOFType;
47  import org.orekit.frames.Transform;
48  import org.orekit.models.earth.atmosphere.Atmosphere;
49  import org.orekit.models.earth.atmosphere.HarrisPriester;
50  import org.orekit.orbits.CartesianOrbit;
51  import org.orekit.orbits.FieldKeplerianOrbit;
52  import org.orekit.orbits.KeplerianOrbit;
53  import org.orekit.orbits.Orbit;
54  import org.orekit.orbits.OrbitType;
55  import org.orekit.orbits.PositionAngle;
56  import org.orekit.propagation.FieldSpacecraftState;
57  import org.orekit.propagation.SpacecraftState;
58  import org.orekit.propagation.numerical.FieldNumericalPropagator;
59  import org.orekit.propagation.numerical.NumericalPropagator;
60  import org.orekit.time.AbsoluteDate;
61  import org.orekit.time.DateComponents;
62  import org.orekit.time.FieldAbsoluteDate;
63  import org.orekit.time.TimeComponents;
64  import org.orekit.time.TimeScale;
65  import org.orekit.time.TimeScalesFactory;
66  import org.orekit.utils.Constants;
67  import org.orekit.utils.FieldPVCoordinates;
68  import org.orekit.utils.IERSConventions;
69  import org.orekit.utils.PVCoordinates;
70  import org.orekit.utils.ParameterDriver;
71  import org.orekit.utils.TimeSpanMap;
72  
73  import java.util.List;
74  
75  public class TimeSpanDragForceTest extends AbstractLegacyForceModelTest {
76  
77      /** UTC time scale. */
78      private TimeScale utc;
79  
80      /** Compute acceleration derivatives around input position at input date.
81       *  Using finite differences in position.
82       */
83      @Override
84      protected FieldVector3D<DerivativeStructure> accelerationDerivatives(final ForceModel forceModel,
85                                                                           final AbsoluteDate date, final  Frame frame,
86                                                                           final FieldVector3D<DerivativeStructure> position,
87                                                                           final FieldVector3D<DerivativeStructure> velocity,
88                                                                           final FieldRotation<DerivativeStructure> rotation,
89                                                                           final DerivativeStructure mass)
90          {
91          try {
92  
93              java.lang.reflect.Field atmosphereField = TimeSpanDragForce.class.getDeclaredField("atmosphere");
94              atmosphereField.setAccessible(true);
95              Atmosphere atmosphere = (Atmosphere) atmosphereField.get(forceModel);
96              java.lang.reflect.Field spacecraftField = DragForce.class.getDeclaredField("spacecraft");
97              spacecraftField.setAccessible(true);
98              // Get the DragSensitive model at date
99              DragSensitive spacecraft = ((TimeSpanDragForce) (forceModel)).getDragSensitive(date);
100 
101             // retrieve derivation properties
102             final DSFactory factory = mass.getFactory();
103 
104             // get atmosphere properties in atmosphere own frame
105             final Frame      atmFrame  = atmosphere.getFrame();
106             final Transform  toBody    = frame.getTransformTo(atmFrame, date);
107             final FieldVector3D<DerivativeStructure> posBodyDS = toBody.transformPosition(position);
108             final Vector3D   posBody   = posBodyDS.toVector3D();
109             final Vector3D   vAtmBody  = atmosphere.getVelocity(date, posBody, atmFrame);
110 
111             // estimate density model by finite differences and composition
112             // the following implementation works only for first order derivatives.
113             // this could be improved by adding a new method
114             // getDensity(AbsoluteDate, DerivativeStructure, Frame)
115             // to the Atmosphere interface
116             if (factory.getCompiler().getOrder() > 1) {
117                 throw new OrekitException(OrekitMessages.OUT_OF_RANGE_DERIVATION_ORDER, factory.getCompiler().getOrder());
118             }
119             final double delta  = 1.0;
120             final double x      = posBody.getX();
121             final double y      = posBody.getY();
122             final double z      = posBody.getZ();
123             final double rho0   = atmosphere.getDensity(date, posBody, atmFrame);
124             final double dRhodX = (atmosphere.getDensity(date, new Vector3D(x + delta, y,         z),         atmFrame) - rho0) / delta;
125             final double dRhodY = (atmosphere.getDensity(date, new Vector3D(x,         y + delta, z),         atmFrame) - rho0) / delta;
126             final double dRhodZ = (atmosphere.getDensity(date, new Vector3D(x,         y,         z + delta), atmFrame) - rho0) / delta;
127             final double[] dXdQ = posBodyDS.getX().getAllDerivatives();
128             final double[] dYdQ = posBodyDS.getY().getAllDerivatives();
129             final double[] dZdQ = posBodyDS.getZ().getAllDerivatives();
130             final double[] rhoAll = new double[dXdQ.length];
131             rhoAll[0] = rho0;
132             for (int i = 1; i < rhoAll.length; ++i) {
133                 rhoAll[i] = dRhodX * dXdQ[i] + dRhodY * dYdQ[i] + dRhodZ * dZdQ[i];
134             }
135             final DerivativeStructure rho = factory.build(rhoAll);
136 
137             // we consider that at first order the atmosphere velocity in atmosphere frame
138             // does not depend on local position; however atmosphere velocity in inertial
139             // frame DOES depend on position since the transform between the frames depends
140             // on it, due to central body rotation rate and velocity composition.
141             // So we use the transform to get the correct partial derivatives on vAtm
142             final FieldVector3D<DerivativeStructure> vAtmBodyDS =
143                             new FieldVector3D<>(factory.constant(vAtmBody.getX()),
144                                             factory.constant(vAtmBody.getY()),
145                                             factory.constant(vAtmBody.getZ()));
146             final FieldPVCoordinates<DerivativeStructure> pvAtmBody = new FieldPVCoordinates<>(posBodyDS, vAtmBodyDS);
147             final FieldPVCoordinates<DerivativeStructure> pvAtm     = toBody.getInverse().transformPVCoordinates(pvAtmBody);
148 
149             // now we can compute relative velocity, it takes into account partial derivatives with respect to position
150             final FieldVector3D<DerivativeStructure> relativeVelocity = pvAtm.getVelocity().subtract(velocity);
151 
152 
153             // Extract drag parameters of the proper model
154             DerivativeStructure[] allParameters = forceModel.getParameters(factory.getDerivativeField());
155             DerivativeStructure[] parameters = ((TimeSpanDragForce) (forceModel)).extractParameters(allParameters,
156                                                                                                     new FieldAbsoluteDate<>(factory.getDerivativeField(), date));
157 
158             // compute acceleration with all its partial derivatives
159             return spacecraft.dragAcceleration(new FieldAbsoluteDate<>(factory.getDerivativeField(), date),
160                                                frame, position, rotation, mass, rho, relativeVelocity,
161                                                parameters);
162         } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
163             return null;
164         }
165     }
166 
167     /** Compute acceleration derivatives around input position at input date.
168      *  Using finite differences in position.
169      */
170     @Override
171     protected FieldVector3D<Gradient> accelerationDerivativesGradient(final ForceModel forceModel,
172                                                                       final AbsoluteDate date, final  Frame frame,
173                                                                       final FieldVector3D<Gradient> position,
174                                                                       final FieldVector3D<Gradient> velocity,
175                                                                       final FieldRotation<Gradient> rotation,
176                                                                       final Gradient mass)
177         {
178         try {
179 
180             java.lang.reflect.Field atmosphereField = TimeSpanDragForce.class.getDeclaredField("atmosphere");
181             atmosphereField.setAccessible(true);
182             Atmosphere atmosphere = (Atmosphere) atmosphereField.get(forceModel);
183             java.lang.reflect.Field spacecraftField = DragForce.class.getDeclaredField("spacecraft");
184             spacecraftField.setAccessible(true);
185             // Get the DragSensitive model at date
186             DragSensitive spacecraft = ((TimeSpanDragForce) (forceModel)).getDragSensitive(date);
187 
188             final int freeParameters = mass.getFreeParameters();
189 
190             // get atmosphere properties in atmosphere own frame
191             final Frame      atmFrame  = atmosphere.getFrame();
192             final Transform  toBody    = frame.getTransformTo(atmFrame, date);
193             final FieldVector3D<Gradient> posBodyG = toBody.transformPosition(position);
194             final Vector3D   posBody   = posBodyG.toVector3D();
195             final Vector3D   vAtmBody  = atmosphere.getVelocity(date, posBody, atmFrame);
196 
197             // estimate density model by finite differences and composition
198             // the following implementation works only for first order derivatives.
199             // this could be improved by adding a new method
200             // getDensity(AbsoluteDate, FieldVector3D<Gradient>, Frame)
201             // to the Atmosphere interface
202             final double delta  = 1.0;
203             final double x      = posBody.getX();
204             final double y      = posBody.getY();
205             final double z      = posBody.getZ();
206             final double rho0   = atmosphere.getDensity(date, posBody, atmFrame);
207             final double dRhodX = (atmosphere.getDensity(date, new Vector3D(x + delta, y,         z),         atmFrame) - rho0) / delta;
208             final double dRhodY = (atmosphere.getDensity(date, new Vector3D(x,         y + delta, z),         atmFrame) - rho0) / delta;
209             final double dRhodZ = (atmosphere.getDensity(date, new Vector3D(x,         y,         z + delta), atmFrame) - rho0) / delta;
210             final double[] dXdQ = posBodyG.getX().getGradient();
211             final double[] dYdQ = posBodyG.getY().getGradient();
212             final double[] dZdQ = posBodyG.getZ().getGradient();
213             final double[] rhoAll = new double[dXdQ.length];
214             for (int i = 0; i < rhoAll.length; ++i) {
215                 rhoAll[i] = dRhodX * dXdQ[i] + dRhodY * dYdQ[i] + dRhodZ * dZdQ[i];
216             }
217             final Gradient rho = new Gradient(rho0, rhoAll);
218 
219             // we consider that at first order the atmosphere velocity in atmosphere frame
220             // does not depend on local position; however atmosphere velocity in inertial
221             // frame DOES depend on position since the transform between the frames depends
222             // on it, due to central body rotation rate and velocity composition.
223             // So we use the transform to get the correct partial derivatives on vAtm
224             final FieldVector3D<Gradient> vAtmBodyG =
225                             new FieldVector3D<>(Gradient.constant(freeParameters, vAtmBody.getX()),
226                                             Gradient.constant(freeParameters, vAtmBody.getY()),
227                                             Gradient.constant(freeParameters, vAtmBody.getZ()));
228             final FieldPVCoordinates<Gradient> pvAtmBody = new FieldPVCoordinates<>(posBodyG, vAtmBodyG);
229             final FieldPVCoordinates<Gradient> pvAtm     = toBody.getInverse().transformPVCoordinates(pvAtmBody);
230 
231             // now we can compute relative velocity, it takes into account partial derivatives with respect to position
232             final FieldVector3D<Gradient> relativeVelocity = pvAtm.getVelocity().subtract(velocity);
233 
234 
235             // Extract drag parameters of the proper model
236             Gradient[] allParameters = forceModel.getParameters(mass.getField());
237             Gradient[] parameters = ((TimeSpanDragForce) (forceModel)).extractParameters(allParameters,
238                                                                                          new FieldAbsoluteDate<>(mass.getField(), date));
239 
240             // compute acceleration with all its partial derivatives
241             return spacecraft.dragAcceleration(new FieldAbsoluteDate<>(mass.getField(), date),
242                                                frame, position, rotation, mass, rho, relativeVelocity,
243                                                parameters);
244         } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
245             return null;
246         }
247     }
248 
249     /** Test that the getParameterDrivers method is working as expected
250      * on an IsotropicDrag-based (ie. spherical) TimeSpanDragForce model.
251      */
252     @Test
253     public void testGetParameterDriversSphere() {
254 
255         // Atmosphere
256         final Atmosphere atmosphere = new HarrisPriester(CelestialBodyFactory.getSun(),
257                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
258                                                                               Constants.WGS84_EARTH_FLATTENING,
259                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
260 
261         // A date
262         AbsoluteDate date = new AbsoluteDate("2000-01-01T00:00:00.000", TimeScalesFactory.getUTC());
263 
264         // One IsotropicDrag added, only one driver should be in the drivers' array
265         // its name should be the default name: IsotropicDrag.DRAG_COEFFICIENT
266         // -----------------------
267         double dragArea = 2.;
268         double dragCd0 = 0.;
269         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, new IsotropicDrag(dragArea, dragCd0));
270         Assertions.assertFalse(forceModel.dependsOnPositionOnly());
271         List<ParameterDriver> drivers = forceModel.getParametersDrivers();
272         Assertions.assertEquals(1,  drivers.size());
273         Assertions.assertEquals(dragCd0,  drivers.get(0).getValue(), 0.);
274         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT,  drivers.get(0).getName());
275 
276         // Extract drag model at an arbitrary epoch and check it is the one added
277         IsotropicDrag isoDrag = (IsotropicDrag) forceModel.getDragSensitive(date);
278         drivers = isoDrag.getDragParametersDrivers();
279         Assertions.assertEquals(1, drivers.size());
280         Assertions.assertEquals(dragCd0,  drivers.get(0).getValue(), 0.);
281         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT,  drivers.get(0).getName());
282 
283         // 3 IsotropicDrag models added, with one default
284         // ----------------------------------------------
285         double dragCd1 = 1.;
286         double dragCd2 = 2.;
287         double dt = 120.;
288         // Build the force model
289         isoDrag = new IsotropicDrag(dragArea, dragCd0);
290         IsotropicDrag isoDrag1 = new IsotropicDrag(dragArea, dragCd1);
291         IsotropicDrag isoDrag2 = new IsotropicDrag(dragArea, dragCd2);
292         forceModel = new TimeSpanDragForce(atmosphere, isoDrag);
293         forceModel.addDragSensitiveValidAfter(isoDrag1, date.shiftedBy(dt));
294         forceModel.addDragSensitiveValidBefore(isoDrag2, date.shiftedBy(-dt));
295 
296         // Extract the drivers and check their values and names
297         drivers = forceModel.getParametersDrivers();
298         Assertions.assertEquals(3,  drivers.size());
299         Assertions.assertEquals(dragCd2,  drivers.get(0).getValue(), 0.);
300         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT + TimeSpanDragForce.DATE_BEFORE + date.shiftedBy(-dt).toString(utc),
301                             drivers.get(0).getName());
302         Assertions.assertEquals(dragCd0,  drivers.get(1).getValue(), 0.);
303         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT,  drivers.get(1).getName());
304         Assertions.assertEquals(dragCd0,  drivers.get(1).getValue(), 0.);
305         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT + TimeSpanDragForce.DATE_AFTER + date.shiftedBy(+dt).toString(utc),
306                             drivers.get(2).getName());
307 
308         // Check that proper models are returned at significant test dates
309         // Cd0 model
310         double eps = 1.e-14;
311         Assertions.assertEquals(isoDrag, forceModel.getDragSensitive(date));
312         Assertions.assertEquals(isoDrag, forceModel.getDragSensitive(date.shiftedBy(-dt)));
313         Assertions.assertEquals(isoDrag, forceModel.getDragSensitive(date.shiftedBy(+dt - eps)));
314         // Cd2 model
315         Assertions.assertEquals(isoDrag2, forceModel.getDragSensitive(date.shiftedBy(-dt - eps)));
316         Assertions.assertEquals(isoDrag2, forceModel.getDragSensitive(date.shiftedBy(-dt - 86400.)));
317         // Cd1 model
318         Assertions.assertEquals(isoDrag1, forceModel.getDragSensitive(date.shiftedBy(+dt)));
319         Assertions.assertEquals(isoDrag1, forceModel.getDragSensitive(date.shiftedBy(+dt + 86400.)));
320 
321         // Add a custom-named driver
322         // ------------
323         double dragCd3 = 3.;
324         IsotropicDrag isoDrag3 = new IsotropicDrag(dragArea, dragCd3);
325         isoDrag3.getDragParametersDrivers().get(0).setName("custom-Cd");
326         forceModel.addDragSensitiveValidAfter(isoDrag3, date.shiftedBy(2. * dt));
327         drivers = forceModel.getParametersDrivers();
328         Assertions.assertEquals(4,  drivers.size());
329         Assertions.assertEquals(dragCd3,  drivers.get(3).getValue(), 0.);
330         Assertions.assertEquals("custom-Cd", drivers.get(3).getName());
331 
332 
333         // Test #getDragSensitiveSpan method
334         Assertions.assertEquals(isoDrag, forceModel.getDragSensitiveSpan(date).getData());
335         Assertions.assertEquals(isoDrag2, forceModel.getDragSensitiveSpan(date.shiftedBy(-dt - 86400.)).getData());
336         Assertions.assertEquals(isoDrag1, forceModel.getDragSensitiveSpan(date.shiftedBy(+dt + 1.)).getData());
337         Assertions.assertEquals(isoDrag3, forceModel.getDragSensitiveSpan(date.shiftedBy(2 * dt + 1.)).getData());
338 
339         // Test #extractDragSensitiveRange
340         TimeSpanMap<DragSensitive> dragMap = forceModel.extractDragSensitiveRange(date, date.shiftedBy(dt + 1.));
341         Assertions.assertEquals(isoDrag, dragMap.getSpan(date).getData());
342         Assertions.assertEquals(isoDrag1, dragMap.getSpan(date.shiftedBy(dt + 86400.)).getData());
343         Assertions.assertEquals(isoDrag, dragMap.getSpan(date.shiftedBy(-dt - 86400.)).getData());
344     }
345 
346 
347     /** Test parameter derivatives for an IsotropicDrag TimeSpanDragForce.
348      *  This test is more or less a copy of the same one in DragForceTest class
349      *  with addition of several IsotropicDrag models valid at different dates
350      *  to test that the different parameters' derivatives are computed correctly.
351      */
352     @Test
353     public void testParameterDerivativeSphere() {
354 
355         // Low Earth orbit definition (about 360km altitude)
356         final Vector3D pos = new Vector3D(6.46885878304673824e+06, -1.88050918456274318e+06, -1.32931592294715829e+04);
357         final Vector3D vel = new Vector3D(2.14718074509906819e+03, 7.38239351251748485e+03, -1.14097953925384523e+01);
358         final AbsoluteDate date = new AbsoluteDate(2003, 3, 5, 0, 24, 0.0, TimeScalesFactory.getTAI());
359         final SpacecraftState state =
360                 new SpacecraftState(new CartesianOrbit(new PVCoordinates(pos, vel),
361                                                        FramesFactory.getGCRF(),
362                                                        date,
363                                                        Constants.EIGEN5C_EARTH_MU));
364 
365         // Atmosphere
366         final Atmosphere atmosphere = new HarrisPriester(CelestialBodyFactory.getSun(),
367                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
368                                                                               Constants.WGS84_EARTH_FLATTENING,
369                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
370 
371         // Constant area for the different tests
372         final double dragArea = 2.5;
373 
374         // Initialize force model (first coef is valid at all epochs)
375         final double dragCd  = 1.2;
376         final IsotropicDrag isotropicDrag = new IsotropicDrag(dragArea, dragCd);
377         isotropicDrag.getDragParametersDrivers().get(0).setName("Cd");
378         final TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, isotropicDrag, TimeScalesFactory.getUTC());
379 
380 
381         // After t2 = t + 4h
382         final double dragCd2 = 3.;
383         final double dt2 = 4 * 3600.;
384         final AbsoluteDate date2 = date.shiftedBy(dt2);
385         final IsotropicDrag isotropicDrag2 = new IsotropicDrag(dragArea, dragCd2);
386         isotropicDrag2.getDragParametersDrivers().get(0).setName("Cd2");
387         forceModel.addDragSensitiveValidAfter(isotropicDrag2, date2);
388 
389         // Before t3 = t - 1day
390         final double dragCd3 = 3.;
391         final double dt3 = -86400.;
392         final AbsoluteDate date3 = date.shiftedBy(dt3);
393         final IsotropicDrag isotropicDrag3 = new IsotropicDrag(dragArea, dragCd3);
394         isotropicDrag3.getDragParametersDrivers().get(0).setName("Cd3");
395         forceModel.addDragSensitiveValidBefore(isotropicDrag3, date3);
396 
397 
398         Assertions.assertFalse(forceModel.dependsOnPositionOnly());
399 
400         // Check parameter derivatives at initial date: only "Cd" shouldn't be 0.
401         checkParameterDerivative(state, forceModel, "Cd" , 1.0e-4, 2.0e-12);
402         checkParameterDerivative(state, forceModel, "Cd2", 1.0e-4, 0.);
403         checkParameterDerivative(state, forceModel, "Cd3", 1.0e-4, 0.);
404 
405         // Check parameter derivatives after date2: only "Cd2" shouldn't be 0.
406         checkParameterDerivative(state.shiftedBy(dt2 * 1.1), forceModel, "Cd", 1.0e-4, 0.);
407         checkParameterDerivative(state.shiftedBy(dt2 * 1.1), forceModel, "Cd2", 1.0e-4, 2.0e-12);
408         checkParameterDerivative(state.shiftedBy(dt2 * 1.1), forceModel, "Cd3", 1.0e-4, 0.);
409 
410         // Check parameter derivatives after date3: only "Cd3" shouldn't be 0.
411         checkParameterDerivative(state.shiftedBy(dt3 * 1.1), forceModel, "Cd", 1.0e-4, 0.);
412         checkParameterDerivative(state.shiftedBy(dt3 * 1.1), forceModel, "Cd2", 1.0e-4, 0.);
413         checkParameterDerivative(state.shiftedBy(dt3 * 1.1), forceModel, "Cd3", 1.0e-4, 2.0e-12);
414     }
415 
416     /** Test parameter derivatives for an IsotropicDrag TimeSpanDragForce.
417      *  This test is more or less a copy of the same one in DragForceTest class
418      *  with addition of several IsotropicDrag models valid at different dates
419      *  to test that the different parameters' derivatives are computed correctly.
420      */
421     @Test
422     public void testParameterDerivativeSphereGradient() {
423 
424         // Low Earth orbit definition (about 360km altitude)
425         final Vector3D pos = new Vector3D(6.46885878304673824e+06, -1.88050918456274318e+06, -1.32931592294715829e+04);
426         final Vector3D vel = new Vector3D(2.14718074509906819e+03, 7.38239351251748485e+03, -1.14097953925384523e+01);
427         final AbsoluteDate date = new AbsoluteDate(2003, 3, 5, 0, 24, 0.0, TimeScalesFactory.getTAI());
428         final SpacecraftState state =
429                 new SpacecraftState(new CartesianOrbit(new PVCoordinates(pos, vel),
430                                                        FramesFactory.getGCRF(),
431                                                        date,
432                                                        Constants.EIGEN5C_EARTH_MU));
433 
434         // Atmosphere
435         final Atmosphere atmosphere = new HarrisPriester(CelestialBodyFactory.getSun(),
436                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
437                                                                               Constants.WGS84_EARTH_FLATTENING,
438                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
439 
440         // Constant area for the different tests
441         final double dragArea = 2.5;
442 
443         // Initialize force model (first coef is valid at all epochs)
444         final double dragCd  = 1.2;
445         final IsotropicDrag isotropicDrag = new IsotropicDrag(dragArea, dragCd);
446         isotropicDrag.getDragParametersDrivers().get(0).setName("Cd");
447         final TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, isotropicDrag, TimeScalesFactory.getUTC());
448 
449 
450         // After t2 = t + 4h
451         final double dragCd2 = 3.;
452         final double dt2 = 4 * 3600.;
453         final AbsoluteDate date2 = date.shiftedBy(dt2);
454         final IsotropicDrag isotropicDrag2 = new IsotropicDrag(dragArea, dragCd2);
455         isotropicDrag2.getDragParametersDrivers().get(0).setName("Cd2");
456         forceModel.addDragSensitiveValidAfter(isotropicDrag2, date2);
457 
458         // Before t3 = t - 1day
459         final double dragCd3 = 3.;
460         final double dt3 = -86400.;
461         final AbsoluteDate date3 = date.shiftedBy(dt3);
462         final IsotropicDrag isotropicDrag3 = new IsotropicDrag(dragArea, dragCd3);
463         isotropicDrag3.getDragParametersDrivers().get(0).setName("Cd3");
464         forceModel.addDragSensitiveValidBefore(isotropicDrag3, date3);
465 
466 
467         Assertions.assertFalse(forceModel.dependsOnPositionOnly());
468 
469         // Check parameter derivatives at initial date: only "Cd" shouldn't be 0.
470         checkParameterDerivativeGradient(state, forceModel, "Cd" , 1.0e-4, 2.0e-12);
471         checkParameterDerivativeGradient(state, forceModel, "Cd2", 1.0e-4, 0.);
472         checkParameterDerivativeGradient(state, forceModel, "Cd3", 1.0e-4, 0.);
473 
474         // Check parameter derivatives after date2: only "Cd2" shouldn't be 0.
475         checkParameterDerivativeGradient(state.shiftedBy(dt2 * 1.1), forceModel, "Cd", 1.0e-4, 0.);
476         checkParameterDerivativeGradient(state.shiftedBy(dt2 * 1.1), forceModel, "Cd2", 1.0e-4, 2.0e-12);
477         checkParameterDerivativeGradient(state.shiftedBy(dt2 * 1.1), forceModel, "Cd3", 1.0e-4, 0.);
478 
479         // Check parameter derivatives after date3: only "Cd3" shouldn't be 0.
480         checkParameterDerivativeGradient(state.shiftedBy(dt3 * 1.1), forceModel, "Cd", 1.0e-4, 0.);
481         checkParameterDerivativeGradient(state.shiftedBy(dt3 * 1.1), forceModel, "Cd2", 1.0e-4, 0.);
482         checkParameterDerivativeGradient(state.shiftedBy(dt3 * 1.1), forceModel, "Cd3", 1.0e-4, 2.0e-12);
483     }
484 
485     /** Test state Jacobian computation. */
486     @Test
487     public void testStateJacobianSphere()
488         {
489 
490         // Initialization
491         AbsoluteDate date = new AbsoluteDate(new DateComponents(2003, 03, 01),
492                                              new TimeComponents(13, 59, 27.816),
493                                              TimeScalesFactory.getUTC());
494         double i     = FastMath.toRadians(98.7);
495         double omega = FastMath.toRadians(93.0);
496         double OMEGA = FastMath.toRadians(15.0 * 22.5);
497         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, i , omega, OMEGA,
498                                          0, PositionAngle.MEAN, FramesFactory.getEME2000(), date,
499                                          Constants.EIGEN5C_EARTH_MU);
500         OrbitType integrationType = OrbitType.CARTESIAN;
501         double[][] tolerances = NumericalPropagator.tolerances(0.01, orbit, integrationType);
502 
503 
504 
505         // Atmosphere
506         final Atmosphere atmosphere = new HarrisPriester(CelestialBodyFactory.getSun(),
507                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
508                                                                               Constants.WGS84_EARTH_FLATTENING,
509                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
510 
511         // Time span drag force model init
512         double dragArea = 2.;
513         double dragCd0 = 1.;
514         double dragCd1 = 2.;
515         double dragCd2 = 3.;
516         double dt = 1. * 3600.;
517         // Build the force model
518         IsotropicDrag isoDrag0 = new IsotropicDrag(dragArea, dragCd0);
519         IsotropicDrag isoDrag1 = new IsotropicDrag(dragArea, dragCd1);
520         IsotropicDrag isoDrag2 = new IsotropicDrag(dragArea, dragCd2);
521         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, isoDrag0);
522         forceModel.addDragSensitiveValidAfter(isoDrag1, date.shiftedBy(dt));
523         forceModel.addDragSensitiveValidBefore(isoDrag2, date.shiftedBy(-dt));
524 
525         // Check state derivatives inside first IsotropicDrag model
526         NumericalPropagator propagator =
527                         new NumericalPropagator(new DormandPrince853Integrator(1.0e-3, 120,
528                                                                                tolerances[0], tolerances[1]));
529         propagator.setOrbitType(integrationType);
530         propagator.addForceModel(forceModel);
531         SpacecraftState state0 = new SpacecraftState(orbit);
532         // Set target date to 0.5*dt to be inside 1st drag model
533         // The further away we are from the initial date, the greater the checkTolerance parameter must be set
534         checkStateJacobian(propagator, state0, date.shiftedBy(0.5 * dt),
535                            1e3, tolerances[0], 9.2e-10);
536 
537         // Check state derivatives inside 2nd IsotropicDrag model
538         propagator = new NumericalPropagator(new DormandPrince853Integrator(1.0e-3, 120,
539                                                                             tolerances[0], tolerances[1]));
540         propagator.setOrbitType(integrationType);
541         propagator.addForceModel(forceModel);
542         // Set target date to 1.5*dt to be inside 2nd drag model
543         // The further away we are from the initial date, the greater the checkTolerance parameter must be set
544         checkStateJacobian(propagator, state0, date.shiftedBy(1.5 * dt),
545                            1e3, tolerances[0], 6.7e-9);
546 
547         // Check state derivatives inside 3rd IsotropicDrag model
548         propagator = new NumericalPropagator(new DormandPrince853Integrator(1.0e-3, 120,
549                                                                             tolerances[0], tolerances[1]));
550         propagator.setOrbitType(integrationType);
551         propagator.addForceModel(forceModel);
552         // Set target date to *1.5*dt to be inside 3rd drag model
553         // The further away we are from the initial date, the greater the checkTolerance parameter must be set
554         checkStateJacobian(propagator, state0, date.shiftedBy(-1.5 * dt),
555                            1e3, tolerances[0], 6.0e-9);
556     }
557 
558     /** Test that the getParameterDrivers method is working as expected
559      * on an BoxAndSolarArraySpacecraft-based TimeSpanDragForce model.
560      * Here only the drag coefficient is modeled.
561      */
562     @Test
563     public void testGetParameterDriversBoxOnlyDrag() {
564 
565         // Atmosphere
566         final CelestialBody sun = CelestialBodyFactory.getSun();
567         final Atmosphere atmosphere = new HarrisPriester(sun,
568                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
569                                                                               Constants.WGS84_EARTH_FLATTENING,
570                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
571 
572         // A date
573         AbsoluteDate date = new AbsoluteDate("2000-01-01T00:00:00.000", TimeScalesFactory.getUTC());
574 
575         // One BoxAndSolarArraySpacecraft added, test with one or two "default" drivers
576         // -----------------------
577 
578         double dragCd0 = 0.;
579         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
580                                                                          sun, 20.0, Vector3D.PLUS_J,
581                                                                          dragCd0,
582                                                                          0.7, 0.2);
583         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
584         Assertions.assertFalse(forceModel.dependsOnPositionOnly());
585         List<ParameterDriver> drivers = forceModel.getParametersDrivers();
586         Assertions.assertEquals(1,  drivers.size());
587         Assertions.assertEquals(dragCd0,  drivers.get(0).getValue(), 0.);
588         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT,  drivers.get(0).getName());
589 
590         // Extract drag model at an arbitrary epoch and check it is the one added
591         BoxAndSolarArraySpacecraft box = (BoxAndSolarArraySpacecraft) forceModel.getDragSensitive(date);
592         drivers = box.getDragParametersDrivers();
593         Assertions.assertEquals(1, drivers.size());
594         Assertions.assertEquals(dragCd0,  drivers.get(0).getValue(), 0.);
595         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT,  drivers.get(0).getName());
596 
597         // 3 BoxAndSolarArraySpacecraft models added, with one "default" in the middle
598         // ----------------------------------------------
599         double dragCd1 = 1.;
600         double dragCd2 = 2.;
601         double dt = 120.;
602         // Build the force model
603         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
604                                                                          sun, 20.0, Vector3D.PLUS_J,
605                                                                          dragCd1,
606                                                                          0.7, 0.2);
607         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
608                                                                          sun, 20.0, Vector3D.PLUS_J,
609                                                                          dragCd2,
610                                                                          0.7, 0.2);
611         forceModel = new TimeSpanDragForce(atmosphere, box0);
612         forceModel.addDragSensitiveValidAfter(box1, date.shiftedBy(dt));
613         forceModel.addDragSensitiveValidBefore(box2, date.shiftedBy(-dt));
614 
615         // Extract the drivers and check their values and names
616         drivers = forceModel.getParametersDrivers();
617         Assertions.assertEquals(3,  drivers.size());
618         Assertions.assertEquals(dragCd2,  drivers.get(0).getValue(), 0.);
619         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT + TimeSpanDragForce.DATE_BEFORE + date.shiftedBy(-dt).toString(utc),
620                             drivers.get(0).getName());
621 
622         Assertions.assertEquals(dragCd0,  drivers.get(1).getValue(), 0.);
623         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT,  drivers.get(1).getName());
624 
625         Assertions.assertEquals(dragCd1,  drivers.get(2).getValue(), 0.);
626         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT + TimeSpanDragForce.DATE_AFTER + date.shiftedBy(+dt).toString(utc),
627                             drivers.get(2).getName());
628 
629         // Check the models at dates
630         // Cd0 model
631         double eps = 1.e-14;
632         Assertions.assertEquals(box0, forceModel.getDragSensitive(date));
633         Assertions.assertEquals(box0, forceModel.getDragSensitive(date.shiftedBy(-dt)));
634         Assertions.assertEquals(box0, forceModel.getDragSensitive(date.shiftedBy(+dt - eps)));
635         // Cd2 model
636         Assertions.assertEquals(box2, forceModel.getDragSensitive(date.shiftedBy(-dt - eps)));
637         Assertions.assertEquals(box2, forceModel.getDragSensitive(date.shiftedBy(-dt - 86400.)));
638         // Cd1 model
639         Assertions.assertEquals(box1, forceModel.getDragSensitive(date.shiftedBy(+dt)));
640         Assertions.assertEquals(box1, forceModel.getDragSensitive(date.shiftedBy(+dt + 86400.)));
641 
642         // Add a custom-named driver
643         // ----------------
644         double dragCd3 = 3.;
645         BoxAndSolarArraySpacecraft box3 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
646                                                                          sun, 20.0, Vector3D.PLUS_J,
647                                                                          dragCd3,
648                                                                          0.7, 0.2);
649         box3.getDragParametersDrivers().get(0).setName("custom-Cd");
650         forceModel.addDragSensitiveValidAfter(box3, date.shiftedBy(2. * dt));
651         drivers = forceModel.getParametersDrivers();
652         Assertions.assertEquals(4,  drivers.size());
653         Assertions.assertEquals(dragCd3,  drivers.get(3).getValue(), 0.);
654         Assertions.assertEquals("custom-Cd", drivers.get(3).getName());
655     }
656 
657     /** Test that the getParameterDrivers method is working as expected
658      * on an BoxAndSolarArraySpacecraft-based TimeSpanDragForce model.
659      * Here both drag and lift coefficients are modeled.
660      */
661     @Test
662     public void testGetParameterDriversBox() {
663 
664         // Atmosphere
665         final CelestialBody sun = CelestialBodyFactory.getSun();
666         final Atmosphere atmosphere = new HarrisPriester(sun,
667                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
668                                                                               Constants.WGS84_EARTH_FLATTENING,
669                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
670 
671         // A date
672         AbsoluteDate date = new AbsoluteDate("2000-01-01T00:00:00.000", TimeScalesFactory.getUTC());
673 
674         // One BoxAndSolarArraySpacecraft added, test with one or two "default" drivers
675         // -----------------------
676 
677         double dragCd0 = 0.;
678         double dragCl0 = 0.;
679         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
680                                                                          sun, 20.0, Vector3D.PLUS_J,
681                                                                          dragCd0, dragCl0,
682                                                                          0.7, 0.2);
683         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
684         Assertions.assertFalse(forceModel.dependsOnPositionOnly());
685         List<ParameterDriver> drivers = forceModel.getParametersDrivers();
686         Assertions.assertEquals(2,  drivers.size());
687         Assertions.assertEquals(dragCd0,  drivers.get(0).getValue(), 0.);
688         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT,  drivers.get(0).getName());
689         Assertions.assertEquals(dragCl0,  drivers.get(1).getValue(), 0.);
690         Assertions.assertEquals(DragSensitive.LIFT_RATIO,  drivers.get(1).getName());
691 
692         // Extract drag model at an arbitrary epoch and check it is the one added
693         BoxAndSolarArraySpacecraft box = (BoxAndSolarArraySpacecraft) forceModel.getDragSensitive(date);
694         drivers = box.getDragParametersDrivers();
695         Assertions.assertEquals(2, drivers.size());
696         Assertions.assertEquals(dragCd0,  drivers.get(0).getValue(), 0.);
697         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT,  drivers.get(0).getName());
698         Assertions.assertEquals(dragCl0,  drivers.get(1).getValue(), 0.);
699         Assertions.assertEquals(DragSensitive.LIFT_RATIO,  drivers.get(1).getName());
700 
701         // 3 BoxAndSolarArraySpacecraft models added, with one "default" in the middle
702         // ----------------------------------------------
703         double dragCd1 = 1.;
704         double dragCl1 = 0.1;
705         double dragCd2 = 2.;
706         double dragCl2 = 0.2;
707         double dt = 120.;
708         // Build the force model
709         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
710                                                                          sun, 20.0, Vector3D.PLUS_J,
711                                                                          dragCd1, dragCl1,
712                                                                          0.7, 0.2);
713         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
714                                                                          sun, 20.0, Vector3D.PLUS_J,
715                                                                          dragCd2, dragCl2,
716                                                                          0.7, 0.2);
717         forceModel = new TimeSpanDragForce(atmosphere, box0);
718         forceModel.addDragSensitiveValidAfter(box1, date.shiftedBy(dt));
719         forceModel.addDragSensitiveValidBefore(box2, date.shiftedBy(-dt));
720 
721         // Extract the drivers and check their values and names
722         drivers = forceModel.getParametersDrivers();
723         Assertions.assertEquals(6,  drivers.size());
724         Assertions.assertEquals(dragCd2,  drivers.get(0).getValue(), 0.);
725         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT + TimeSpanDragForce.DATE_BEFORE + date.shiftedBy(-dt).toString(utc),
726                             drivers.get(0).getName());
727         Assertions.assertEquals(dragCl2,  drivers.get(1).getValue(), 0.);
728         Assertions.assertEquals(DragSensitive.LIFT_RATIO + TimeSpanDragForce.DATE_BEFORE + date.shiftedBy(-dt).toString(utc),
729                             drivers.get(1).getName());
730 
731         Assertions.assertEquals(dragCd0,  drivers.get(2).getValue(), 0.);
732         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT,  drivers.get(2).getName());
733         Assertions.assertEquals(dragCl0,  drivers.get(3).getValue(), 0.);
734         Assertions.assertEquals(DragSensitive.LIFT_RATIO,  drivers.get(3).getName());
735 
736         Assertions.assertEquals(dragCd1,  drivers.get(4).getValue(), 0.);
737         Assertions.assertEquals(DragSensitive.DRAG_COEFFICIENT + TimeSpanDragForce.DATE_AFTER + date.shiftedBy(+dt).toString(utc),
738                             drivers.get(4).getName());
739         Assertions.assertEquals(dragCl1,  drivers.get(5).getValue(), 0.);
740         Assertions.assertEquals(DragSensitive.LIFT_RATIO + TimeSpanDragForce.DATE_AFTER + date.shiftedBy(+dt).toString(utc),
741                             drivers.get(5).getName());
742 
743         // Check the models at dates
744         // Cd0 model
745         double eps = 1.e-14;
746         Assertions.assertEquals(box0, forceModel.getDragSensitive(date));
747         Assertions.assertEquals(box0, forceModel.getDragSensitive(date.shiftedBy(-dt)));
748         Assertions.assertEquals(box0, forceModel.getDragSensitive(date.shiftedBy(+dt - eps)));
749         // Cd2 model
750         Assertions.assertEquals(box2, forceModel.getDragSensitive(date.shiftedBy(-dt - eps)));
751         Assertions.assertEquals(box2, forceModel.getDragSensitive(date.shiftedBy(-dt - 86400.)));
752         // Cd1 model
753         Assertions.assertEquals(box1, forceModel.getDragSensitive(date.shiftedBy(+dt)));
754         Assertions.assertEquals(box1, forceModel.getDragSensitive(date.shiftedBy(+dt + 86400.)));
755 
756 
757         // Add a custom-named driver
758         // ----------------
759         double dragCd3 = 3.;
760         double dragCl3 = 0.3;
761         BoxAndSolarArraySpacecraft box3 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
762                                                                          sun, 20.0, Vector3D.PLUS_J,
763                                                                          dragCd3, dragCl3,
764                                                                          0.7, 0.2);
765         box3.getDragParametersDrivers().get(0).setName("custom-Cd");
766         box3.getDragParametersDrivers().get(1).setName("custom-Cl");
767         forceModel.addDragSensitiveValidAfter(box3, date.shiftedBy(2. * dt));
768         drivers = forceModel.getParametersDrivers();
769         Assertions.assertEquals(8,  drivers.size());
770         Assertions.assertEquals(dragCd3,  drivers.get(6).getValue(), 0.);
771         Assertions.assertEquals("custom-Cd", drivers.get(6).getName());
772         Assertions.assertEquals(dragCl3,  drivers.get(7).getValue(), 0.);
773         Assertions.assertEquals("custom-Cl", drivers.get(7).getName());
774     }
775 
776     /** Test parameter derivatives for an BoxAndSolarArraySpacecraft TimeSpanDragForce.
777      *  This test is more or less a copy of the same one in DragForceTest class
778      *  with addition of several BoxAndSolarArraySpacecraft models valid at different dates
779      *  to test that the different parameters' derivatives are computed correctly.
780      */
781     @Test
782     public void testParametersDerivativesBox() {
783 
784         // Low Earth orbit definition (about 360km altitude)
785         final Vector3D pos = new Vector3D(6.46885878304673824e+06, -1.88050918456274318e+06, -1.32931592294715829e+04);
786         final Vector3D vel = new Vector3D(2.14718074509906819e+03, 7.38239351251748485e+03, -1.14097953925384523e+01);
787         final AbsoluteDate date = new AbsoluteDate(2003, 3, 5, 0, 24, 0.0, TimeScalesFactory.getTAI());
788         final SpacecraftState state =
789                 new SpacecraftState(new CartesianOrbit(new PVCoordinates(pos, vel),
790                                                        FramesFactory.getGCRF(),
791                                                        date,
792                                                        Constants.EIGEN5C_EARTH_MU));
793 
794         // Atmosphere
795         final CelestialBody sun = CelestialBodyFactory.getSun();
796         final Atmosphere atmosphere = new HarrisPriester(sun,
797                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
798                                                                               Constants.WGS84_EARTH_FLATTENING,
799                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
800 
801         // Initialize force model (first coef is valid at all epochs)
802         final double dragCd  = 1.;
803         final double dragCl  = 0.1;
804         final BoxAndSolarArraySpacecraft box = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
805                                                                                sun, 20.0, Vector3D.PLUS_J,
806                                                                                dragCd, dragCl, 0.7, 0.2);
807         final TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box);
808 
809 
810         // After t2 = 4h
811         final double dragCd2 = 2.;
812         final double dragCl2 = 0.2;
813         final double dt2 = 4 * 3600.;
814         final AbsoluteDate date2 = date.shiftedBy(dt2);
815         final BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
816                                                                                sun, 20.0, Vector3D.PLUS_J,
817                                                                                dragCd2, dragCl2, 0.7, 0.2);
818         box2.getDragParametersDrivers().get(0).setName("Cd2");
819         box2.getDragParametersDrivers().get(1).setName("Cl2");
820         forceModel.addDragSensitiveValidAfter(box2, date2);
821 
822         // Before t3 = 1day
823         final double dragCd3 = 3.;
824         final double dragCl3 = 0.3;
825         final double dt3 = -86400.;
826         final AbsoluteDate date3 = date.shiftedBy(dt3);
827         final BoxAndSolarArraySpacecraft box3 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
828                                                                                sun, 20.0, Vector3D.PLUS_J,
829                                                                                dragCd3, dragCl3, 0.7, 0.2);
830         box3.getDragParametersDrivers().get(0).setName("Cd3");
831         forceModel.addDragSensitiveValidBefore(box3, date3);
832 
833         // Name of Cl3 is kept as default for the test
834         final String nameCl3 = DragSensitive.LIFT_RATIO + TimeSpanDragForce.DATE_BEFORE + date3.toString(utc);
835 
836 
837         Assertions.assertFalse(forceModel.dependsOnPositionOnly());
838 
839         // Check parameter derivatives at initial date: only 1st model parameter derivatives shouldn't be 0.
840         checkParameterDerivative(state, forceModel, DragSensitive.DRAG_COEFFICIENT, 1.0e-4, 2.0e-12);
841         checkParameterDerivative(state, forceModel, DragSensitive.LIFT_RATIO, 1.0e-4, 2.2e-11);
842         checkParameterDerivative(state, forceModel, "Cd2", 1.0e-4, 0.);
843         checkParameterDerivative(state, forceModel, "Cl2", 1.0e-4, 0.);
844         checkParameterDerivative(state, forceModel, "Cd3", 1.0e-4, 0.);
845         checkParameterDerivative(state, forceModel, nameCl3, 1.0e-4, 0.);
846 
847         // Check parameter derivatives after date2: only 2nd model parameter derivatives shouldn't be 0.
848         checkParameterDerivative(state.shiftedBy(dt2 * 1.1), forceModel, DragSensitive.DRAG_COEFFICIENT, 1.0e-4, 0.);
849         checkParameterDerivative(state.shiftedBy(dt2 * 1.1), forceModel, DragSensitive.LIFT_RATIO, 1.0e-4, 0.);
850         checkParameterDerivative(state.shiftedBy(dt2 * 1.1), forceModel, "Cd2", 1.0e-4, 2.2e-12);
851         checkParameterDerivative(state.shiftedBy(dt2 * 1.1), forceModel, "Cl2", 1.0e-4, 2.0e-11);
852         checkParameterDerivative(state.shiftedBy(dt2 * 1.1), forceModel, "Cd3", 1.0e-4, 0.);
853         checkParameterDerivative(state.shiftedBy(dt2 * 1.1), forceModel, nameCl3, 1.0e-4, 0.);
854 
855         // Check parameter derivatives before date3: only 3nd model parameter derivatives shouldn't be 0.
856         checkParameterDerivative(state.shiftedBy(dt3 * 1.1), forceModel, DragSensitive.DRAG_COEFFICIENT, 1.0e-4, 0.);
857         checkParameterDerivative(state.shiftedBy(dt3 * 1.1), forceModel, DragSensitive.LIFT_RATIO, 1.0e-4, 0.);
858         checkParameterDerivative(state.shiftedBy(dt3 * 1.1), forceModel, "Cd2", 1.0e-4, 0.);
859         checkParameterDerivative(state.shiftedBy(dt3 * 1.1), forceModel, "Cl2", 1.0e-4, 0.);
860         checkParameterDerivative(state.shiftedBy(dt3 * 1.1), forceModel, "Cd3", 1.0e-4, 2.0e-12);
861         checkParameterDerivative(state.shiftedBy(dt3 * 1.1), forceModel, nameCl3, 1.0e-4, 2.0e-11);
862     }
863 
864     /** Test parameter derivatives for an BoxAndSolarArraySpacecraft TimeSpanDragForce.
865      *  This test is more or less a copy of the same one in DragForceTest class
866      *  with addition of several BoxAndSolarArraySpacecraft models valid at different dates
867      *  to test that the different parameters' derivatives are computed correctly.
868      */
869     @Test
870     public void testParametersDerivativesBoxGradient() {
871 
872         // Low Earth orbit definition (about 360km altitude)
873         final Vector3D pos = new Vector3D(6.46885878304673824e+06, -1.88050918456274318e+06, -1.32931592294715829e+04);
874         final Vector3D vel = new Vector3D(2.14718074509906819e+03, 7.38239351251748485e+03, -1.14097953925384523e+01);
875         final AbsoluteDate date = new AbsoluteDate(2003, 3, 5, 0, 24, 0.0, TimeScalesFactory.getTAI());
876         final SpacecraftState state =
877                 new SpacecraftState(new CartesianOrbit(new PVCoordinates(pos, vel),
878                                                        FramesFactory.getGCRF(),
879                                                        date,
880                                                        Constants.EIGEN5C_EARTH_MU));
881 
882         // Atmosphere
883         final CelestialBody sun = CelestialBodyFactory.getSun();
884         final Atmosphere atmosphere = new HarrisPriester(sun,
885                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
886                                                                               Constants.WGS84_EARTH_FLATTENING,
887                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
888 
889         // Initialize force model (first coef is valid at all epochs)
890         final double dragCd  = 1.;
891         final double dragCl  = 0.1;
892         final BoxAndSolarArraySpacecraft box = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
893                                                                                sun, 20.0, Vector3D.PLUS_J,
894                                                                                dragCd, dragCl, 0.7, 0.2);
895         final TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box);
896 
897 
898         // After t2 = 4h
899         final double dragCd2 = 2.;
900         final double dragCl2 = 0.2;
901         final double dt2 = 4 * 3600.;
902         final AbsoluteDate date2 = date.shiftedBy(dt2);
903         final BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
904                                                                                sun, 20.0, Vector3D.PLUS_J,
905                                                                                dragCd2, dragCl2, 0.7, 0.2);
906         box2.getDragParametersDrivers().get(0).setName("Cd2");
907         box2.getDragParametersDrivers().get(1).setName("Cl2");
908         forceModel.addDragSensitiveValidAfter(box2, date2);
909 
910         // Before t3 = 1day
911         final double dragCd3 = 3.;
912         final double dragCl3 = 0.3;
913         final double dt3 = -86400.;
914         final AbsoluteDate date3 = date.shiftedBy(dt3);
915         final BoxAndSolarArraySpacecraft box3 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8,
916                                                                                sun, 20.0, Vector3D.PLUS_J,
917                                                                                dragCd3, dragCl3, 0.7, 0.2);
918         box3.getDragParametersDrivers().get(0).setName("Cd3");
919         forceModel.addDragSensitiveValidBefore(box3, date3);
920 
921         // Name of Cl3 is kept as default for the test
922         final String nameCl3 = DragSensitive.LIFT_RATIO + TimeSpanDragForce.DATE_BEFORE + date3.toString(utc);
923 
924 
925         Assertions.assertFalse(forceModel.dependsOnPositionOnly());
926 
927         // Check parameter derivatives at initial date: only 1st model parameter derivatives shouldn't be 0.
928         checkParameterDerivativeGradient(state, forceModel, DragSensitive.DRAG_COEFFICIENT, 1.0e-4, 2.0e-12);
929         checkParameterDerivativeGradient(state, forceModel, DragSensitive.LIFT_RATIO, 1.0e-4, 2.2e-11);
930         checkParameterDerivativeGradient(state, forceModel, "Cd2", 1.0e-4, 0.);
931         checkParameterDerivativeGradient(state, forceModel, "Cl2", 1.0e-4, 0.);
932         checkParameterDerivativeGradient(state, forceModel, "Cd3", 1.0e-4, 0.);
933         checkParameterDerivativeGradient(state, forceModel, nameCl3, 1.0e-4, 0.);
934 
935         // Check parameter derivatives after date2: only 2nd model parameter derivatives shouldn't be 0.
936         checkParameterDerivativeGradient(state.shiftedBy(dt2 * 1.1), forceModel, DragSensitive.DRAG_COEFFICIENT, 1.0e-4, 0.);
937         checkParameterDerivativeGradient(state.shiftedBy(dt2 * 1.1), forceModel, DragSensitive.LIFT_RATIO, 1.0e-4, 0.);
938         checkParameterDerivativeGradient(state.shiftedBy(dt2 * 1.1), forceModel, "Cd2", 1.0e-4, 2.2e-12);
939         checkParameterDerivativeGradient(state.shiftedBy(dt2 * 1.1), forceModel, "Cl2", 1.0e-4, 2.0e-11);
940         checkParameterDerivativeGradient(state.shiftedBy(dt2 * 1.1), forceModel, "Cd3", 1.0e-4, 0.);
941         checkParameterDerivativeGradient(state.shiftedBy(dt2 * 1.1), forceModel, nameCl3, 1.0e-4, 0.);
942 
943         // Check parameter derivatives before date3: only 3nd model parameter derivatives shouldn't be 0.
944         checkParameterDerivativeGradient(state.shiftedBy(dt3 * 1.1), forceModel, DragSensitive.DRAG_COEFFICIENT, 1.0e-4, 0.);
945         checkParameterDerivativeGradient(state.shiftedBy(dt3 * 1.1), forceModel, DragSensitive.LIFT_RATIO, 1.0e-4, 0.);
946         checkParameterDerivativeGradient(state.shiftedBy(dt3 * 1.1), forceModel, "Cd2", 1.0e-4, 0.);
947         checkParameterDerivativeGradient(state.shiftedBy(dt3 * 1.1), forceModel, "Cl2", 1.0e-4, 0.);
948         checkParameterDerivativeGradient(state.shiftedBy(dt3 * 1.1), forceModel, "Cd3", 1.0e-4, 2.0e-12);
949         checkParameterDerivativeGradient(state.shiftedBy(dt3 * 1.1), forceModel, nameCl3, 1.0e-4, 2.0e-11);
950     }
951 
952     /** Test state Jacobian computation using finite differences in position
953      * and method {@link #accelerationDerivatives}
954      */
955     @Test
956     public void testJacobianBoxVs80Implementation()
957         {
958 
959         // initialization
960         AbsoluteDate date = new AbsoluteDate(new DateComponents(2003, 03, 01),
961                                              new TimeComponents(13, 59, 27.816),
962                                              TimeScalesFactory.getUTC());
963         double i     = FastMath.toRadians(98.7);
964         double omega = FastMath.toRadians(93.0);
965         double OMEGA = FastMath.toRadians(15.0 * 22.5);
966         Orbit refOrbit = new KeplerianOrbit(7201009.7124401, 1e-3, i , omega, OMEGA,
967                                             0, PositionAngle.MEAN, FramesFactory.getEME2000(), date,
968                                             Constants.EIGEN5C_EARTH_MU);
969         CelestialBody sun = CelestialBodyFactory.getSun();
970 
971         // Atmosphere
972         final Atmosphere atmosphere =
973                         new HarrisPriester(sun,
974                                            new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
975                                                                 Constants.WGS84_EARTH_FLATTENING,
976                                                                 FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
977         // Time span drag force model initialization
978         double dragCd0 = 1.;
979         double dragCl0 = 0.1;
980         double dragCd1 = 2.;
981         double dragCl1 = 0.2;
982         double dragCd2 = 3.;
983         double dragCl2 = 0.3;
984         double dt = 3. * 3600.;
985 
986         // Build the force model
987         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
988                                                                          Vector3D.PLUS_J, dragCd0, dragCl0, 0.7, 0.2);
989         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
990                                                                          Vector3D.PLUS_J, dragCd1, dragCl1, 0.7, 0.2);
991         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
992                                                                          Vector3D.PLUS_J, dragCd2, dragCl2, 0.7, 0.2);
993         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
994         forceModel.addDragSensitiveValidAfter(box1, date.shiftedBy(dt));
995         forceModel.addDragSensitiveValidBefore(box2, date.shiftedBy(-dt));
996 
997         // Check state derivatives inside first box model
998         Orbit orbit = refOrbit.shiftedBy(0.);
999         SpacecraftState state = new SpacecraftState(orbit,
1000                                                     Utils.defaultLaw().getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1001         checkStateJacobianVs80Implementation(state, forceModel,
1002                                              new LofOffset(state.getFrame(), LOFType.LVLH_CCSDS),
1003                                              5e-6, false);
1004 
1005         // Check state derivatives inside 2nd box model
1006         orbit = refOrbit.shiftedBy(1.1 * dt);
1007         state = new SpacecraftState(orbit,
1008                                     Utils.defaultLaw().getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1009         checkStateJacobianVs80Implementation(state, forceModel,
1010                                              new LofOffset(state.getFrame(), LOFType.LVLH_CCSDS),
1011                                              5e-6, false);
1012 
1013         // Check state derivatives inside 3rd box model
1014         orbit = refOrbit.shiftedBy(-1.1 * dt);
1015         state = new SpacecraftState(orbit,
1016                                     Utils.defaultLaw().getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1017         checkStateJacobianVs80Implementation(state, forceModel,
1018                                              new LofOffset(state.getFrame(), LOFType.LVLH_CCSDS),
1019                                              5e-6, false);
1020 
1021     }
1022 
1023     /** Test state Jacobian computation using finite differences in position
1024      * and method {@link #accelerationDerivatives}
1025      */
1026     @Test
1027     public void testJacobianBoxVs80ImplementationGradient()
1028         {
1029 
1030         // initialization
1031         AbsoluteDate date = new AbsoluteDate(new DateComponents(2003, 03, 01),
1032                                              new TimeComponents(13, 59, 27.816),
1033                                              TimeScalesFactory.getUTC());
1034         double i     = FastMath.toRadians(98.7);
1035         double omega = FastMath.toRadians(93.0);
1036         double OMEGA = FastMath.toRadians(15.0 * 22.5);
1037         Orbit refOrbit = new KeplerianOrbit(7201009.7124401, 1e-3, i , omega, OMEGA,
1038                                             0, PositionAngle.MEAN, FramesFactory.getEME2000(), date,
1039                                             Constants.EIGEN5C_EARTH_MU);
1040         CelestialBody sun = CelestialBodyFactory.getSun();
1041         AttitudeProvider defaultLaw = Utils.defaultLaw();
1042 
1043         // Atmosphere
1044         final Atmosphere atmosphere =
1045                         new HarrisPriester(sun,
1046                                            new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
1047                                                                 Constants.WGS84_EARTH_FLATTENING,
1048                                                                 FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
1049         // Time span drag force model initialization
1050         double dragCd0 = 1.;
1051         double dragCl0 = 0.1;
1052         double dragCd1 = 2.;
1053         double dragCl1 = 0.2;
1054         double dragCd2 = 3.;
1055         double dragCl2 = 0.3;
1056         double dt = 3. * 3600.;
1057 
1058         // Build the force model
1059         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1060                                                                          Vector3D.PLUS_J, dragCd0, dragCl0, 0.7, 0.2);
1061         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1062                                                                          Vector3D.PLUS_J, dragCd1, dragCl1, 0.7, 0.2);
1063         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1064                                                                          Vector3D.PLUS_J, dragCd2, dragCl2, 0.7, 0.2);
1065         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
1066         forceModel.addDragSensitiveValidAfter(box1, date.shiftedBy(dt));
1067         forceModel.addDragSensitiveValidBefore(box2, date.shiftedBy(-dt));
1068 
1069         // Check state derivatives inside first box model
1070         Orbit orbit = refOrbit.shiftedBy(0.);
1071         SpacecraftState state = new SpacecraftState(orbit,
1072                                                     Utils.defaultLaw().getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1073         checkStateJacobianVs80ImplementationGradient(state, forceModel,
1074                                              new LofOffset(state.getFrame(), LOFType.LVLH_CCSDS),
1075                                              5e-6, false);
1076 
1077         // Check state derivatives inside 2nd box model
1078         orbit = refOrbit.shiftedBy(1.1 * dt);
1079         state = new SpacecraftState(orbit,
1080                                     defaultLaw.getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1081         checkStateJacobianVs80ImplementationGradient(state, forceModel,
1082                                              new LofOffset(state.getFrame(), LOFType.LVLH_CCSDS),
1083                                              5e-6, false);
1084 
1085         // Check state derivatives inside 3rd box model
1086         orbit = refOrbit.shiftedBy(-1.1 * dt);
1087         state = new SpacecraftState(orbit,
1088                                     defaultLaw.getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1089         checkStateJacobianVs80ImplementationGradient(state, forceModel,
1090                                              new LofOffset(state.getFrame(), LOFType.LVLH_CCSDS),
1091                                              5e-6, false);
1092 
1093     }
1094 
1095     /** Test state Jacobian computation using finite differences once again.
1096      * This time the differentiation is made using built-in numerical differentiation method.
1097      */
1098     @Test
1099     public void testJacobianBoxVsFiniteDifferences()
1100         {
1101 
1102         // initialization
1103         AbsoluteDate date = new AbsoluteDate(new DateComponents(2003, 03, 01),
1104                                              new TimeComponents(13, 59, 27.816),
1105                                              TimeScalesFactory.getUTC());
1106         double i     = FastMath.toRadians(98.7);
1107         double omega = FastMath.toRadians(93.0);
1108         double OMEGA = FastMath.toRadians(15.0 * 22.5);
1109         Orbit refOrbit = new KeplerianOrbit(7201009.7124401, 1e-3, i , omega, OMEGA,
1110                                             0, PositionAngle.MEAN, FramesFactory.getEME2000(), date,
1111                                             Constants.EIGEN5C_EARTH_MU);
1112         CelestialBody sun = CelestialBodyFactory.getSun();
1113         AttitudeProvider defaultLaw = Utils.defaultLaw();
1114 
1115         // Atmosphere
1116         final Atmosphere atmosphere =
1117                         new HarrisPriester(sun,
1118                                            new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
1119                                                                 Constants.WGS84_EARTH_FLATTENING,
1120                                                                 FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
1121 
1122         // Time span drag force model initialization
1123         double dragCd0 = 1.;
1124         double dragCl0 = 0.1;
1125         double dragCd1 = 2.;
1126         double dragCl1 = 0.2;
1127         double dragCd2 = 3.;
1128         double dragCl2 = 0.3;
1129         double dt = 3. * 3600.;
1130 
1131         // Build the force model
1132         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1133                                                                          Vector3D.PLUS_J, dragCd0, dragCl0, 0.7, 0.2);
1134         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1135                                                                          Vector3D.PLUS_J, dragCd1, dragCl1, 0.7, 0.2);
1136         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1137                                                                          Vector3D.PLUS_J, dragCd2, dragCl2, 0.7, 0.2);
1138         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
1139         forceModel.addDragSensitiveValidAfter(box1, date.shiftedBy(dt));
1140         forceModel.addDragSensitiveValidBefore(box2, date.shiftedBy(-dt));
1141 
1142         // Check state derivatives inside first box model
1143         Orbit orbit = refOrbit.shiftedBy(0.);
1144         SpacecraftState state = new SpacecraftState(orbit,
1145                                                     defaultLaw.getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1146         checkStateJacobianVsFiniteDifferences(state, forceModel, defaultLaw, 1.0, 5.0e-6, false);
1147 
1148         // Check state derivatives inside 2nd box model
1149         orbit = refOrbit.shiftedBy(1.1 * dt);
1150         state = new SpacecraftState(orbit,
1151                                     defaultLaw.getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1152         checkStateJacobianVsFiniteDifferences(state, forceModel, defaultLaw, 1.0, 5.0e-6, false);
1153 
1154         // Check state derivatives inside 3rd box model
1155         orbit = refOrbit.shiftedBy(-1.1 * dt);
1156         state = new SpacecraftState(orbit,
1157                                     defaultLaw.getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1158         checkStateJacobianVsFiniteDifferences(state, forceModel, defaultLaw, 1.0, 6.0e-6, false);
1159     }
1160 
1161     /** Test state Jacobian computation using finite differences once again.
1162      * This time the differentiation is made using built-in numerical differentiation method.
1163      */
1164     @Test
1165     public void testJacobianBoxVsFiniteDifferencesGradient()
1166         {
1167 
1168         // initialization
1169         AbsoluteDate date = new AbsoluteDate(new DateComponents(2003, 03, 01),
1170                                              new TimeComponents(13, 59, 27.816),
1171                                              TimeScalesFactory.getUTC());
1172         double i     = FastMath.toRadians(98.7);
1173         double omega = FastMath.toRadians(93.0);
1174         double OMEGA = FastMath.toRadians(15.0 * 22.5);
1175         Orbit refOrbit = new KeplerianOrbit(7201009.7124401, 1e-3, i , omega, OMEGA,
1176                                             0, PositionAngle.MEAN, FramesFactory.getEME2000(), date,
1177                                             Constants.EIGEN5C_EARTH_MU);
1178         CelestialBody sun = CelestialBodyFactory.getSun();
1179         AttitudeProvider defaultLaw = Utils.defaultLaw();
1180 
1181         // Atmosphere
1182         final Atmosphere atmosphere =
1183                         new HarrisPriester(sun,
1184                                            new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
1185                                                                 Constants.WGS84_EARTH_FLATTENING,
1186                                                                 FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
1187 
1188         // Time span drag force model initialization
1189         double dragCd0 = 1.;
1190         double dragCl0 = 0.1;
1191         double dragCd1 = 2.;
1192         double dragCl1 = 0.2;
1193         double dragCd2 = 3.;
1194         double dragCl2 = 0.3;
1195         double dt = 3. * 3600.;
1196 
1197         // Build the force model
1198         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1199                                                                          Vector3D.PLUS_J, dragCd0, dragCl0, 0.7, 0.2);
1200         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1201                                                                          Vector3D.PLUS_J, dragCd1, dragCl1, 0.7, 0.2);
1202         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1203                                                                          Vector3D.PLUS_J, dragCd2, dragCl2, 0.7, 0.2);
1204         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
1205         forceModel.addDragSensitiveValidAfter(box1, date.shiftedBy(dt));
1206         forceModel.addDragSensitiveValidBefore(box2, date.shiftedBy(-dt));
1207 
1208         // Check state derivatives inside first box model
1209         Orbit orbit = refOrbit.shiftedBy(0.);
1210         SpacecraftState state = new SpacecraftState(orbit,
1211                                                     defaultLaw.getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1212         checkStateJacobianVsFiniteDifferencesGradient(state, forceModel, defaultLaw, 1.0, 5.0e-6, false);
1213 
1214         // Check state derivatives inside 2nd box model
1215         orbit = refOrbit.shiftedBy(1.1 * dt);
1216         state = new SpacecraftState(orbit,
1217                                     defaultLaw.getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1218         checkStateJacobianVsFiniteDifferencesGradient(state, forceModel, defaultLaw, 1.0, 5.0e-6, false);
1219 
1220         // Check state derivatives inside 3rd box model
1221         orbit = refOrbit.shiftedBy(-1.1 * dt);
1222         state = new SpacecraftState(orbit,
1223                                     defaultLaw.getAttitude(orbit, orbit.getDate(), orbit.getFrame()));
1224         checkStateJacobianVsFiniteDifferencesGradient(state, forceModel, defaultLaw, 1.0, 6.0e-6, false);
1225     }
1226 
1227     /** Test state Jacobian computation. */
1228     @Test
1229     public void testGlobalStateJacobianBox()
1230         {
1231 
1232         // Initialization
1233         AbsoluteDate date = new AbsoluteDate(new DateComponents(2003, 03, 01),
1234                                              new TimeComponents(13, 59, 27.816),
1235                                              TimeScalesFactory.getUTC());
1236         double i     = FastMath.toRadians(98.7);
1237         double omega = FastMath.toRadians(93.0);
1238         double OMEGA = FastMath.toRadians(15.0 * 22.5);
1239         Orbit orbit = new KeplerianOrbit(7201009.7124401, 1e-3, i , omega, OMEGA,
1240                                          0, PositionAngle.MEAN, FramesFactory.getEME2000(), date,
1241                                          Constants.EIGEN5C_EARTH_MU);
1242         OrbitType integrationType = OrbitType.CARTESIAN;
1243         double[][] tolerances = NumericalPropagator.tolerances(0.01, orbit, integrationType);
1244         CelestialBody sun = CelestialBodyFactory.getSun();
1245 
1246         // Atmosphere
1247         final Atmosphere atmosphere = new HarrisPriester(sun,
1248                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
1249                                                                               Constants.WGS84_EARTH_FLATTENING,
1250                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
1251         // Time span drag force model init
1252         double dragCd0 = 1.;
1253         double dragCl0 = 0.1;
1254         double dragCd1 = 2.;
1255         double dragCl1 = 0.2;
1256         double dragCd2 = 3.;
1257         double dragCl2 = 0.3;
1258         double dt = 1. * 3600.;
1259 
1260         // Build the force model
1261         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1262                                                                          Vector3D.PLUS_J, dragCd0, dragCl0, 0.7, 0.2);
1263         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1264                                                                          Vector3D.PLUS_J, dragCd1, dragCl1, 0.7, 0.2);
1265         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1266                                                                          Vector3D.PLUS_J, dragCd2, dragCl2, 0.7, 0.2);
1267         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
1268         forceModel.addDragSensitiveValidAfter(box1, date.shiftedBy(dt));
1269         forceModel.addDragSensitiveValidBefore(box2, date.shiftedBy(-dt));
1270 
1271         // Check state derivatives inside first box model
1272         NumericalPropagator propagator =
1273                         new NumericalPropagator(new DormandPrince853Integrator(1.0e-3, 120,
1274                                                                                tolerances[0], tolerances[1]));
1275         propagator.setOrbitType(integrationType);
1276         propagator.addForceModel(forceModel);
1277         SpacecraftState state0 = new SpacecraftState(orbit);
1278         // Set target date to 0.5*dt to be inside 1st drag model
1279         // The further away we are from the initial date, the greater the checkTolerance parameter must be set
1280         checkStateJacobian(propagator, state0, date.shiftedBy(0.5 * dt),
1281                            1e3, tolerances[0], 1.1e-9);
1282 
1283         // Check state derivatives inside 2nd box model
1284         propagator = new NumericalPropagator(new DormandPrince853Integrator(1.0e-3, 120,
1285                                                                             tolerances[0], tolerances[1]));
1286         propagator.setOrbitType(integrationType);
1287         propagator.addForceModel(forceModel);
1288         // Set target date to 1.5*dt to be inside 2nd drag model
1289         // The further away we are from the initial date, the greater the checkTolerance parameter must be set
1290         checkStateJacobian(propagator, state0, date.shiftedBy(1.5 * dt),
1291                            1e3, tolerances[0], 9.7e-9);
1292 
1293         // Check state derivatives inside 3rd box model
1294         propagator = new NumericalPropagator(new DormandPrince853Integrator(1.0e-3, 120,
1295                                                                             tolerances[0], tolerances[1]));
1296         propagator.setOrbitType(integrationType);
1297         propagator.addForceModel(forceModel);
1298         // Set target date to *1.5*dt to be inside 3rd drag model
1299         // The further away we are from the initial date, the greater the checkTolerance parameter must be set
1300         checkStateJacobian(propagator, state0, date.shiftedBy(-1.5 * dt),
1301                            1e3, tolerances[0], 4.9e-9);
1302     }
1303 
1304     /** Testing if the propagation between the FieldPropagation and the propagation is equivalent.
1305      * Also testing if propagating X+dX with the propagation is equivalent to
1306      * propagation X with the FieldPropagation and then applying the Taylor
1307      * expansion of dX to the result.*/
1308     @Test
1309     public void RealFieldTest() {
1310         // Initial field Keplerian orbit
1311         // The variables are the six orbital parameters
1312         DSFactory factory = new DSFactory(6, 4);
1313         DerivativeStructure a_0 = factory.variable(0, 7e6);
1314         DerivativeStructure e_0 = factory.variable(1, 0.01);
1315         DerivativeStructure i_0 = factory.variable(2, 1.2);
1316         DerivativeStructure R_0 = factory.variable(3, 0.7);
1317         DerivativeStructure O_0 = factory.variable(4, 0.5);
1318         DerivativeStructure n_0 = factory.variable(5, 0.1);
1319 
1320         Field<DerivativeStructure> field = a_0.getField();
1321         DerivativeStructure zero = field.getZero();
1322 
1323         // Initial date = J2000 epoch
1324         FieldAbsoluteDate<DerivativeStructure> J2000 = new FieldAbsoluteDate<>(field);
1325 
1326         // J2000 frame
1327         Frame EME = FramesFactory.getEME2000();
1328 
1329         // Create initial field Keplerian orbit
1330         FieldKeplerianOrbit<DerivativeStructure> FKO = new FieldKeplerianOrbit<>(a_0, e_0, i_0, R_0, O_0, n_0,
1331                                                                                  PositionAngle.MEAN,
1332                                                                                  EME,
1333                                                                                  J2000,
1334                                                                                  zero.add(Constants.EIGEN5C_EARTH_MU));
1335 
1336         // Initial field and classical S/Cs
1337         FieldSpacecraftState<DerivativeStructure> initialState = new FieldSpacecraftState<>(FKO);
1338         SpacecraftState iSR = initialState.toSpacecraftState();
1339 
1340         // Field integrator and classical integrator
1341         ClassicalRungeKuttaFieldIntegrator<DerivativeStructure> integrator =
1342                         new ClassicalRungeKuttaFieldIntegrator<>(field, zero.add(6));
1343         ClassicalRungeKuttaIntegrator RIntegrator =
1344                         new ClassicalRungeKuttaIntegrator(6);
1345         OrbitType type = OrbitType.EQUINOCTIAL;
1346 
1347         // Field and classical numerical propagators
1348         FieldNumericalPropagator<DerivativeStructure> FNP = new FieldNumericalPropagator<>(field, integrator);
1349         FNP.setOrbitType(type);
1350         FNP.setInitialState(initialState);
1351 
1352         NumericalPropagator NP = new NumericalPropagator(RIntegrator);
1353         NP.setOrbitType(type);
1354         NP.setInitialState(iSR);
1355 
1356 
1357         // Set up force model
1358         CelestialBody sun = CelestialBodyFactory.getSun();
1359 
1360         // Atmosphere
1361         final Atmosphere atmosphere = new HarrisPriester(sun,
1362                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
1363                                                                               Constants.WGS84_EARTH_FLATTENING,
1364                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
1365         // Time span drag force model init
1366         double dragCd0 = 1.;
1367         double dragCl0 = 0.1;
1368         double dragCd1 = 2.;
1369         double dragCl1 = 0.2;
1370         double dragCd2 = 3.;
1371         double dragCl2 = 0.3;
1372         double dt = 1000.;
1373 
1374         // Build the force model
1375         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1376                                                                          Vector3D.PLUS_J, dragCd0, dragCl0, 0.7, 0.2);
1377         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1378                                                                          Vector3D.PLUS_J, dragCd1, dragCl1, 0.7, 0.2);
1379         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1380                                                                          Vector3D.PLUS_J, dragCd2, dragCl2, 0.7, 0.2);
1381         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
1382         forceModel.addDragSensitiveValidAfter(box1, J2000.toAbsoluteDate().shiftedBy(dt));
1383         forceModel.addDragSensitiveValidBefore(box2, J2000.toAbsoluteDate().shiftedBy(-dt));
1384         FNP.addForceModel(forceModel);
1385         NP.addForceModel(forceModel);
1386 
1387         // Do the test
1388         // -----------
1389 
1390         // Propagate inside 1st drag model
1391         checkRealFieldPropagation(FKO, PositionAngle.MEAN, 0.9 * dt, NP, FNP,
1392                                   1.0e-30, 6.0e-09, 2.0e-10, 5.0e-11,
1393                                   1, false);
1394 
1395         // Propagate to 2nd drag model (reset propagator first)
1396         FNP.resetInitialState(initialState);
1397         NP.resetInitialState(iSR);
1398         checkRealFieldPropagation(FKO, PositionAngle.MEAN, 1.1 * dt, NP, FNP,
1399                                   1.0e-30, 2.0e-08, 8.0e-11, 1.0e-10,
1400                                   1, false);
1401 
1402         // Propagate to 3rd drag model  (reset propagator first)
1403         FNP.resetInitialState(initialState);
1404         NP.resetInitialState(iSR);
1405         checkRealFieldPropagation(FKO, PositionAngle.MEAN, -1.1 * dt, NP, FNP,
1406                                   1.0e-15, 2.0e-08, 2.0e-09, 2.0e-09,
1407                                   1, false);
1408     }
1409 
1410     /** Testing if the propagation between the FieldPropagation and the propagation is equivalent.
1411      * Also testing if propagating X+dX with the propagation is equivalent to
1412      * propagation X with the FieldPropagation and then applying the Taylor
1413      * expansion of dX to the result.*/
1414     @Test
1415     public void RealFieldGradientTest() {
1416         // Initial field Keplerian orbit
1417         // The variables are the six orbital parameters
1418         final int freeParameters = 6;
1419         Gradient a_0 = Gradient.variable(freeParameters, 0, 7e6);
1420         Gradient e_0 = Gradient.variable(freeParameters, 1, 0.01);
1421         Gradient i_0 = Gradient.variable(freeParameters, 2, 1.2);
1422         Gradient R_0 = Gradient.variable(freeParameters, 3, 0.7);
1423         Gradient O_0 = Gradient.variable(freeParameters, 4, 0.5);
1424         Gradient n_0 = Gradient.variable(freeParameters, 5, 0.1);
1425 
1426         Field<Gradient> field = a_0.getField();
1427         Gradient zero = field.getZero();
1428 
1429         // Initial date = J2000 epoch
1430         FieldAbsoluteDate<Gradient> J2000 = new FieldAbsoluteDate<>(field);
1431 
1432         // J2000 frame
1433         Frame EME = FramesFactory.getEME2000();
1434 
1435         // Create initial field Keplerian orbit
1436         FieldKeplerianOrbit<Gradient> FKO = new FieldKeplerianOrbit<>(a_0, e_0, i_0, R_0, O_0, n_0,
1437                                                                       PositionAngle.MEAN,
1438                                                                       EME,
1439                                                                       J2000,
1440                                                                       zero.add(Constants.EIGEN5C_EARTH_MU));
1441 
1442         // Initial field and classical S/Cs
1443         FieldSpacecraftState<Gradient> initialState = new FieldSpacecraftState<>(FKO);
1444         SpacecraftState iSR = initialState.toSpacecraftState();
1445 
1446         // Field integrator and classical integrator
1447         ClassicalRungeKuttaFieldIntegrator<Gradient> integrator =
1448                         new ClassicalRungeKuttaFieldIntegrator<>(field, zero.add(6));
1449         ClassicalRungeKuttaIntegrator RIntegrator =
1450                         new ClassicalRungeKuttaIntegrator(6);
1451         OrbitType type = OrbitType.EQUINOCTIAL;
1452 
1453         // Field and classical numerical propagators
1454         FieldNumericalPropagator<Gradient> FNP = new FieldNumericalPropagator<>(field, integrator);
1455         FNP.setOrbitType(type);
1456         FNP.setInitialState(initialState);
1457 
1458         NumericalPropagator NP = new NumericalPropagator(RIntegrator);
1459         NP.setOrbitType(type);
1460         NP.setInitialState(iSR);
1461 
1462 
1463         // Set up force model
1464         CelestialBody sun = CelestialBodyFactory.getSun();
1465 
1466         // Atmosphere
1467         final Atmosphere atmosphere = new HarrisPriester(sun,
1468                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
1469                                                                               Constants.WGS84_EARTH_FLATTENING,
1470                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
1471         // Time span drag force model init
1472         double dragCd0 = 1.;
1473         double dragCl0 = 0.1;
1474         double dragCd1 = 2.;
1475         double dragCl1 = 0.2;
1476         double dragCd2 = 3.;
1477         double dragCl2 = 0.3;
1478         double dt = 1000.;
1479 
1480         // Build the force model
1481         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1482                                                                          Vector3D.PLUS_J, dragCd0, dragCl0, 0.7, 0.2);
1483         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1484                                                                          Vector3D.PLUS_J, dragCd1, dragCl1, 0.7, 0.2);
1485         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1486                                                                          Vector3D.PLUS_J, dragCd2, dragCl2, 0.7, 0.2);
1487         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
1488         forceModel.addDragSensitiveValidAfter(box1, J2000.toAbsoluteDate().shiftedBy(dt));
1489         forceModel.addDragSensitiveValidBefore(box2, J2000.toAbsoluteDate().shiftedBy(-dt));
1490         FNP.addForceModel(forceModel);
1491         NP.addForceModel(forceModel);
1492 
1493         // Do the test
1494         // -----------
1495 
1496         // Propagate inside 1st drag model
1497         checkRealFieldPropagationGradient(FKO, PositionAngle.MEAN, 0.9 * dt, NP, FNP,
1498                                   1.0e-30, 2.5e-02, 7.7e-2, 1.9e-4,
1499                                   1, false);
1500 
1501         // Propagate to 2nd drag model (reset propagator first)
1502         FNP.resetInitialState(initialState);
1503         NP.resetInitialState(iSR);
1504         checkRealFieldPropagationGradient(FKO, PositionAngle.MEAN, 1.1 * dt, NP, FNP,
1505                                   1.0e-30, 4.4e-02, 7.6e-5, 4.1e-4,
1506                                   1, false);
1507 
1508         // Propagate to 3rd drag model  (reset propagator first)
1509         FNP.resetInitialState(initialState);
1510         NP.resetInitialState(iSR);
1511         checkRealFieldPropagationGradient(FKO, PositionAngle.MEAN, -1.1 * dt, NP, FNP,
1512                                   1.0e-8, 2.4e-02, 2.3e-04, 3.9e-04,
1513                                   1, false);
1514     }
1515 
1516     /**Same test as the previous one but not adding the ForceModel to the NumericalPropagator
1517     it is a test to validate the previous test.
1518     (to test if the ForceModel it's actually
1519     doing something in the Propagator and the FieldPropagator)*/
1520     @Test
1521     public void RealFieldExpectErrorTest() {
1522         // Initial field Keplerian orbit
1523         // The variables are the six orbital parameters
1524         DSFactory factory = new DSFactory(6, 4);
1525         DerivativeStructure a_0 = factory.variable(0, 7e6);
1526         DerivativeStructure e_0 = factory.variable(1, 0.01);
1527         DerivativeStructure i_0 = factory.variable(2, 1.2);
1528         DerivativeStructure R_0 = factory.variable(3, 0.7);
1529         DerivativeStructure O_0 = factory.variable(4, 0.5);
1530         DerivativeStructure n_0 = factory.variable(5, 0.1);
1531 
1532         Field<DerivativeStructure> field = a_0.getField();
1533         DerivativeStructure zero = field.getZero();
1534 
1535         // Initial date = J2000 epoch
1536         FieldAbsoluteDate<DerivativeStructure> J2000 = new FieldAbsoluteDate<>(field);
1537 
1538         // J2000 frame
1539         Frame EME = FramesFactory.getEME2000();
1540 
1541         // Create initial field Keplerian orbit
1542         FieldKeplerianOrbit<DerivativeStructure> FKO = new FieldKeplerianOrbit<>(a_0, e_0, i_0, R_0, O_0, n_0,
1543                                                                                  PositionAngle.MEAN,
1544                                                                                  EME,
1545                                                                                  J2000,
1546                                                                                  zero.add(Constants.EIGEN5C_EARTH_MU));
1547 
1548         // Initial field and classical S/Cs
1549         FieldSpacecraftState<DerivativeStructure> initialState = new FieldSpacecraftState<>(FKO);
1550         SpacecraftState iSR = initialState.toSpacecraftState();
1551 
1552         // Field integrator and classical integrator
1553         ClassicalRungeKuttaFieldIntegrator<DerivativeStructure> integrator =
1554                         new ClassicalRungeKuttaFieldIntegrator<>(field, zero.add(6));
1555         ClassicalRungeKuttaIntegrator RIntegrator =
1556                         new ClassicalRungeKuttaIntegrator(6);
1557         OrbitType type = OrbitType.EQUINOCTIAL;
1558 
1559         // Field and classical numerical propagators
1560         FieldNumericalPropagator<DerivativeStructure> FNP = new FieldNumericalPropagator<>(field, integrator);
1561         FNP.setOrbitType(type);
1562         FNP.setInitialState(initialState);
1563 
1564         NumericalPropagator NP = new NumericalPropagator(RIntegrator);
1565         NP.setOrbitType(type);
1566         NP.setInitialState(iSR);
1567 
1568 
1569         // Set up force model
1570         CelestialBody sun = CelestialBodyFactory.getSun();
1571 
1572         // Atmosphere
1573         final Atmosphere atmosphere = new HarrisPriester(sun,
1574                                                          new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
1575                                                                               Constants.WGS84_EARTH_FLATTENING,
1576                                                                               FramesFactory.getITRF(IERSConventions.IERS_2010, true)));
1577         // Time span drag force model init
1578         double dragCd0 = 1.;
1579         double dragCl0 = 0.1;
1580         double dragCd1 = 2.;
1581         double dragCl1 = 0.2;
1582         double dragCd2 = 3.;
1583         double dragCl2 = 0.3;
1584         double dt = 1. * 1100.;
1585 
1586         // Build the force model
1587         BoxAndSolarArraySpacecraft box0 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1588                                                                          Vector3D.PLUS_J, dragCd0, dragCl0, 0.7, 0.2);
1589         BoxAndSolarArraySpacecraft box1 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1590                                                                          Vector3D.PLUS_J, dragCd1, dragCl1, 0.7, 0.2);
1591         BoxAndSolarArraySpacecraft box2 = new BoxAndSolarArraySpacecraft(1.5, 2.0, 1.8, sun, 20.0,
1592                                                                          Vector3D.PLUS_J, dragCd2, dragCl2, 0.7, 0.2);
1593         TimeSpanDragForce forceModel = new TimeSpanDragForce(atmosphere, box0);
1594         forceModel.addDragSensitiveValidAfter(box1, J2000.toAbsoluteDate().shiftedBy(dt));
1595         forceModel.addDragSensitiveValidBefore(box2, J2000.toAbsoluteDate().shiftedBy(-dt));
1596         FNP.addForceModel(forceModel);
1597         // NOT ADDING THE FORCE MODEL TO THE NUMERICAL PROPAGATOR   NP.addForceModel(forceModel);
1598 
1599         FieldAbsoluteDate<DerivativeStructure> target = J2000.shiftedBy(100.);
1600         FieldSpacecraftState<DerivativeStructure> finalState_DS = FNP.propagate(target);
1601         SpacecraftState finalState_R = NP.propagate(target.toAbsoluteDate());
1602         FieldPVCoordinates<DerivativeStructure> finPVC_DS = finalState_DS.getPVCoordinates();
1603         PVCoordinates finPVC_R = finalState_R.getPVCoordinates();
1604 
1605         Assertions.assertFalse(FastMath.abs(finPVC_DS.toPVCoordinates().getPosition().getX() - finPVC_R.getPosition().getX()) < FastMath.abs(finPVC_R.getPosition().getX()) * 1e-11);
1606         Assertions.assertFalse(FastMath.abs(finPVC_DS.toPVCoordinates().getPosition().getY() - finPVC_R.getPosition().getY()) < FastMath.abs(finPVC_R.getPosition().getY()) * 1e-11);
1607         Assertions.assertFalse(FastMath.abs(finPVC_DS.toPVCoordinates().getPosition().getZ() - finPVC_R.getPosition().getZ()) < FastMath.abs(finPVC_R.getPosition().getZ()) * 1e-11);
1608     }
1609 
1610     @BeforeEach
1611     public void setUp() {
1612         Utils.setDataRoot("regular-data");
1613         utc = TimeScalesFactory.getUTC();
1614     }
1615 }