1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.attitudes;
18  
19  import static org.hamcrest.CoreMatchers.is;
20  import static org.hamcrest.CoreMatchers.not;
21  import static org.orekit.OrekitMatchers.attitudeIs;
22  import static org.orekit.OrekitMatchers.closeTo;
23  import static org.orekit.OrekitMatchers.distanceIs;
24  
25  import org.hamcrest.MatcherAssert;
26  import org.hipparchus.CalculusFieldElement;
27  import org.hipparchus.Field;
28  import org.hipparchus.analysis.differentiation.GradientField;
29  import org.hipparchus.complex.ComplexField;
30  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
31  import org.hipparchus.geometry.euclidean.threed.Rotation;
32  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
33  import org.hipparchus.geometry.euclidean.threed.Vector3D;
34  import org.hipparchus.util.Binary64;
35  import org.hipparchus.util.Binary64Field;
36  import org.hipparchus.util.FastMath;
37  import org.junit.jupiter.api.AfterEach;
38  import org.junit.jupiter.api.Assertions;
39  import org.junit.jupiter.api.BeforeEach;
40  import org.junit.jupiter.api.Test;
41  import org.orekit.Utils;
42  import org.orekit.annotation.DefaultDataContext;
43  import org.orekit.errors.OrekitException;
44  import org.orekit.frames.Frame;
45  import org.orekit.frames.FramesFactory;
46  import org.orekit.frames.Transform;
47  import org.orekit.orbits.FieldCartesianOrbit;
48  import org.orekit.orbits.FieldOrbit;
49  import org.orekit.orbits.KeplerianOrbit;
50  import org.orekit.orbits.Orbit;
51  import org.orekit.orbits.PositionAngleType;
52  import org.orekit.propagation.FieldSpacecraftState;
53  import org.orekit.propagation.Propagator;
54  import org.orekit.propagation.SpacecraftState;
55  import org.orekit.propagation.analytical.KeplerianPropagator;
56  import org.orekit.time.AbsoluteDate;
57  import org.orekit.time.DateComponents;
58  import org.orekit.time.FieldAbsoluteDate;
59  import org.orekit.time.TimeComponents;
60  import org.orekit.time.TimeScalesFactory;
61  import org.orekit.utils.AngularCoordinates;
62  import org.orekit.utils.FieldPVCoordinates;
63  import org.orekit.utils.FieldPVCoordinatesProvider;
64  
65  
66  public class FrameAlignedProviderTest {
67  
68      private AbsoluteDate t0;
69      private Orbit        orbit0;
70  
71      @Test
72      @DefaultDataContext
73      void testIsInertial() {
74          FrameAlignedProvider law = new FrameAlignedProvider(new Rotation(new Vector3D(0.6, 0.48, 0.64), 0.9,
75                                                                           RotationConvention.VECTOR_OPERATOR));
76          KeplerianPropagator propagator = new KeplerianPropagator(orbit0, law);
77          Attitude initial = propagator.propagate(t0).getAttitude();
78          for (double t = 0; t < 10000.0; t += 100) {
79              SpacecraftState state = propagator.propagate(t0.shiftedBy(t));
80              checkField(Binary64Field.getInstance(), law, state.getOrbit(), state.getDate(), state.getFrame());
81              Attitude attitude = state.getAttitude();
82              Rotation evolution = attitude.getRotation().compose(initial.getRotation().revert(),
83                                                                  RotationConvention.VECTOR_OPERATOR);
84              Assertions.assertEquals(0, evolution.getAngle(), 1.0e-10);
85              Assertions.assertEquals(FramesFactory.getEME2000(), attitude.getReferenceFrame());
86          }
87      }
88  
89      @Test
90      @DefaultDataContext
91      void testCompensateMomentum() {
92          FrameAlignedProvider law = new FrameAlignedProvider(new Rotation(new Vector3D(-0.64, 0.6, 0.48), 0.2,
93                                                                           RotationConvention.VECTOR_OPERATOR));
94          KeplerianPropagator propagator = new KeplerianPropagator(orbit0, law);
95          Attitude initial = propagator.propagate(t0).getAttitude();
96          for (double t = 0; t < 10000.0; t += 100) {
97              Attitude attitude = propagator.propagate(t0.shiftedBy(t)).getAttitude();
98              Rotation evolution = attitude.getRotation().compose(initial.getRotation().revert(),
99                                                                  RotationConvention.VECTOR_OPERATOR);
100             Assertions.assertEquals(0, evolution.getAngle(), 1.0e-10);
101             Assertions.assertEquals(FramesFactory.getEME2000(), attitude.getReferenceFrame());
102         }
103     }
104 
105     @Test
106     @DefaultDataContext
107     void testSpin() {
108         AbsoluteDate date = new AbsoluteDate(new DateComponents(1970, 1, 1),
109                                              new TimeComponents(3, 25, 45.6789),
110                                              TimeScalesFactory.getUTC());
111 
112         AttitudeProvider law = new FrameAlignedProvider(new Rotation(new Vector3D(-0.64, 0.6, 0.48), 0.2,
113                                                                      RotationConvention.VECTOR_OPERATOR));
114         KeplerianOrbit orbit =
115             new KeplerianOrbit(7178000.0, 1.e-4, FastMath.toRadians(50.),
116                               FastMath.toRadians(10.), FastMath.toRadians(20.),
117                               FastMath.toRadians(30.), PositionAngleType.MEAN,
118                               FramesFactory.getEME2000(), date, 3.986004415e14);
119 
120         Propagator propagator = new KeplerianPropagator(orbit, law);
121 
122         double h = 100.0;
123         SpacecraftState sMinus = propagator.propagate(date.shiftedBy(-h));
124         SpacecraftState s0     = propagator.propagate(date);
125         SpacecraftState sPlus  = propagator.propagate(date.shiftedBy(h));
126 
127         // check spin is consistent with attitude evolution
128         double errorAngleMinus     = Rotation.distance(sMinus.shiftedBy(h).getAttitude().getRotation(),
129                                                        s0.getAttitude().getRotation());
130         double evolutionAngleMinus = Rotation.distance(sMinus.getAttitude().getRotation(),
131                                                        s0.getAttitude().getRotation());
132         Assertions.assertEquals(0.0, errorAngleMinus, 1.0e-6 * evolutionAngleMinus);
133         double errorAnglePlus      = Rotation.distance(s0.getAttitude().getRotation(),
134                                                        sPlus.shiftedBy(-h).getAttitude().getRotation());
135         double evolutionAnglePlus  = Rotation.distance(s0.getAttitude().getRotation(),
136                                                        sPlus.getAttitude().getRotation());
137         Assertions.assertEquals(0.0, errorAnglePlus, 1.0e-6 * evolutionAnglePlus);
138 
139         // compute spin axis using finite differences
140         Rotation rMinus = sMinus.getAttitude().getRotation();
141         Rotation rPlus  = sPlus.getAttitude().getRotation();
142         Rotation dr     = rPlus.compose(rMinus.revert(), RotationConvention.VECTOR_OPERATOR);
143         Assertions.assertEquals(0, dr.getAngle(), 1.0e-10);
144 
145         Vector3D spin0 = s0.getAttitude().getSpin();
146         Assertions.assertEquals(0, spin0.getNorm(), 1.0e-10);
147 
148     }
149 
150     private <T extends CalculusFieldElement<T>> void checkField(final Field<T> field, final AttitudeProvider provider,
151                                                                 final Orbit orbit, final AbsoluteDate date,
152                                                                 final Frame frame)
153         {
154         Attitude attitudeD = provider.getAttitude(orbit, date, frame);
155         final FieldOrbit<T> orbitF = new FieldSpacecraftState<>(field, new SpacecraftState(orbit)).getOrbit();
156         final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, date);
157         FieldAttitude<T> attitudeF = provider.getAttitude(orbitF, dateF, frame);
158         Assertions.assertEquals(0.0, Rotation.distance(attitudeD.getRotation(), attitudeF.getRotation().toRotation()), 1.0e-15);
159         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getSpin(), attitudeF.getSpin().toVector3D()), 1.0e-15);
160         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getRotationAcceleration(), attitudeF.getRotationAcceleration().toVector3D()), 1.0e-15);
161     }
162 
163     @Test
164     void testGetAttitude() {
165         // expected
166         Frame eci = orbit0.getFrame();
167         Attitude expected = new Attitude(t0, eci, AngularCoordinates.IDENTITY);
168         AttitudeProvider law = FrameAlignedProvider.of(eci);
169 
170         // action + verify
171         Attitude actual = law.getAttitude(orbit0, t0, eci);
172         MatcherAssert.assertThat(actual.getReferenceFrame(), is(eci));
173         MatcherAssert.assertThat(actual, attitudeIs(expected));
174         actual = law.getAttitude(orbit0.shiftedBy(1e3), t0.shiftedBy(1e3), eci);
175         MatcherAssert.assertThat(actual.getReferenceFrame(), is(eci));
176         MatcherAssert.assertThat(actual, attitudeIs(expected));
177         // create new frame for testing frame transforms
178         Rotation rotation = new Rotation(
179                 Vector3D.PLUS_K,
180                 FastMath.PI / 2.0,
181                 RotationConvention.FRAME_TRANSFORM);
182         Transform angular = new Transform(
183                 t0,
184                 rotation,
185                 new Vector3D(1, 2, 3),
186                 new Vector3D(-4, 5, 6));
187         Transform translation = new Transform(
188                 t0,
189                 new Vector3D(-1, 2, -3),
190                 new Vector3D(4, -5, 6),
191                 new Vector3D(7, 8, -9));
192         Frame other = new Frame(eci, new Transform(t0, angular, translation), "other");
193         actual = law.getAttitude(orbit0.shiftedBy(1e3), t0.shiftedBy(1e3), other);
194         MatcherAssert.assertThat(actual.getReferenceFrame(), is(other));
195         MatcherAssert.assertThat(actual, attitudeIs(expected));
196         // check not identity rotation
197         MatcherAssert.assertThat(actual.getRotation(),
198                                  not(distanceIs(Rotation.IDENTITY, closeTo(0.0, 1e-1))));
199     }
200 
201 
202     /**
203      * Unit tests for {@link FrameAlignedProvider#getAttitude(FieldPVCoordinatesProvider,
204      * FieldAbsoluteDate, Frame)}.
205      */
206     @Test
207     void testGetAttitudeField() {
208         // expected
209         Frame eci = orbit0.getFrame();
210         Attitude expected = new Attitude(t0, eci, AngularCoordinates.IDENTITY);
211         AttitudeProvider law = FrameAlignedProvider.of(eci);
212         Binary64 one = Binary64.ONE;
213         FieldAbsoluteDate<Binary64> date = new FieldAbsoluteDate<>(one.getField(), t0);
214         FieldOrbit<Binary64> orbit = new FieldCartesianOrbit<>(
215                 new FieldPVCoordinates<>(one, this.orbit0.getPVCoordinates()),
216                 eci,
217                 date,
218                 one.newInstance(orbit0.getMu()));
219 
220         // action + verify
221         FieldAttitude<Binary64> actual = law.getAttitude(orbit, date, eci);
222         MatcherAssert.assertThat(actual.getReferenceFrame(), is(eci));
223         MatcherAssert.assertThat(actual.toAttitude(), attitudeIs(expected));
224         actual = law.getAttitude(orbit.shiftedBy(1e3), date.shiftedBy(1e3), eci);
225         MatcherAssert.assertThat(actual.getReferenceFrame(), is(eci));
226         MatcherAssert.assertThat(actual.toAttitude(), attitudeIs(expected));
227         // create new frame for testing frame transforms
228         Rotation rotation = new Rotation(
229                 Vector3D.PLUS_K,
230                 FastMath.PI / 2.0,
231                 RotationConvention.FRAME_TRANSFORM);
232         Transform angular = new Transform(
233                 t0,
234                 rotation,
235                 new Vector3D(1, 2, 3),
236                 new Vector3D(-4, 5, 6));
237         Transform translation = new Transform(
238                 t0,
239                 new Vector3D(-1, 2, -3),
240                 new Vector3D(4, -5, 6),
241                 new Vector3D(7, 8, -9));
242         Frame other = new Frame(eci, new Transform(t0, angular, translation), "other");
243         actual = law.getAttitude(orbit.shiftedBy(1e3), date.shiftedBy(1e3), other);
244         MatcherAssert.assertThat(actual.getReferenceFrame(), is(other));
245         MatcherAssert.assertThat(actual.toAttitude(), attitudeIs(expected));
246         // check not identity rotation
247         MatcherAssert.assertThat(actual.getRotation().toRotation(),
248                 not(distanceIs(Rotation.IDENTITY, closeTo(0.0, 1e-1))));
249     }
250 
251     @Test
252     @DefaultDataContext
253     void testGetAttitudeRotation() {
254         // GIVEN
255         final Frame frame1 = FramesFactory.getGCRF();
256         final Frame frame2 = FramesFactory.getEME2000();
257         final AbsoluteDate date = orbit0.getDate();
258         final FrameAlignedProvider frameAlignedProvider = new FrameAlignedProvider(frame1);
259         // WHEN
260         final Rotation actualRotation = frameAlignedProvider.getAttitudeRotation(orbit0, date, frame2);
261         // THEN
262         final Rotation expectedRotation = frameAlignedProvider.getAttitude(orbit0, date, frame2).getRotation();
263         Assertions.assertEquals(0., Rotation.distance(expectedRotation, actualRotation));
264     }
265 
266     @Test
267     @DefaultDataContext
268     void testGetAttitudeRotationFieldComplex() {
269         final ComplexField complexField = ComplexField.getInstance();
270         templateTestGetRotationField(complexField);
271     }
272 
273     @Test
274     @DefaultDataContext
275     void testGetAttitudeRotationFieldGradient() {
276         final GradientField gradientField = GradientField.getField(1);
277         templateTestGetRotationField(gradientField);
278     }
279 
280     @DefaultDataContext
281     <T extends CalculusFieldElement<T>> void templateTestGetRotationField(final Field<T> field) {
282         // GIVEN
283         final Frame frame1 = FramesFactory.getGCRF();
284         final Frame frame2 = FramesFactory.getEME2000();
285         final FrameAlignedProvider frameAlignedProvider = new FrameAlignedProvider(frame1);
286         final SpacecraftState state = new SpacecraftState(orbit0);
287         final FieldSpacecraftState<T> fieldState = new FieldSpacecraftState<>(field, state);
288         // WHEN
289         final FieldRotation<T> actualRotation = frameAlignedProvider.getAttitudeRotation(fieldState.getOrbit(), fieldState.getDate(), frame2);
290         // THEN
291         final FieldRotation<T> expectedRotation = frameAlignedProvider.getAttitude(fieldState.getOrbit(), fieldState.getDate(), frame2).getRotation();
292         Assertions.assertEquals(0., Rotation.distance(expectedRotation.toRotation(), actualRotation.toRotation()));
293     }
294 
295     @BeforeEach
296     @DefaultDataContext
297     public void setUp() {
298         try {
299             Utils.setDataRoot("regular-data");
300 
301             t0 = new AbsoluteDate(new DateComponents(2008, 6, 3), TimeComponents.H12,
302                                   TimeScalesFactory.getUTC());
303             orbit0 =
304                 new KeplerianOrbit(12345678.9, 0.001, 2.3, 0.1, 3.04, 2.4,
305                                    PositionAngleType.TRUE, FramesFactory.getEME2000(),
306                                    t0, 3.986004415e14);
307         } catch (OrekitException oe) {
308             Assertions.fail(oe.getMessage());
309         }
310     }
311 
312     @AfterEach
313     public void tearDown() {
314         t0     = null;
315         orbit0 = null;
316     }
317 
318 }
319