1   /* Copyright 2022-2025 Luc Maisonobe
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.attitudes;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.analysis.UnivariateVectorFunction;
22  import org.hipparchus.analysis.differentiation.FiniteDifferencesDifferentiator;
23  import org.hipparchus.analysis.differentiation.UnivariateDerivative1;
24  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Rotation;
27  import org.hipparchus.geometry.euclidean.threed.Vector3D;
28  import org.hipparchus.util.Binary64;
29  import org.hipparchus.util.Binary64Field;
30  import org.hipparchus.util.FastMath;
31  import org.junit.jupiter.api.AfterEach;
32  import org.junit.jupiter.api.Assertions;
33  import org.junit.jupiter.api.BeforeEach;
34  import org.junit.jupiter.api.Test;
35  import org.junit.jupiter.params.ParameterizedTest;
36  import org.junit.jupiter.params.provider.EnumSource;
37  import org.orekit.Utils;
38  import org.orekit.bodies.AnalyticalSolarPositionProvider;
39  import org.orekit.bodies.CelestialBody;
40  import org.orekit.bodies.CelestialBodyFactory;
41  import org.orekit.bodies.FieldGeodeticPoint;
42  import org.orekit.bodies.GeodeticPoint;
43  import org.orekit.bodies.OneAxisEllipsoid;
44  import org.orekit.errors.OrekitException;
45  import org.orekit.errors.OrekitMessages;
46  import org.orekit.frames.FieldTransform;
47  import org.orekit.frames.Frame;
48  import org.orekit.frames.FramesFactory;
49  import org.orekit.frames.Transform;
50  import org.orekit.orbits.FieldCartesianOrbit;
51  import org.orekit.orbits.FieldOrbit;
52  import org.orekit.orbits.KeplerianOrbit;
53  import org.orekit.orbits.Orbit;
54  import org.orekit.propagation.FieldPropagator;
55  import org.orekit.propagation.Propagator;
56  import org.orekit.propagation.analytical.FieldKeplerianPropagator;
57  import org.orekit.propagation.analytical.KeplerianPropagator;
58  import org.orekit.time.AbsoluteDate;
59  import org.orekit.time.FieldAbsoluteDate;
60  import org.orekit.time.TimeScalesFactory;
61  import org.orekit.utils.Constants;
62  import org.orekit.utils.ExtendedPositionProvider;
63  import org.orekit.utils.IERSConventions;
64  import org.orekit.utils.PVCoordinates;
65  
66  import java.util.function.Function;
67  
68  class AlignedAndConstrainedTest {
69  
70      @Test
71      void testAlignmentsEarthSun() {
72          doTestAlignment(Vector3D.PLUS_K, PredefinedTarget.EARTH,
73                          Vector3D.PLUS_I, PredefinedTarget.SUN,
74                          t -> Vector3D.ZERO,
75                          t -> sun.getPosition(t, gcrf),
76                          1.0e-15, 1.0e-15);
77      }
78  
79      @Test
80      void testAlignmentsEarthSunField() {
81          final Binary64Field field = Binary64Field.getInstance();
82          doTestAlignment(field,
83                          Vector3D.PLUS_K, PredefinedTarget.EARTH,
84                          Vector3D.PLUS_I, PredefinedTarget.SUN,
85                          t -> FieldVector3D.getZero(field),
86                          t -> sun.getPosition(t, gcrf),
87                          1.0e-15, 1.0e-15);
88      }
89  
90      @Test
91      void testDerivativesEarthSun() {
92          doTestDerivatives(Vector3D.PLUS_K, PredefinedTarget.EARTH,
93                            Vector3D.PLUS_I, PredefinedTarget.SUN,
94                            2.0e-15);
95      }
96  
97      @Test
98      void testAlignmentsNadirNorth() {
99          doTestAlignment(Vector3D.PLUS_K, PredefinedTarget.NADIR,
100                         Vector3D.PLUS_I, PredefinedTarget.NORTH,
101                         t -> {
102                             final Vector3D      pH          = orbit.shiftedBy(t.durationFrom(orbit.getDate())).getPosition();
103                             final GeodeticPoint gpH         = earth.transform(pH, gcrf, t);
104                             final GeodeticPoint gp0         = new GeodeticPoint(gpH.getLatitude(), gpH.getLongitude(), 0.0);
105                             final Vector3D      p0          = earth.transform(gp0);
106                             final Transform     earth2Inert = earth.getBodyFrame().getTransformTo(gcrf, t);
107                             return earth2Inert.transformPosition(p0);
108                         },
109                         t -> Vector3D.PLUS_K,
110                         1.0e-13, 4.0e-5);
111     }
112 
113     @Test
114     void testAlignmentsNadirNorthField() {
115         final Binary64Field field = Binary64Field.getInstance();
116         doTestAlignment(field,
117                         Vector3D.PLUS_K, PredefinedTarget.NADIR,
118                         Vector3D.PLUS_I, PredefinedTarget.NORTH,
119                         t -> {
120                             final FieldVector3D<Binary64>      pH          = getOrbit(field).shiftedBy(t.durationFrom(orbit.getDate())).getPosition();
121                             final FieldGeodeticPoint<Binary64> gpH         = earth.transform(pH, gcrf, t);
122                             final FieldGeodeticPoint<Binary64> gp0         = new FieldGeodeticPoint<>(gpH.getLatitude(),
123                                                                                                       gpH.getLongitude(),
124                                                                                                       field.getZero());
125                             final FieldVector3D<Binary64>      p0          = earth.transform(gp0);
126                             final FieldTransform<Binary64>     earth2Inert = earth.getBodyFrame().getTransformTo(gcrf, t);
127                             return earth2Inert.transformPosition(p0);
128                         },
129                         t -> FieldVector3D.getPlusK(field),
130                         1.0e-10, 4.0e-5);
131     }
132 
133     @Test
134     void testDerivativesNadirNorth() {
135         doTestDerivatives(Vector3D.PLUS_K, PredefinedTarget.NADIR,
136                           Vector3D.PLUS_I, PredefinedTarget.NORTH,
137                           1.0e-12);
138     }
139 
140     @Test
141     void testAlignmentsVelocityMomentum() {
142         doTestAlignment(Vector3D.MINUS_J, PredefinedTarget.VELOCITY,
143                         Vector3D.MINUS_K, PredefinedTarget.MOMENTUM,
144                         t -> orbit.shiftedBy(t.durationFrom(orbit.getDate())).getVelocity().normalize(),
145                         t -> orbit.shiftedBy(t.durationFrom(orbit.getDate())).getPVCoordinates().getMomentum().normalize(),
146                         1.0e-10, 1.0e-15);
147     }
148 
149     @Test
150     void testAlignmentsVelocityMomentumField() {
151         final Binary64Field field = Binary64Field.getInstance();
152         doTestAlignment(field,
153                         Vector3D.MINUS_J, PredefinedTarget.VELOCITY,
154                         Vector3D.MINUS_K, PredefinedTarget.MOMENTUM,
155                         t -> getOrbit(field).shiftedBy(t.durationFrom(orbit.getDate())).getVelocity().normalize(),
156                         t -> getOrbit(field).shiftedBy(t.durationFrom(orbit.getDate())).getPVCoordinates().getMomentum().normalize(),
157                         1.0e-10, 1.0e-15);
158     }
159 
160     @Test
161     void testDerivativesVelocityMomentum() {
162         doTestDerivatives(Vector3D.MINUS_J, PredefinedTarget.VELOCITY,
163                           Vector3D.MINUS_K, PredefinedTarget.MOMENTUM,
164                           7.0e-16);
165     }
166 
167     @Test
168     void testAlignmentsStationEast() {
169         doTestAlignment(Vector3D.PLUS_K, new GroundPointTarget(station),
170                         Vector3D.PLUS_I, PredefinedTarget.EAST,
171                         t -> earth.getBodyFrame().getTransformTo(gcrf, t).transformPosition(station),
172                         t -> {
173                            final Transform earth2Inert = earth.getBodyFrame().getTransformTo(gcrf, t);
174                            final Vector3D  pInert      = orbit.shiftedBy(t.durationFrom(orbit.getDate())).getPosition();
175                            final GeodeticPoint gp      = earth.transform(pInert, gcrf, t);
176                            return earth2Inert.transformVector(gp.getEast()).normalize();
177                         },
178                         4.0e-10, 1.0e-10);
179     }
180 
181     @Test
182     void testAlignmentsStationEastField() {
183         final Binary64Field field = Binary64Field.getInstance();
184         doTestAlignment(field,
185                         Vector3D.PLUS_K, new GroundPointTarget(station),
186                         Vector3D.PLUS_I, PredefinedTarget.EAST,
187                         t -> earth.getBodyFrame().getTransformTo(gcrf, t).transformPosition(station),
188                         t -> {
189                             final FieldTransform<Binary64> earth2Inert = earth.getBodyFrame().getTransformTo(gcrf, t);
190                             final FieldVector3D<Binary64>  pInert      = getOrbit(field).shiftedBy(t.durationFrom(orbit.getDate())).getPosition();
191                             final FieldGeodeticPoint<Binary64> gp      = earth.transform(pInert, gcrf, t);
192                             return earth2Inert.transformVector(gp.getEast()).normalize();
193                         },
194                         4.0e-10, 1e-10);
195     }
196 
197     @Test
198     void testDerivativesStationEast() {
199         doTestDerivatives(Vector3D.PLUS_K, new GroundPointTarget(station),
200                           Vector3D.PLUS_I, PredefinedTarget.EAST,
201                           7.0e-13);
202     }
203 
204     private void doTestAlignment(final Vector3D primarySat, final TargetProvider primaryTarget,
205                                  final Vector3D secondarySat, final TargetProvider secondaryTarget,
206                                  final Function<AbsoluteDate, Vector3D> referencePrimaryProvider,
207                                  final Function<AbsoluteDate, Vector3D> referenceSecondaryProvider,
208                                  final double primaryTolerance, final double secondaryTolerance) {
209 
210         final Propagator propagator = new KeplerianPropagator(orbit);
211         propagator.setAttitudeProvider(new AlignedAndConstrained(primarySat, primaryTarget,
212                                                                  secondarySat, secondaryTarget,
213                                                                  sun, earth));
214 
215         propagator.getMultiplexer().add(60.0, state -> {
216             final Vector3D satP        = state.getPVCoordinates().getPosition();
217             final Vector3D primaryP    = referencePrimaryProvider.apply(state.getDate());
218             final Vector3D secondaryP  = referenceSecondaryProvider.apply(state.getDate());
219             final Transform inertToSat = state.toTransform();
220             final Vector3D primaryDir;
221             if (FastMath.abs(primaryP.getNorm() - 1.0) < 1.0e-10) {
222                 // reference is a unit vector
223                 primaryDir = primaryP;
224             } else {
225                 // reference is a position
226                 primaryDir = primaryP.subtract(satP);
227             }
228             final Vector3D secondaryDir;
229             if (FastMath.abs(secondaryP.getNorm() - 1.0) < 1.0e-10) {
230                 // reference is a unit vector
231                 secondaryDir = secondaryP;
232             } else {
233                 // reference is a position
234                 secondaryDir = secondaryP.subtract(satP);
235             }
236             Assertions.assertEquals(0,
237                                     Vector3D.angle(inertToSat.transformVector(primaryDir), primarySat),
238                                     primaryTolerance);
239             Assertions.assertEquals(0,
240                                     Vector3D.angle(inertToSat.transformVector(Vector3D.crossProduct(primaryDir,
241                                                                                                     secondaryDir)),
242                                                    Vector3D.crossProduct(primarySat, secondarySat)),
243                                     secondaryTolerance);
244         });
245         propagator.propagate(propagator.getInitialState().getDate().shiftedBy(3600));
246 
247     }
248 
249     private <T extends CalculusFieldElement<T>> void doTestAlignment(final Field<T> field,
250                                                                      final Vector3D primarySat, final TargetProvider primaryTarget,
251                                                                      final Vector3D secondarySat, final TargetProvider secondaryTarget,
252                                                                      final Function<FieldAbsoluteDate<T>, FieldVector3D<T>> referencePrimaryProvider,
253                                                                      final Function<FieldAbsoluteDate<T>, FieldVector3D<T>> referenceSecondaryProvider,
254                                                                      final double primaryTolerance, final double secondaryTolerance) {
255 
256         final FieldVector3D<T> primarySatF   = new FieldVector3D<>(field, primarySat);
257         final FieldVector3D<T> secondarySatF = new FieldVector3D<>(field, secondarySat);
258 
259         final FieldPropagator<T> propagator = new FieldKeplerianPropagator<>(getOrbit(field));
260         propagator.setAttitudeProvider(new AlignedAndConstrained(primarySat, primaryTarget,
261                                                                  secondarySat, secondaryTarget,
262                                                                  sun, earth));
263 
264         propagator.getMultiplexer().add(field.getZero().newInstance(60.0), state -> {
265             final FieldVector3D<T> satP        = state.getPVCoordinates().getPosition();
266             final FieldVector3D<T> primaryP    = referencePrimaryProvider.apply(state.getDate());
267             final FieldVector3D<T> secondaryP  = referenceSecondaryProvider.apply(state.getDate());
268             final FieldTransform<T> inertToSat = state.toTransform();
269             final FieldVector3D<T> primaryDir;
270             if (FastMath.abs(primaryP.getNorm().getReal() - 1.0) < 1.0e-10) {
271                 // reference is a unit vector
272                 primaryDir = primaryP;
273             } else {
274                 // reference is a position
275                 primaryDir = primaryP.subtract(satP);
276             }
277             final FieldVector3D<T> secondaryDir;
278             if (FastMath.abs(secondaryP.getNorm().getReal() - 1.0) < 1.0e-10) {
279                 // reference is a unit vector
280                 secondaryDir = secondaryP;
281             } else {
282                 // reference is a position
283                 secondaryDir = secondaryP.subtract(satP);
284             }
285             Assertions.assertEquals(0,
286                                     FieldVector3D.angle(inertToSat.transformVector(primaryDir), primarySatF).getReal(),
287                                     primaryTolerance);
288             Assertions.assertEquals(0,
289                                     FieldVector3D.angle(inertToSat.transformVector(FieldVector3D.crossProduct(primaryDir,
290                                                                                                               secondaryDir)),
291                                                         FieldVector3D.crossProduct(primarySatF, secondarySatF)).getReal(),
292                                     secondaryTolerance);
293         });
294         propagator.propagate(propagator.getInitialState().getDate().shiftedBy(3600));
295 
296     }
297 
298     private void doTestDerivatives(final Vector3D primarySat, final TargetProvider primaryTarget,
299                                    final Vector3D secondarySat, final TargetProvider secondaryTarget,
300                                    final double tolerance) {
301 
302         final AlignedAndConstrained aac = new AlignedAndConstrained(primarySat, primaryTarget,
303                                                                     secondarySat, secondaryTarget,
304                                                                     sun, earth);
305 
306         // evaluate quaternion derivatives using finite differences
307         final UnivariateVectorFunction q = dt -> {
308             final Attitude attitude = aac.getAttitude(orbit.shiftedBy(dt), orbit.getDate().shiftedBy(dt), gcrf);
309             final Rotation rotation = attitude.getRotation();
310             return new double[] {
311                 rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3()
312             };
313         };
314         final UnivariateDerivative1[] qDot = new FiniteDifferencesDifferentiator(8, 0.1).
315                                              differentiate(q).
316                                              value(new UnivariateDerivative1(0.0, 1.0));
317 
318         // evaluate quaternions derivatives using internal model
319         final FieldRotation<UnivariateDerivative1> r = aac.
320                                                        getAttitude(orbit, orbit.getDate(), gcrf).
321                                                        getOrientation().
322                                                        toUnivariateDerivative1Rotation();
323 
324         Assertions.assertEquals(qDot[0].getDerivative(1), r.getQ0().getDerivative(1), tolerance);
325         Assertions.assertEquals(qDot[1].getDerivative(1), r.getQ1().getDerivative(1), tolerance);
326         Assertions.assertEquals(qDot[2].getDerivative(1), r.getQ2().getDerivative(1), tolerance);
327         Assertions.assertEquals(qDot[3].getDerivative(1), r.getQ3().getDerivative(1), tolerance);
328 
329     }
330 
331     private <T extends CalculusFieldElement<T>> FieldOrbit<T> getOrbit(final Field<T> field) {
332         return orbit.getType().convertToFieldOrbit(field, orbit);
333     }
334 
335     @ParameterizedTest
336     @EnumSource(value = PredefinedTarget.class)
337     void testGetAttitudeRotation(final PredefinedTarget target) {
338         // GIVEN
339         final GroundPointTarget groundPointTarget = new GroundPointTarget(new Vector3D(1., 2.));
340         final AlignedAndConstrained alignedAndConstrained = new AlignedAndConstrained(Vector3D.PLUS_I, target,
341                 Vector3D.MINUS_J, groundPointTarget, orbit.getFrame(), sun, earth);
342         // WHEN
343         final Rotation actualRotation = alignedAndConstrained.getAttitudeRotation(orbit, orbit.getDate(), orbit.getFrame());
344         // THEN
345         final Rotation expectedRotation = alignedAndConstrained.getAttitude(orbit, orbit.getDate(), orbit.getFrame()).getRotation();
346         Assertions.assertEquals(0., Rotation.distance(expectedRotation, actualRotation), 5e-9);
347     }
348 
349     @ParameterizedTest
350     @EnumSource(value = PredefinedTarget.class)
351     void testGetAttitudeRotationDifferentFrame(final PredefinedTarget target) {
352         // GIVEN
353         final GroundPointTarget groundPointTarget = new GroundPointTarget(new Vector3D(1., 2.));
354         final Frame inertialFrame = FramesFactory.getTOD(false);
355         final AlignedAndConstrained alignedAndConstrained = new AlignedAndConstrained(Vector3D.PLUS_I, target,
356                 Vector3D.MINUS_J, groundPointTarget, inertialFrame, sun, earth);
357         // WHEN
358         final Rotation actualRotation = alignedAndConstrained.getAttitudeRotation(orbit, orbit.getDate(), orbit.getFrame());
359         // THEN
360         final Rotation expectedRotation = alignedAndConstrained.getAttitude(orbit, orbit.getDate(), orbit.getFrame()).getRotation();
361         Assertions.assertEquals(0., Rotation.distance(expectedRotation, actualRotation), 5e-9);
362     }
363 
364     @ParameterizedTest
365     @EnumSource(value = PredefinedTarget.class)
366     void testFieldGetAttitudeRotationVersusNonField(final PredefinedTarget target) {
367         // GIVEN
368         final GroundPointTarget groundPointTarget = new GroundPointTarget(new Vector3D(1., 2.));
369         final AlignedAndConstrained alignedAndConstrained = new AlignedAndConstrained(Vector3D.PLUS_I, target,
370                 Vector3D.MINUS_J, groundPointTarget, orbit.getFrame(), sun, earth);
371         final FieldOrbit<Binary64> fieldOrbit = new FieldCartesianOrbit<>(Binary64Field.getInstance(), orbit);
372         // WHEN
373         final Rotation actualRotation = alignedAndConstrained.getAttitudeRotation(fieldOrbit, fieldOrbit.getDate(),
374                 fieldOrbit.getFrame()).toRotation();
375         // THEN
376         final Rotation expectedRotation = alignedAndConstrained.getAttitudeRotation(orbit, orbit.getDate(), orbit.getFrame());
377         Assertions.assertEquals(0., Rotation.distance(expectedRotation, actualRotation), 5e-9);
378     }
379 
380     @ParameterizedTest
381     @EnumSource(value = PredefinedTarget.class)
382     void testFieldGetAttitudeRotationVersusNonFieldDifferentFrame(final PredefinedTarget target) {
383         // GIVEN
384         final GroundPointTarget groundPointTarget = new GroundPointTarget(new Vector3D(1., 2.));
385         final Frame inertialFrame = FramesFactory.getTOD(false);
386         final AlignedAndConstrained alignedAndConstrained = new AlignedAndConstrained(Vector3D.PLUS_I, target,
387                 Vector3D.MINUS_J, groundPointTarget, inertialFrame, sun, earth);
388         final FieldOrbit<Binary64> fieldOrbit = new FieldCartesianOrbit<>(Binary64Field.getInstance(), orbit);
389         // WHEN
390         final Rotation actualRotation = alignedAndConstrained.getAttitudeRotation(fieldOrbit, fieldOrbit.getDate(),
391                 fieldOrbit.getFrame()).toRotation();
392         // THEN
393         final Rotation expectedRotation = alignedAndConstrained.getAttitudeRotation(orbit, orbit.getDate(), orbit.getFrame());
394         Assertions.assertEquals(0., Rotation.distance(expectedRotation, actualRotation), 5e-9);
395     }
396 
397     @ParameterizedTest
398     @EnumSource(value = PredefinedTarget.class)
399     void testFieldGetAttitudeRotation(final PredefinedTarget target) {
400         // GIVEN
401         final GroundPointTarget groundPointTarget = new GroundPointTarget(new Vector3D(1., 2.));
402         final AlignedAndConstrained alignedAndConstrained = new AlignedAndConstrained(Vector3D.PLUS_I, target,
403                 Vector3D.MINUS_J, groundPointTarget, orbit.getFrame(), sun, earth);
404         final FieldOrbit<Binary64> fieldOrbit = new FieldCartesianOrbit<>(Binary64Field.getInstance(), orbit);
405         // WHEN
406         final FieldRotation<Binary64> actualRotation = alignedAndConstrained.getAttitudeRotation(fieldOrbit, fieldOrbit.getDate(), fieldOrbit.getFrame());
407         // THEN
408         final FieldRotation<Binary64> expectedRotation = alignedAndConstrained.getAttitude(fieldOrbit, fieldOrbit.getDate(), fieldOrbit.getFrame()).getRotation();
409         Assertions.assertEquals(0., Rotation.distance(expectedRotation.toRotation(), actualRotation.toRotation()), 5e-9);
410     }
411 
412     @ParameterizedTest
413     @EnumSource(value = PredefinedTarget.class)
414     void testFieldGetAttitudeRotationDifferentFrames(final PredefinedTarget target) {
415         // GIVEN
416         final GroundPointTarget groundPointTarget = new GroundPointTarget(new Vector3D(1., 2.));
417         final Frame inertialFrame = FramesFactory.getTOD(false);
418         final AlignedAndConstrained alignedAndConstrained = new AlignedAndConstrained(Vector3D.PLUS_I, target,
419                 Vector3D.MINUS_J, groundPointTarget, inertialFrame, sun, earth);
420         final FieldOrbit<Binary64> fieldOrbit = new FieldCartesianOrbit<>(Binary64Field.getInstance(), orbit);
421         // WHEN
422         final FieldRotation<Binary64> actualRotation = alignedAndConstrained.getAttitudeRotation(fieldOrbit, fieldOrbit.getDate(), fieldOrbit.getFrame());
423         // THEN
424         final FieldRotation<Binary64> expectedRotation = alignedAndConstrained.getAttitude(fieldOrbit, fieldOrbit.getDate(), fieldOrbit.getFrame()).getRotation();
425         Assertions.assertEquals(0., Rotation.distance(expectedRotation.toRotation(), actualRotation.toRotation()), 5e-9);
426     }
427 
428     @Test
429     void testGetAttitude() {
430         // GIVEN
431         final ExtendedPositionProvider sunProvider = new AnalyticalSolarPositionProvider();
432         final Frame inertialFrame = FramesFactory.getGCRF();
433         final AlignedAndConstrained attitudeProvider = new AlignedAndConstrained(Vector3D.PLUS_I,
434                 PredefinedTarget.VELOCITY, Vector3D.PLUS_K, PredefinedTarget.SUN, inertialFrame, sunProvider, null);
435         // WHEN
436         final Attitude actualAttitude = attitudeProvider.getAttitude(orbit, orbit.getDate(), inertialFrame);
437         // THEN
438         final Frame earthFixedFrame = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
439         final Attitude attitude = attitudeProvider.getAttitude(orbit, orbit.getDate(), earthFixedFrame);
440         final Attitude expectedAttitude = attitude.withReferenceFrame(inertialFrame);
441         Assertions.assertEquals(0., Rotation.distance(expectedAttitude.getRotation(),
442                 actualAttitude.getRotation()), 1e-10);
443         Assertions.assertEquals(0., expectedAttitude.getSpin().subtract(actualAttitude.getSpin()).getNorm(),
444                 1e-6);
445     }
446 
447     @Test
448     void testConstructorException() {
449         // GIVEN
450         final Frame nonInertialFrame = FramesFactory.getGTOD(false);
451         // WHEN
452         final OrekitException exception = Assertions.assertThrows(OrekitException.class, () -> new AlignedAndConstrained(Vector3D.PLUS_I,
453                 PredefinedTarget.VELOCITY, Vector3D.PLUS_K, PredefinedTarget.SUN, nonInertialFrame, null, null));
454         // THEN
455         Assertions.assertEquals(OrekitMessages.NON_PSEUDO_INERTIAL_FRAME, exception.getSpecifier());
456     }
457 
458     @BeforeEach
459     public void setUp() {
460         Utils.setDataRoot("regular-data");
461         gcrf = FramesFactory.getGCRF();
462         orbit = new KeplerianOrbit(new PVCoordinates(new Vector3D(28812595.32012577, 5948437.4640250085, 0),
463                                                      new Vector3D(0, 0, 3680.853673522056)),
464                                    gcrf,
465                                    new AbsoluteDate(2003, 3, 2, 13, 17, 7.865, TimeScalesFactory.getUTC()),
466                                    3.986004415e14);
467         earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
468                                      Constants.WGS84_EARTH_FLATTENING,
469                                      FramesFactory.getITRF(IERSConventions.IERS_2010, true));
470         sun = CelestialBodyFactory.getSun();
471         station = earth.transform(new GeodeticPoint(FastMath.toRadians(0.33871), FastMath.toRadians(6.73054), 0.0));
472     }
473 
474     @AfterEach
475     public void tearDown() {
476         gcrf = null;
477         orbit   = null;
478         earth   = null;
479         sun     = null;
480         station = null;
481     }
482 
483     private Frame gcrf;
484     private Orbit orbit;
485     private CelestialBody sun;
486     private OneAxisEllipsoid earth;
487     private Vector3D station;
488 
489 }