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