1   /* Copyright 2002-2017 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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;
18  
19  
20  import java.util.List;
21  
22  import org.hipparchus.Field;
23  import org.hipparchus.analysis.differentiation.DSFactory;
24  import org.hipparchus.analysis.differentiation.DerivativeStructure;
25  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
26  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
27  import org.hipparchus.geometry.euclidean.threed.Rotation;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.hipparchus.util.Decimal64;
30  import org.hipparchus.util.Decimal64Field;
31  import org.hipparchus.util.FastMath;
32  import org.hipparchus.util.Precision;
33  import org.junit.Assert;
34  import org.junit.Before;
35  import org.junit.Test;
36  import org.orekit.OrekitMatchers;
37  import org.orekit.Utils;
38  import org.orekit.attitudes.LofOffset;
39  import org.orekit.bodies.CelestialBody;
40  import org.orekit.bodies.CelestialBodyFactory;
41  import org.orekit.errors.OrekitException;
42  import org.orekit.errors.OrekitMessages;
43  import org.orekit.forces.drag.DragSensitive;
44  import org.orekit.forces.radiation.RadiationSensitive;
45  import org.orekit.frames.Frame;
46  import org.orekit.frames.FramesFactory;
47  import org.orekit.frames.LOFType;
48  import org.orekit.orbits.CircularOrbit;
49  import org.orekit.orbits.Orbit;
50  import org.orekit.orbits.PositionAngle;
51  import org.orekit.propagation.Propagator;
52  import org.orekit.propagation.SpacecraftState;
53  import org.orekit.propagation.analytical.EcksteinHechlerPropagator;
54  import org.orekit.time.AbsoluteDate;
55  import org.orekit.time.DateComponents;
56  import org.orekit.time.FieldAbsoluteDate;
57  import org.orekit.time.TimeComponents;
58  import org.orekit.time.TimeScalesFactory;
59  import org.orekit.utils.ParameterDriver;
60  import org.orekit.utils.TimeStampedPVCoordinates;
61  
62  public class BoxAndSolarArraySpacecraftTest {
63  
64      @Test
65      public void testParametersDrivers() throws OrekitException {
66          
67          CelestialBody sun = CelestialBodyFactory.getSun();
68          BoxAndSolarArraySpacecraft.Facet[] facets = new BoxAndSolarArraySpacecraft.Facet[] {
69              new BoxAndSolarArraySpacecraft.Facet(Vector3D.MINUS_I, 3.0),
70              new BoxAndSolarArraySpacecraft.Facet(Vector3D.PLUS_I,  3.0),
71              new BoxAndSolarArraySpacecraft.Facet(Vector3D.MINUS_J, 3.0),
72              new BoxAndSolarArraySpacecraft.Facet(Vector3D.PLUS_J,  3.0),
73              new BoxAndSolarArraySpacecraft.Facet(Vector3D.MINUS_K, 3.0),
74              new BoxAndSolarArraySpacecraft.Facet(Vector3D.PLUS_K,  3.0)
75          };
76  
77          BoxAndSolarArraySpacecraft s1 =
78                          new BoxAndSolarArraySpacecraft(1.5, 3.5, 2.5, sun, 20.0, Vector3D.PLUS_J, 2.0, 0.8, 0.1);
79          Assert.assertEquals(1, s1.getDragParametersDrivers().length);
80          Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, s1.getDragParametersDrivers()[0].getName());
81          Assert.assertEquals(2.0, s1.getDragParametersDrivers()[0].getValue(), 1.0e-15);
82          Assert.assertEquals(2, s1.getRadiationParametersDrivers().length);
83          Assert.assertEquals(RadiationSensitive.ABSORPTION_COEFFICIENT, s1.getRadiationParametersDrivers()[0].getName());
84          Assert.assertEquals(0.8, s1.getRadiationParametersDrivers()[0].getValue(), 1.0e-15);
85          Assert.assertEquals(RadiationSensitive.REFLECTION_COEFFICIENT, s1.getRadiationParametersDrivers()[1].getName());
86          Assert.assertEquals(0.1, s1.getRadiationParametersDrivers()[1].getValue(), 1.0e-15);
87  
88          BoxAndSolarArraySpacecraft s2 =
89                          new BoxAndSolarArraySpacecraft(1.5, 3.5, 2.5, sun, 20.0, Vector3D.PLUS_J, 2.0, 0.4, 0.8, 0.1);
90          Assert.assertEquals(2, s2.getDragParametersDrivers().length);
91          Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, s2.getDragParametersDrivers()[0].getName());
92          Assert.assertEquals(2.0, s2.getDragParametersDrivers()[0].getValue(), 1.0e-15);
93          Assert.assertEquals(DragSensitive.LIFT_RATIO, s2.getDragParametersDrivers()[1].getName());
94          Assert.assertEquals(0.4, s2.getDragParametersDrivers()[1].getValue(), 1.0e-15);
95          Assert.assertEquals(2, s2.getRadiationParametersDrivers().length);
96          Assert.assertEquals(RadiationSensitive.ABSORPTION_COEFFICIENT, s2.getRadiationParametersDrivers()[0].getName());
97          Assert.assertEquals(0.8, s2.getRadiationParametersDrivers()[0].getValue(), 1.0e-15);
98          Assert.assertEquals(RadiationSensitive.REFLECTION_COEFFICIENT, s2.getRadiationParametersDrivers()[1].getName());
99          Assert.assertEquals(0.1, s2.getRadiationParametersDrivers()[1].getValue(), 1.0e-15);
100 
101         BoxAndSolarArraySpacecraft s3 =
102                         new BoxAndSolarArraySpacecraft(facets, sun, 20.0, Vector3D.PLUS_J, 2.0, 0.8, 0.1);
103         Assert.assertEquals(1, s3.getDragParametersDrivers().length);
104         Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, s3.getDragParametersDrivers()[0].getName());
105         Assert.assertEquals(2.0, s3.getDragParametersDrivers()[0].getValue(), 1.0e-15);
106         Assert.assertEquals(2, s3.getRadiationParametersDrivers().length);
107         Assert.assertEquals(RadiationSensitive.ABSORPTION_COEFFICIENT, s3.getRadiationParametersDrivers()[0].getName());
108         Assert.assertEquals(0.8, s3.getRadiationParametersDrivers()[0].getValue(), 1.0e-15);
109         Assert.assertEquals(RadiationSensitive.REFLECTION_COEFFICIENT, s3.getRadiationParametersDrivers()[1].getName());
110         Assert.assertEquals(0.1, s3.getRadiationParametersDrivers()[1].getValue(), 1.0e-15);
111 
112         BoxAndSolarArraySpacecraft s4 =
113                         new BoxAndSolarArraySpacecraft(facets, sun, 20.0, Vector3D.PLUS_J, 2.0, 0.4, 0.8, 0.1);
114         Assert.assertEquals(2, s4.getDragParametersDrivers().length);
115         Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, s4.getDragParametersDrivers()[0].getName());
116         Assert.assertEquals(2.0, s4.getDragParametersDrivers()[0].getValue(), 1.0e-15);
117         Assert.assertEquals(DragSensitive.LIFT_RATIO, s4.getDragParametersDrivers()[1].getName());
118         Assert.assertEquals(0.4, s4.getDragParametersDrivers()[1].getValue(), 1.0e-15);
119         Assert.assertEquals(2, s4.getRadiationParametersDrivers().length);
120         Assert.assertEquals(RadiationSensitive.ABSORPTION_COEFFICIENT, s4.getRadiationParametersDrivers()[0].getName());
121         Assert.assertEquals(0.8, s4.getRadiationParametersDrivers()[0].getValue(), 1.0e-15);
122         Assert.assertEquals(RadiationSensitive.REFLECTION_COEFFICIENT, s4.getRadiationParametersDrivers()[1].getName());
123         Assert.assertEquals(0.1, s4.getRadiationParametersDrivers()[1].getValue(), 1.0e-15);
124 
125         BoxAndSolarArraySpacecraft s5 =
126                         new BoxAndSolarArraySpacecraft(1.5, 3.5, 2.5, sun, 20.0, Vector3D.PLUS_J,
127                                                        AbsoluteDate.J2000_EPOCH, Vector3D.PLUS_I, 7.292e-5,
128                                                        2.0, 0.8, 0.1);
129         Assert.assertEquals(1, s5.getDragParametersDrivers().length);
130         Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, s5.getDragParametersDrivers()[0].getName());
131         Assert.assertEquals(2.0, s5.getDragParametersDrivers()[0].getValue(), 1.0e-15);
132         Assert.assertEquals(2, s5.getRadiationParametersDrivers().length);
133         Assert.assertEquals(RadiationSensitive.ABSORPTION_COEFFICIENT, s5.getRadiationParametersDrivers()[0].getName());
134         Assert.assertEquals(0.8, s5.getRadiationParametersDrivers()[0].getValue(), 1.0e-15);
135         Assert.assertEquals(RadiationSensitive.REFLECTION_COEFFICIENT, s5.getRadiationParametersDrivers()[1].getName());
136         Assert.assertEquals(0.1, s5.getRadiationParametersDrivers()[1].getValue(), 1.0e-15);
137 
138         BoxAndSolarArraySpacecraft s6 =
139                         new BoxAndSolarArraySpacecraft(1.5, 3.5, 2.5, sun, 20.0, Vector3D.PLUS_J,
140                                                        AbsoluteDate.J2000_EPOCH, Vector3D.PLUS_I, 7.292e-5,
141                                                        2.0, 0.4, 0.8, 0.1);
142         Assert.assertEquals(2, s6.getDragParametersDrivers().length);
143         Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, s6.getDragParametersDrivers()[0].getName());
144         Assert.assertEquals(2.0, s6.getDragParametersDrivers()[0].getValue(), 1.0e-15);
145         Assert.assertEquals(DragSensitive.LIFT_RATIO, s6.getDragParametersDrivers()[1].getName());
146         Assert.assertEquals(0.4, s6.getDragParametersDrivers()[1].getValue(), 1.0e-15);
147         Assert.assertEquals(2, s6.getRadiationParametersDrivers().length);
148         Assert.assertEquals(RadiationSensitive.ABSORPTION_COEFFICIENT, s6.getRadiationParametersDrivers()[0].getName());
149         Assert.assertEquals(0.8, s6.getRadiationParametersDrivers()[0].getValue(), 1.0e-15);
150         Assert.assertEquals(RadiationSensitive.REFLECTION_COEFFICIENT, s6.getRadiationParametersDrivers()[1].getName());
151         Assert.assertEquals(0.1, s6.getRadiationParametersDrivers()[1].getValue(), 1.0e-15);
152 
153         BoxAndSolarArraySpacecraft s7 =
154                         new BoxAndSolarArraySpacecraft(facets, sun, 20.0, Vector3D.PLUS_J,
155                                                        AbsoluteDate.J2000_EPOCH, Vector3D.PLUS_I, 7.292e-5,
156                                                        2.0, 0.8, 0.1);
157         Assert.assertEquals(1, s7.getDragParametersDrivers().length);
158         Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, s7.getDragParametersDrivers()[0].getName());
159         Assert.assertEquals(2.0, s7.getDragParametersDrivers()[0].getValue(), 1.0e-15);
160         Assert.assertEquals(2, s7.getRadiationParametersDrivers().length);
161         Assert.assertEquals(RadiationSensitive.ABSORPTION_COEFFICIENT, s7.getRadiationParametersDrivers()[0].getName());
162         Assert.assertEquals(0.8, s7.getRadiationParametersDrivers()[0].getValue(), 1.0e-15);
163         Assert.assertEquals(RadiationSensitive.REFLECTION_COEFFICIENT, s7.getRadiationParametersDrivers()[1].getName());
164         Assert.assertEquals(0.1, s7.getRadiationParametersDrivers()[1].getValue(), 1.0e-15);
165 
166         BoxAndSolarArraySpacecraft s8 =
167                         new BoxAndSolarArraySpacecraft(facets, sun, 20.0, Vector3D.PLUS_J,
168                                                        AbsoluteDate.J2000_EPOCH, Vector3D.PLUS_I, 7.292e-5,
169                                                        2.0, 0.4, 0.8, 0.1);
170         Assert.assertEquals(2, s8.getDragParametersDrivers().length);
171         Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, s8.getDragParametersDrivers()[0].getName());
172         Assert.assertEquals(2.0, s8.getDragParametersDrivers()[0].getValue(), 1.0e-15);
173         Assert.assertEquals(DragSensitive.LIFT_RATIO, s8.getDragParametersDrivers()[1].getName());
174         Assert.assertEquals(0.4, s8.getDragParametersDrivers()[1].getValue(), 1.0e-15);
175         Assert.assertEquals(2, s8.getRadiationParametersDrivers().length);
176         Assert.assertEquals(RadiationSensitive.ABSORPTION_COEFFICIENT, s8.getRadiationParametersDrivers()[0].getName());
177         Assert.assertEquals(0.8, s8.getRadiationParametersDrivers()[0].getValue(), 1.0e-15);
178         Assert.assertEquals(RadiationSensitive.REFLECTION_COEFFICIENT, s8.getRadiationParametersDrivers()[1].getName());
179         Assert.assertEquals(0.1, s8.getRadiationParametersDrivers()[1].getValue(), 1.0e-15);
180 
181     }
182 
183     @Test
184     public void testBestPointing() throws OrekitException {
185 
186         AbsoluteDate initialDate = propagator.getInitialState().getDate();
187         CelestialBody sun = CelestialBodyFactory.getSun();
188         BoxAndSolarArraySpacecraft s =
189             new BoxAndSolarArraySpacecraft(1.5, 3.5, 2.5, sun, 20.0, Vector3D.PLUS_J, 0.0, 0.0, 0.0);
190         for (double dt = 0; dt < 4000; dt += 60) {
191 
192             SpacecraftState state = propagator.propagate(initialDate.shiftedBy(dt));
193 
194             Vector3D sunInert = sun.getPVCoordinates(initialDate, state.getFrame()).getPosition();
195             Vector3D momentum = state.getPVCoordinates().getMomentum();
196             double sunElevation = FastMath.PI / 2 - Vector3D.angle(sunInert, momentum);
197             Assert.assertEquals(15.1, FastMath.toDegrees(sunElevation), 0.1);
198 
199             Vector3D n = s.getNormal(state.getDate(), state.getFrame(),
200                                      state.getPVCoordinates().getPosition(),
201                                      state.getAttitude().getRotation());
202             Assert.assertEquals(0.0, n.getY(), 1.0e-10);
203 
204             // normal misalignment should be entirely due to sun being out of orbital plane
205             Vector3D sunSat = state.getAttitude().getRotation().applyTo(sunInert);
206             double misAlignment = Vector3D.angle(sunSat, n);
207             Assert.assertEquals(sunElevation, misAlignment, 1.0e-3);
208 
209         }
210     }
211 
212     @Test
213     public void testCorrectFixedRate() throws OrekitException {
214 
215         AbsoluteDate initialDate = propagator.getInitialState().getDate();
216         CelestialBody sun = CelestialBodyFactory.getSun();
217         BoxAndSolarArraySpacecraft s =
218             new BoxAndSolarArraySpacecraft(1.5, 3.5, 2.5, sun, 20.0, Vector3D.PLUS_J,
219                                            initialDate,
220                                            new Vector3D(0.46565509814462996, 0.0,  0.884966287251619),
221                                            propagator.getInitialState().getKeplerianMeanMotion(),
222                                            0.0, 0.0, 0.0);
223 
224         for (double dt = 0; dt < 4000; dt += 60) {
225 
226             SpacecraftState state = propagator.propagate(initialDate.shiftedBy(dt));
227 
228             Vector3D sunInert = sun.getPVCoordinates(initialDate, state.getFrame()).getPosition();
229             Vector3D momentum = state.getPVCoordinates().getMomentum();
230             double sunElevation = FastMath.PI / 2 - Vector3D.angle(sunInert, momentum);
231             Assert.assertEquals(15.1, FastMath.toDegrees(sunElevation), 0.1);
232 
233             Vector3D n = s.getNormal(state.getDate(), state.getFrame(),
234                                      state.getPVCoordinates().getPosition(),
235                                      state.getAttitude().getRotation());
236             Assert.assertEquals(0.0, n.getY(), 1.0e-10);
237 
238             // normal misalignment should be entirely due to sun being out of orbital plane
239             Vector3D sunSat = state.getAttitude().getRotation().applyTo(sunInert);
240             double misAlignment = Vector3D.angle(sunSat, n);
241             Assert.assertEquals(sunElevation, misAlignment, 1.0e-3);
242 
243         }
244     }
245 
246     @Test
247     public void testTooSlowFixedRate() throws OrekitException {
248 
249             AbsoluteDate initialDate = propagator.getInitialState().getDate();
250             CelestialBody sun = CelestialBodyFactory.getSun();
251             BoxAndSolarArraySpacecraft s =
252                 new BoxAndSolarArraySpacecraft(1.5, 3.5, 2.5, sun, 20.0, Vector3D.PLUS_J,
253                                                initialDate,
254                                                new Vector3D(0.46565509814462996, 0.0,  0.884966287251619),
255                                                0.1 * propagator.getInitialState().getKeplerianMeanMotion(),
256                                                0.0, 0.0, 0.0);
257 
258             double maxDelta = 0;
259             for (double dt = 0; dt < 4000; dt += 60) {
260 
261                 SpacecraftState state = propagator.propagate(initialDate.shiftedBy(dt));
262 
263                 Vector3D sunInert = sun.getPVCoordinates(initialDate, state.getFrame()).getPosition();
264                 Vector3D momentum = state.getPVCoordinates().getMomentum();
265                 double sunElevation = FastMath.PI / 2 - Vector3D.angle(sunInert, momentum);
266                 Assert.assertEquals(15.1, FastMath.toDegrees(sunElevation), 0.1);
267 
268                 Vector3D n = s.getNormal(state.getDate(), state.getFrame(),
269                                          state.getPVCoordinates().getPosition(),
270                                          state.getAttitude().getRotation());
271                 Assert.assertEquals(0.0, n.getY(), 1.0e-10);
272 
273                 // normal misalignment should become very large as solar array rotation is plain wrong
274                 Vector3D sunSat = state.getAttitude().getRotation().applyTo(sunInert);
275                 double misAlignment = Vector3D.angle(sunSat, n);
276                 maxDelta = FastMath.max(maxDelta, FastMath.abs(sunElevation - misAlignment));
277 
278             }
279             Assert.assertTrue(FastMath.toDegrees(maxDelta) > 120.0);
280 
281     }
282 
283     @Test
284     public void testNoLiftWithoutReflection() throws OrekitException {
285 
286         AbsoluteDate initialDate = propagator.getInitialState().getDate();
287         CelestialBody sun = CelestialBodyFactory.getSun();
288         BoxAndSolarArraySpacecraft s =
289             new BoxAndSolarArraySpacecraft(1.5, 3.5, 2.5, sun, 20.0, Vector3D.PLUS_J, 1.0, 0.0, 1.0, 0.0);
290 
291         Vector3D earthRot = new Vector3D(0.0, 0.0, 7.292115e-4);
292         for (double dt = 0; dt < 4000; dt += 60) {
293 
294             AbsoluteDate date = initialDate.shiftedBy(dt);
295             SpacecraftState state = propagator.propagate(date);
296 
297             // simple Earth fixed atmosphere
298             Vector3D p = state.getPVCoordinates().getPosition();
299             Vector3D v = state.getPVCoordinates().getVelocity();
300             Vector3D vAtm = Vector3D.crossProduct(earthRot, p);
301             Vector3D relativeVelocity = vAtm.subtract(v);
302 
303             Vector3D drag = s.dragAcceleration(state.getDate(), state.getFrame(),
304                                                state.getPVCoordinates().getPosition(),
305                                                state.getAttitude().getRotation(),
306                                                state.getMass(), 0.001, relativeVelocity,
307                                                getDragParameters(s));
308             Assert.assertEquals(0.0, Vector3D.angle(relativeVelocity, drag), 1.0e-15);
309 
310             Vector3D sunDirection = sun.getPVCoordinates(date, state.getFrame()).getPosition().normalize();
311             Vector3D flux = new Vector3D(-4.56e-6, sunDirection);
312             Vector3D radiation = s.radiationPressureAcceleration(state.getDate(), state.getFrame(),
313                                                                  state.getPVCoordinates().getPosition(),
314                                                                  state.getAttitude().getRotation(),
315                                                                  state.getMass(), flux,
316                                                                  getRadiationParameters(s));
317             Assert.assertEquals(0.0, Vector3D.angle(flux, radiation), 1.0e-9);
318 
319         }
320 
321     }
322 
323     @Test
324     public void testOnlyLiftWithoutReflection() throws OrekitException {
325 
326         AbsoluteDate initialDate = propagator.getInitialState().getDate();
327         CelestialBody sun = CelestialBodyFactory.getSun();
328         BoxAndSolarArraySpacecraft s =
329             new BoxAndSolarArraySpacecraft(1.5, 3.5, 2.5, sun, 20.0, Vector3D.PLUS_J, 1.0, 1.0, 1.0, 0.0);
330 
331         Vector3D earthRot = new Vector3D(0.0, 0.0, 7.292115e-4);
332         for (double dt = 0; dt < 4000; dt += 60) {
333 
334             AbsoluteDate date = initialDate.shiftedBy(dt);
335             SpacecraftState state = propagator.propagate(date);
336 
337             // simple Earth fixed atmosphere
338             Vector3D p = state.getPVCoordinates().getPosition();
339             Vector3D v = state.getPVCoordinates().getVelocity();
340             Vector3D vAtm = Vector3D.crossProduct(earthRot, p);
341             Vector3D relativeVelocity = vAtm.subtract(v);
342 
343             Vector3D drag = s.dragAcceleration(state.getDate(), state.getFrame(),
344                                                state.getPVCoordinates().getPosition(),
345                                                state.getAttitude().getRotation(),
346                                                state.getMass(), 0.001, relativeVelocity,
347                                                getDragParameters(s));
348             Assert.assertTrue(Vector3D.angle(relativeVelocity, drag) > 0.167);
349             Assert.assertTrue(Vector3D.angle(relativeVelocity, drag) < 0.736);
350 
351             Vector3D sunDirection = sun.getPVCoordinates(date, state.getFrame()).getPosition().normalize();
352             Vector3D flux = new Vector3D(-4.56e-6, sunDirection);
353             Vector3D radiation = s.radiationPressureAcceleration(state.getDate(), state.getFrame(),
354                                                                  state.getPVCoordinates().getPosition(),
355                                                                  state.getAttitude().getRotation(),
356                                                                  state.getMass(), flux,
357                                                                  getRadiationParameters(s));
358             Assert.assertEquals(0.0, Vector3D.angle(flux, radiation), 1.0e-9);
359 
360         }
361 
362     }
363 
364     @Test
365     public void testLiftVsNoLift()
366         throws OrekitException, NoSuchFieldException, SecurityException,
367                IllegalArgumentException, IllegalAccessException {
368 
369         CelestialBody sun = CelestialBodyFactory.getSun();
370 
371         // older implementation did not consider lift, so it really worked
372         // only for symmetrical shapes. For testing purposes, we will use a
373         // basic cubic shape without solar arrays and a relative atmosphere
374         // velocity either *exactly* facing a side or *exactly* along a main diagonal
375         BoxAndSolarArraySpacecraft.Facet[] facets = new BoxAndSolarArraySpacecraft.Facet[] {
376             new BoxAndSolarArraySpacecraft.Facet(Vector3D.MINUS_I, 3.0),
377             new BoxAndSolarArraySpacecraft.Facet(Vector3D.PLUS_I,  3.0),
378             new BoxAndSolarArraySpacecraft.Facet(Vector3D.MINUS_J, 3.0),
379             new BoxAndSolarArraySpacecraft.Facet(Vector3D.PLUS_J,  3.0),
380             new BoxAndSolarArraySpacecraft.Facet(Vector3D.MINUS_K, 3.0),
381             new BoxAndSolarArraySpacecraft.Facet(Vector3D.PLUS_K,  3.0)
382         };
383         BoxAndSolarArraySpacecraft cube =
384                         new BoxAndSolarArraySpacecraft(facets, sun, 0.0, Vector3D.PLUS_J, 1.0, 1.0, 1.0, 0.0);
385 
386         AbsoluteDate date = AbsoluteDate.J2000_EPOCH;
387         Frame frame = FramesFactory.getEME2000();
388         Vector3D position = new Vector3D(1234567.8, 9876543.21, 121212.3434);
389         double mass = 1000.0;
390         double density = 0.001;
391         Rotation rotation = Rotation.IDENTITY;
392 
393         // head-on, there acceleration with lift should be twice acceleration without lift
394         Vector3D headOnVelocity = new Vector3D(2000, 0.0, 0.0);
395         Vector3D newHeadOnDrag  = cube.dragAcceleration(date, frame, position, rotation, mass, density, headOnVelocity,
396                                                         getDragParameters(cube));
397         Vector3D oldHeadOnDrag  = oldDragAcceleration(cube, date, frame, position, rotation, mass, density, headOnVelocity);
398         Assert.assertThat(newHeadOnDrag, OrekitMatchers.vectorCloseTo(oldHeadOnDrag.scalarMultiply(2), 1));
399 
400         // on an angle, the no lift implementation applies drag to the velocity direction
401         // instead of to the facet normal direction. In the symmetrical case, this implies
402         // it applied a single cos(θ) coefficient (projected surface reduction) instead
403         // of using cos²(θ) (projected surface reduction *and* normal component projection)
404         // and since molecule is reflected backward with the same velocity, this implies a
405         // factor 2 in linear momentum differences
406         Vector3D diagonalVelocity = new Vector3D(2000, 2000, 2000);
407         Vector3D newDiagDrag= cube.dragAcceleration(date, frame, position, rotation, mass, density, diagonalVelocity,
408                                                     getDragParameters(cube));
409         Vector3D oldDiagDrag = oldDragAcceleration(cube, date, frame, position, rotation, mass, density, diagonalVelocity);
410         double oldMissingCoeff = 2.0 / FastMath.sqrt(3.0);
411         Vector3D fixedOldDrag = new Vector3D(oldMissingCoeff, oldDiagDrag);
412         Assert.assertThat(newDiagDrag, OrekitMatchers.vectorCloseTo(fixedOldDrag, 1));
413 
414     }
415 
416     // this is a slightly adapted version of the pre-9.0 implementation
417     // (changes are only related to retrieve the fields using reflection)
418     // Beware that this implementation is WRONG
419     private Vector3D oldDragAcceleration(final BoxAndSolarArraySpacecraft bsa,
420                                          final AbsoluteDate date, final Frame frame, final Vector3D position,
421                                          final Rotation rotation, final double mass,
422                                          final double density, final Vector3D relativeVelocity)
423          throws OrekitException, IllegalArgumentException, IllegalAccessException,
424                 NoSuchFieldException, SecurityException {
425 
426         java.lang.reflect.Field facetsField = BoxAndSolarArraySpacecraft.class.getDeclaredField("facets");
427         facetsField.setAccessible(true);
428         @SuppressWarnings("unchecked")
429         final List<BoxAndSolarArraySpacecraft.Facet> facets = (List<BoxAndSolarArraySpacecraft.Facet>) facetsField.get(bsa);
430         
431         java.lang.reflect.Field saAreaField = BoxAndSolarArraySpacecraft.class.getDeclaredField("solarArrayArea");
432         saAreaField.setAccessible(true);
433         final double solarArrayArea = (Double) saAreaField.get(bsa);
434         
435         final double dragCoeff = bsa.getDragParametersDrivers()[0].getValue();
436         
437         // relative velocity in spacecraft frame
438         final Vector3D v = rotation.applyTo(relativeVelocity);
439 
440         // solar array contribution
441         final Vector3D solarArrayFacet = new Vector3D(solarArrayArea, bsa.getNormal(date, frame, position, rotation));
442         double sv = FastMath.abs(Vector3D.dotProduct(solarArrayFacet, v));
443 
444         // body facets contribution
445         for (final BoxAndSolarArraySpacecraft.Facet facet : facets) {
446             final double dot = Vector3D.dotProduct(facet.getNormal(), v);
447             if (dot < 0) {
448                 // the facet intercepts the incoming flux
449                 sv -= facet.getArea() * dot;
450             }
451         }
452 
453         return new Vector3D(sv * density * dragCoeff / (2.0 * mass), relativeVelocity);
454 
455     }
456 
457     @Test
458     public void testPlaneSpecularReflection() throws OrekitException {
459 
460         AbsoluteDate initialDate = propagator.getInitialState().getDate();
461         CelestialBody sun = CelestialBodyFactory.getSun();
462         BoxAndSolarArraySpacecraft s =
463             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 0.0, 0.0, 1.0);
464 
465         for (double dt = 0; dt < 4000; dt += 60) {
466 
467             AbsoluteDate date = initialDate.shiftedBy(dt);
468             SpacecraftState state = propagator.propagate(date);
469 
470             Vector3D sunDirection = sun.getPVCoordinates(date, state.getFrame()).getPosition().normalize();
471             Vector3D flux = new Vector3D(-4.56e-6, sunDirection);
472             Vector3D acceleration = s.radiationPressureAcceleration(state.getDate(), state.getFrame(),
473                                                                     state.getPVCoordinates().getPosition(),
474                                                                     state.getAttitude().getRotation(),
475                                                                     state.getMass(), flux,
476                                                                     getRadiationParameters(s));
477             Vector3D normal = state.getAttitude().getRotation().applyInverseTo(s.getNormal(state.getDate(), state.getFrame(),
478                                                                                            state.getPVCoordinates().getPosition(),
479                                                                                            state.getAttitude().getRotation()));
480 
481             // solar array normal is slightly misaligned with Sun direction due to Sun being out of orbital plane
482             Assert.assertEquals(15.1, FastMath.toDegrees(Vector3D.angle(sunDirection, normal)), 0.11);
483 
484             // radiation pressure is exactly opposed to solar array normal as there is only specular reflection
485             Assert.assertEquals(180.0, FastMath.toDegrees(Vector3D.angle(acceleration, normal)), 1.0e-3);
486 
487         }
488 
489     }
490 
491     @Test
492     public void testPlaneAbsorption() throws OrekitException {
493 
494         AbsoluteDate initialDate = propagator.getInitialState().getDate();
495         CelestialBody sun = CelestialBodyFactory.getSun();
496         BoxAndSolarArraySpacecraft s =
497             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
498 
499         for (double dt = 0; dt < 4000; dt += 60) {
500 
501             AbsoluteDate date = initialDate.shiftedBy(dt);
502             SpacecraftState state = propagator.propagate(date);
503 
504             Vector3D sunDirection = sun.getPVCoordinates(date, state.getFrame()).getPosition().normalize();
505             Vector3D flux = new Vector3D(-4.56e-6, sunDirection);
506             Vector3D acceleration =
507                     s.radiationPressureAcceleration(state.getDate(), state.getFrame(),
508                                                     state.getPVCoordinates().getPosition(),
509                                                     state.getAttitude().getRotation(),
510                                                     state.getMass(), flux,
511                                                     getRadiationParameters(s));
512             Vector3D normal = state.getAttitude().getRotation().applyInverseTo(s.getNormal(state.getDate(), state.getFrame(),
513                                                                                            state.getPVCoordinates().getPosition(),
514                                                                                            state.getAttitude().getRotation()));
515 
516             // solar array normal is slightly misaligned with Sun direction due to Sun being out of orbital plane
517             Assert.assertEquals(15.1, FastMath.toDegrees(Vector3D.angle(sunDirection, normal)), 0.11);
518 
519             // radiation pressure is exactly opposed to Sun direction as there is only absorption
520             Assert.assertEquals(180.0, FastMath.toDegrees(Vector3D.angle(acceleration, sunDirection)), 1.0e-3);
521 
522         }
523 
524     }
525 
526     @Test
527     public void testWrongParameterDrag() throws OrekitException {
528         SpacecraftState state = propagator.getInitialState();
529         CelestialBody sun = CelestialBodyFactory.getSun();
530         BoxAndSolarArraySpacecraft s =
531             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
532         try {
533             s.dragAcceleration(state.getDate(), state.getFrame(),
534                                state.getPVCoordinates().getPosition(),
535                                state.getAttitude().getRotation(),
536                                state.getMass(), 1.0e-6, Vector3D.PLUS_I,
537                                getDragParameters(s),
538                                "wrong");
539             Assert.fail("an exception should have been thrown");
540         } catch (OrekitException oe) {
541             Assert.assertEquals(OrekitMessages.UNSUPPORTED_PARAMETER_NAME,
542                                 oe.getSpecifier());
543             Assert.assertEquals("wrong", (String) oe.getParts()[0]);
544         }
545     }
546 
547     @Test
548     public void testMissingParameterLift() throws OrekitException {
549         SpacecraftState state = propagator.getInitialState();
550         CelestialBody sun = CelestialBodyFactory.getSun();
551         BoxAndSolarArraySpacecraft s =
552             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 2.0, 1.0, 0.0);
553         try {
554             s.dragAcceleration(state.getDate(), state.getFrame(),
555                                state.getPVCoordinates().getPosition(),
556                                state.getAttitude().getRotation(),
557                                state.getMass(), 1.0e-6, Vector3D.PLUS_I,
558                                getDragParameters(s),
559                                DragSensitive.LIFT_RATIO);
560             Assert.fail("an exception should have been thrown");
561         } catch (OrekitException oe) {
562             Assert.assertEquals(OrekitMessages.UNSUPPORTED_PARAMETER_NAME,
563                                 oe.getSpecifier());
564             Assert.assertEquals(2, oe.getParts().length);
565             Assert.assertEquals(DragSensitive.LIFT_RATIO, (String) oe.getParts()[0]);
566             Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, (String) oe.getParts()[1]);
567         }
568     }
569 
570     @Test
571     public void testPresentParameterLift() throws OrekitException {
572         SpacecraftState state = propagator.getInitialState();
573         CelestialBody sun = CelestialBodyFactory.getSun();
574         BoxAndSolarArraySpacecraft s =
575             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 2.0, 0.4, 1.0, 0.0);
576         FieldVector3D<DerivativeStructure> a = s.dragAcceleration(state.getDate(), state.getFrame(),
577                                                                   state.getPVCoordinates().getPosition(),
578                                                                   state.getAttitude().getRotation(),
579                                                                   state.getMass(), 1.0e-6, Vector3D.PLUS_I,
580                                                                   getDragParameters(s),
581                                                                   DragSensitive.LIFT_RATIO);
582         Assert.assertEquals(5.58e-10, a.getNorm().getReal(), 1.0e-12);
583     }
584 
585     @Test
586     public void testWrongParameterLift() throws OrekitException {
587         SpacecraftState state = propagator.getInitialState();
588         CelestialBody sun = CelestialBodyFactory.getSun();
589         BoxAndSolarArraySpacecraft s =
590             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 2.0, 0.4, 1.0, 0.0);
591         try {
592             s.dragAcceleration(state.getDate(), state.getFrame(),
593                                state.getPVCoordinates().getPosition(),
594                                state.getAttitude().getRotation(),
595                                state.getMass(), 1.0e-6, Vector3D.PLUS_I,
596                                getDragParameters(s),
597                                "wrong");
598             Assert.fail("an exception should have been thrown");
599         } catch (OrekitException oe) {
600             Assert.assertEquals(OrekitMessages.UNSUPPORTED_PARAMETER_NAME,
601                                 oe.getSpecifier());
602             Assert.assertEquals(3, oe.getParts().length);
603             Assert.assertEquals("wrong", (String) oe.getParts()[0]);
604             Assert.assertEquals(DragSensitive.DRAG_COEFFICIENT, (String) oe.getParts()[1]);
605             Assert.assertEquals(DragSensitive.LIFT_RATIO, (String) oe.getParts()[2]);
606         }
607     }
608 
609     @Test
610     public void testWrongParameterRadiation() throws OrekitException {
611         SpacecraftState state = propagator.getInitialState();
612         CelestialBody sun = CelestialBodyFactory.getSun();
613         BoxAndSolarArraySpacecraft s =
614             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
615         try {
616             s.radiationPressureAcceleration(state.getDate(), state.getFrame(),
617                                             state.getPVCoordinates().getPosition(),
618                                             state.getAttitude().getRotation(),
619                                             state.getMass(), Vector3D.PLUS_I,
620                                             getRadiationParameters(s),
621                                             "wrong");
622             Assert.fail("an exception should have been thrown");
623         } catch (OrekitException oe) {
624             Assert.assertEquals(OrekitMessages.UNSUPPORTED_PARAMETER_NAME,
625                                 oe.getSpecifier());
626             Assert.assertEquals("wrong", (String) oe.getParts()[0]);
627         }
628     }
629 
630     @Test
631     public void testNullIllumination() throws OrekitException {
632         SpacecraftState state = propagator.getInitialState();
633         CelestialBody sun = CelestialBodyFactory.getSun();
634         BoxAndSolarArraySpacecraft s =
635             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
636         FieldVector3D<DerivativeStructure> a =
637                         s.radiationPressureAcceleration(state.getDate(), state.getFrame(),
638                                             state.getPVCoordinates().getPosition(),
639                                             state.getAttitude().getRotation(),
640                                             state.getMass(), new Vector3D(Precision.SAFE_MIN / 2,
641                                                                           Vector3D.PLUS_I),
642                                             getRadiationParameters(s),
643                                             RadiationSensitive.ABSORPTION_COEFFICIENT);
644         Assert.assertEquals(0.0, a.getNorm().getReal(), Double.MIN_VALUE);
645     }
646 
647     @Test
648     public void testBackwardIllumination() throws OrekitException {
649         SpacecraftState state = propagator.getInitialState();
650         CelestialBody sun = CelestialBodyFactory.getSun();
651         BoxAndSolarArraySpacecraft s =
652             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
653         Vector3D n = s.getNormal(state.getDate(), state.getFrame(),
654                                  state.getPVCoordinates().getPosition(),
655                                  state.getAttitude().getRotation());
656         FieldVector3D<DerivativeStructure> aPlus =
657                         s.radiationPressureAcceleration(state.getDate(), state.getFrame(),
658                                             state.getPVCoordinates().getPosition(),
659                                             state.getAttitude().getRotation(),
660                                             state.getMass(), n,
661                                             getRadiationParameters(s),
662                                             RadiationSensitive.ABSORPTION_COEFFICIENT);
663         FieldVector3D<DerivativeStructure> aMinus =
664                         s.radiationPressureAcceleration(state.getDate(), state.getFrame(),
665                                             state.getPVCoordinates().getPosition(),
666                                             state.getAttitude().getRotation(),
667                                             state.getMass(), n.negate(),
668                                             getRadiationParameters(s),
669                                             RadiationSensitive.ABSORPTION_COEFFICIENT);
670         Assert.assertEquals(0.0, aPlus.add(aMinus).getNorm().getReal(), Double.MIN_VALUE);
671     }
672 
673     @Test
674     public void testNormalOptimalRotationDouble() throws OrekitException {
675         AbsoluteDate initialDate = propagator.getInitialState().getDate();
676         CelestialBody sun = CelestialBodyFactory.getSun();
677         BoxAndSolarArraySpacecraft s =
678             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
679         for (double dt = 0; dt < 4000; dt += 60) {
680             AbsoluteDate date = initialDate.shiftedBy(dt);
681             SpacecraftState state = propagator.propagate(date);
682             Vector3D normal = s.getNormal(state.getDate(), state.getFrame(),
683                                             state.getPVCoordinates().getPosition(),
684                                             state.getAttitude().getRotation());
685             Assert.assertEquals(0, Vector3D.dotProduct(normal, Vector3D.PLUS_J), 1.0e-16);
686         }
687     }
688 
689     @Test
690     public void testNormalOptimalRotationField() throws OrekitException {
691         AbsoluteDate initialDate = propagator.getInitialState().getDate();
692         CelestialBody sun = CelestialBodyFactory.getSun();
693         BoxAndSolarArraySpacecraft s =
694             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
695         Field<Decimal64> field = Decimal64Field.getInstance();
696         for (double dt = 0; dt < 4000; dt += 60) {
697             AbsoluteDate date = initialDate.shiftedBy(dt);
698             SpacecraftState state = propagator.propagate(date);
699             FieldVector3D<Decimal64> normal = s.getNormal(new FieldAbsoluteDate<>(field, state.getDate()),
700                                                           state.getFrame(),
701                                                           new FieldVector3D<>(field, state.getPVCoordinates().getPosition()),
702                                                           new FieldRotation<>(field, state.getAttitude().getRotation()));
703             Assert.assertEquals(0, FieldVector3D.dotProduct(normal, Vector3D.PLUS_J).getReal(), 1.0e-16);
704         }
705     }
706 
707     @Test
708     public void testNormalOptimalRotationDS() throws OrekitException {
709         AbsoluteDate initialDate = propagator.getInitialState().getDate();
710         CelestialBody sun = CelestialBodyFactory.getSun();
711         BoxAndSolarArraySpacecraft s =
712             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
713         DSFactory factory = new DSFactory(1, 2);
714         for (double dt = 0; dt < 4000; dt += 60) {
715             AbsoluteDate date = initialDate.shiftedBy(dt);
716             SpacecraftState state = propagator.propagate(date);
717             FieldVector3D<DerivativeStructure> normal = s.getNormal(state.getDate(),
718                                                                     state.getFrame(),
719                                                                     new FieldVector3D<>(factory.getDerivativeField(), state.getPVCoordinates().getPosition()),
720                                                                     new FieldRotation<>(factory.getDerivativeField(), state.getAttitude().getRotation()));
721             Assert.assertEquals(0, FieldVector3D.dotProduct(normal, Vector3D.PLUS_J).getReal(), 1.0e-16);
722         }
723     }
724 
725     @Test
726     public void testNormalFixedRateDouble() throws OrekitException {
727         AbsoluteDate initialDate = propagator.getInitialState().getDate();
728         CelestialBody sun = CelestialBodyFactory.getSun();
729         BoxAndSolarArraySpacecraft s =
730             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J,
731                                            initialDate, Vector3D.PLUS_K, 1.0e-3,
732                                            0.0, 1.0, 0.0);
733         for (double dt = 0; dt < 4000; dt += 60) {
734             AbsoluteDate date = initialDate.shiftedBy(dt);
735             SpacecraftState state = propagator.propagate(date);
736             Vector3D normal = s.getNormal(state.getDate(), state.getFrame(),
737                                             state.getPVCoordinates().getPosition(),
738                                             state.getAttitude().getRotation());
739             Assert.assertEquals(0, Vector3D.dotProduct(normal, Vector3D.PLUS_J), 1.0e-16);
740         }
741     }
742 
743     @Test
744     public void testNormalFixedRateField() throws OrekitException {
745         AbsoluteDate initialDate = propagator.getInitialState().getDate();
746         CelestialBody sun = CelestialBodyFactory.getSun();
747         BoxAndSolarArraySpacecraft s =
748             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J,
749                                            initialDate, Vector3D.PLUS_K, 1.0e-3,
750                                            0.0, 1.0, 0.0);
751         Field<Decimal64> field = Decimal64Field.getInstance();
752         for (double dt = 0; dt < 4000; dt += 60) {
753             AbsoluteDate date = initialDate.shiftedBy(dt);
754             SpacecraftState state = propagator.propagate(date);
755             FieldVector3D<Decimal64> normal = s.getNormal(new FieldAbsoluteDate<>(field, state.getDate()),
756                                                           state.getFrame(),
757                                                           new FieldVector3D<>(field, state.getPVCoordinates().getPosition()),
758                                                           new FieldRotation<>(field, state.getAttitude().getRotation()));
759             Assert.assertEquals(0, FieldVector3D.dotProduct(normal, Vector3D.PLUS_J).getReal(), 1.0e-16);
760         }
761     }
762 
763     @Test
764     public void testNormalFixedRateDS() throws OrekitException {
765         AbsoluteDate initialDate = propagator.getInitialState().getDate();
766         CelestialBody sun = CelestialBodyFactory.getSun();
767         BoxAndSolarArraySpacecraft s =
768             new BoxAndSolarArraySpacecraft(0, 0, 0, sun, 20.0, Vector3D.PLUS_J,
769                                            initialDate, Vector3D.PLUS_K, 1.0e-3,
770                                            0.0, 1.0, 0.0);
771         DSFactory factory = new DSFactory(1, 2);
772         for (double dt = 0; dt < 4000; dt += 60) {
773             AbsoluteDate date = initialDate.shiftedBy(dt);
774             SpacecraftState state = propagator.propagate(date);
775             FieldVector3D<DerivativeStructure> normal = s.getNormal(state.getDate(),
776                                                                     state.getFrame(),
777                                                                     new FieldVector3D<>(factory.getDerivativeField(), state.getPVCoordinates().getPosition()),
778                                                                     new FieldRotation<>(factory.getDerivativeField(), state.getAttitude().getRotation()));
779             Assert.assertEquals(0, FieldVector3D.dotProduct(normal, Vector3D.PLUS_J).getReal(), 1.0e-16);
780         }
781     }
782 
783     @Test
784     public void testNormalSunAlignedDouble() throws OrekitException {
785         BoxAndSolarArraySpacecraft s =
786             new BoxAndSolarArraySpacecraft(0, 0, 0,
787                                            (date, frame) -> new TimeStampedPVCoordinates(date, new Vector3D(0, 1e6, 0), Vector3D.ZERO),
788                                            20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
789         Vector3D normal = s.getNormal(AbsoluteDate.J2000_EPOCH, FramesFactory.getEME2000(),
790                                       Vector3D.ZERO, Rotation.IDENTITY);
791         Assert.assertEquals(0, Vector3D.dotProduct(normal, Vector3D.PLUS_J), 1.0e-16);
792     }
793 
794     @Test
795     public void testNormalSunAlignedField() throws OrekitException {
796         BoxAndSolarArraySpacecraft s =
797                         new BoxAndSolarArraySpacecraft(0, 0, 0,
798                                                        (date, frame) -> new TimeStampedPVCoordinates(date, new Vector3D(0, 1e6, 0), Vector3D.ZERO),
799                                                        20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
800         Field<Decimal64> field = Decimal64Field.getInstance();
801         FieldVector3D<Decimal64> normal = s.getNormal(FieldAbsoluteDate.getJ2000Epoch(field),
802                                                       FramesFactory.getEME2000(),
803                                                       FieldVector3D.getZero(field),
804                                                       FieldRotation.getIdentity(field));
805         Assert.assertEquals(0, FieldVector3D.dotProduct(normal, Vector3D.PLUS_J).getReal(), 1.0e-16);
806     }
807 
808     @Test
809     public void testNormalSunAlignedDS() throws OrekitException {
810         BoxAndSolarArraySpacecraft s =
811                         new BoxAndSolarArraySpacecraft(0, 0, 0,
812                                                        (date, frame) -> new TimeStampedPVCoordinates(date, new Vector3D(0, 1e6, 0), Vector3D.ZERO),
813                                                        20.0, Vector3D.PLUS_J, 0.0, 1.0, 0.0);
814         DSFactory factory = new DSFactory(1, 2);
815         FieldVector3D<DerivativeStructure> normal = s.getNormal(AbsoluteDate.J2000_EPOCH,
816                                                                 FramesFactory.getEME2000(),
817                                                                 FieldVector3D.getZero(factory.getDerivativeField()),
818                                                                 FieldRotation.getIdentity(factory.getDerivativeField()));
819         Assert.assertEquals(0, FieldVector3D.dotProduct(normal, Vector3D.PLUS_J).getReal(), 1.0e-16);
820     }
821 
822     private double[] getDragParameters(final BoxAndSolarArraySpacecraft basa) {
823         final ParameterDriver[] drivers = basa.getDragParametersDrivers();
824         final double[] parameters = new double[drivers.length];
825         for (int i = 0; i < drivers.length; ++i) {
826             parameters[i] = drivers[i].getValue();
827         }
828         return parameters;
829     }
830 
831     private double[] getRadiationParameters(final BoxAndSolarArraySpacecraft basa) {
832         final ParameterDriver[] drivers = basa.getRadiationParametersDrivers();
833         final double[] parameters = new double[drivers.length];
834         for (int i = 0; i < drivers.length; ++i) {
835             parameters[i] = drivers[i].getValue();
836         }
837         return parameters;
838     }
839 
840     @Before
841     public void setUp() {
842         try {
843         Utils.setDataRoot("regular-data");
844         mu  = 3.9860047e14;
845         double ae  = 6.378137e6;
846         double c20 = -1.08263e-3;
847         double c30 = 2.54e-6;
848         double c40 = 1.62e-6;
849         double c50 = 2.3e-7;
850         double c60 = -5.5e-7;
851 
852         AbsoluteDate date = new AbsoluteDate(new DateComponents(1970, 7, 1),
853                                              new TimeComponents(13, 59, 27.816),
854                                              TimeScalesFactory.getUTC());
855 
856         // Satellite position as circular parameters, raan chosen to have sun elevation with
857         // respect to orbit plane roughly evolving roughly from 15 to 15.2 degrees in the test range
858         Orbit circ =
859             new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(50.), FastMath.toRadians(280),
860                                    FastMath.toRadians(10.0), PositionAngle.MEAN,
861                                    FramesFactory.getEME2000(), date, mu);
862         propagator =
863             new EcksteinHechlerPropagator(circ,
864                                           new LofOffset(circ.getFrame(), LOFType.VVLH),
865                                           ae, mu, c20, c30, c40, c50, c60);
866         } catch (OrekitException oe) {
867             Assert.fail(oe.getLocalizedMessage());
868         }
869     }
870 
871     private double mu;
872     private Propagator propagator;
873 
874 }