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 org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.complex.Complex;
22  import org.hipparchus.complex.ComplexField;
23  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
24  import org.hipparchus.geometry.euclidean.threed.Rotation;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.hipparchus.util.Binary64Field;
27  import org.hipparchus.util.FastMath;
28  import org.junit.jupiter.api.Assertions;
29  import org.junit.jupiter.api.BeforeEach;
30  import org.junit.jupiter.api.Test;
31  import org.orekit.Utils;
32  import org.orekit.annotation.DefaultDataContext;
33  import org.orekit.bodies.CelestialBody;
34  import org.orekit.bodies.CelestialBodyFactory;
35  import org.orekit.frames.Frame;
36  import org.orekit.frames.FramesFactory;
37  import org.orekit.orbits.*;
38  import org.orekit.propagation.FieldSpacecraftState;
39  import org.orekit.propagation.Propagator;
40  import org.orekit.propagation.SpacecraftState;
41  import org.orekit.propagation.analytical.KeplerianPropagator;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.time.DateComponents;
44  import org.orekit.time.FieldAbsoluteDate;
45  import org.orekit.time.TimeComponents;
46  import org.orekit.time.TimeScalesFactory;
47  import org.orekit.utils.AngularCoordinates;
48  import org.orekit.utils.Constants;
49  import org.orekit.utils.PVCoordinates;
50  import org.orekit.utils.PVCoordinatesProvider;
51  
52  class SpinStabilizedTest {
53  
54      @Test
55      @DefaultDataContext
56      void testBBQMode() {
57          CelestialBody sun = CelestialBodyFactory.getSun();
58          AbsoluteDate date = new AbsoluteDate(new DateComponents(1970, 1, 1),
59                                               new TimeComponents(3, 25, 45.6789),
60                                               TimeScalesFactory.getTAI());
61          double rate = 2.0 * FastMath.PI / (12 * 60);
62          AttitudeProvider cbp = new CelestialBodyPointed(FramesFactory.getEME2000(), sun, Vector3D.PLUS_K,
63                                                          Vector3D.PLUS_I, Vector3D.PLUS_K);
64          SpinStabilized bbq = new SpinStabilized(cbp, date, Vector3D.PLUS_K, rate);
65          PVCoordinates pv =
66              new PVCoordinates(new Vector3D(28812595.32012577, 5948437.4640250085, 0),
67                                new Vector3D(0, 0, 3680.853673522056));
68          KeplerianOrbit kep = new KeplerianOrbit(pv, FramesFactory.getEME2000(), date, 3.986004415e14);
69          Attitude attitude = bbq.getAttitude(kep, date, kep.getFrame());
70          checkField(Binary64Field.getInstance(), bbq, kep, kep.getDate(), kep.getFrame());
71          Vector3D xDirection = attitude.getRotation().applyInverseTo(Vector3D.PLUS_I);
72          Assertions.assertEquals(FastMath.atan(1.0 / 5000.0),
73                       Vector3D.angle(xDirection, sun.getPosition(date, FramesFactory.getEME2000())),
74                       2.0e-15);
75          Assertions.assertEquals(rate, attitude.getSpin().getNorm(), 1.0e-6);
76          Assertions.assertSame(cbp, bbq.getUnderlyingAttitudeProvider());
77  
78      }
79  
80      @Test
81      @DefaultDataContext
82      void testGetAttitudeRotation() {
83          // GIVEN
84          final Orbit orbit = getOrbit();
85          final AttitudeProvider baseProvider = new FrameAlignedProvider(FramesFactory.getEME2000());
86          final AbsoluteDate startDate = orbit.getDate().shiftedBy(-10.);
87          final SpinStabilized spinStabilized = new SpinStabilized(baseProvider, startDate, Vector3D.PLUS_K, 0.1);
88          // WHEN
89          final Rotation rotation = spinStabilized.getAttitudeRotation(orbit, orbit.getDate(), orbit.getFrame());
90          // THEN
91          final Attitude attitude = spinStabilized.getAttitude(orbit, orbit.getDate(), orbit.getFrame());
92          Assertions.assertEquals(0., Rotation.distance(rotation, attitude.getRotation()));
93      }
94  
95      @Test
96      @DefaultDataContext
97      void testFieldGetAttitudeRotation() {
98          // GIVEN
99          final CartesianOrbit orbit = getOrbit();
100         final AbsoluteDate startDate = orbit.getDate().shiftedBy(-10.);
101 
102         final AttitudeProvider baseProvider = new FrameAlignedProvider(FramesFactory.getEME2000());
103         final SpinStabilized spinStabilized = new SpinStabilized(baseProvider, startDate, Vector3D.PLUS_K, 0.1);
104         final FieldOrbit<Complex> fieldOrbit = new FieldCircularOrbit<>(ComplexField.getInstance(), orbit);
105         // WHEN
106         final FieldRotation<Complex> rotation = spinStabilized.getAttitudeRotation(fieldOrbit, fieldOrbit.getDate(), fieldOrbit.getFrame());
107         // THEN
108         final FieldAttitude<Complex> attitude = spinStabilized.getAttitude(fieldOrbit, fieldOrbit.getDate(), fieldOrbit.getFrame());
109         Assertions.assertEquals(0., Rotation.distance(rotation.toRotation(), attitude.getRotation().toRotation()));
110     }
111 
112     @DefaultDataContext
113     private CartesianOrbit getOrbit() {
114         final PVCoordinates pv =
115                 new PVCoordinates(new Vector3D(28812595.32012577, 5948437.4640250085, 0),
116                         new Vector3D(0, 0, 3680.853673522056));
117         return new CartesianOrbit(pv, FramesFactory.getGCRF(), AbsoluteDate.ARBITRARY_EPOCH, Constants.EIGEN5C_EARTH_MU);
118     }
119 
120     @Test
121     @DefaultDataContext
122     void testSpin() {
123 
124         AbsoluteDate date = new AbsoluteDate(new DateComponents(1970, 1, 1),
125                                              new TimeComponents(3, 25, 45.6789),
126                                              TimeScalesFactory.getUTC());
127         double rate = 2.0 * FastMath.PI / (12 * 60);
128         AttitudeProvider law =
129             new SpinStabilized(new FrameAlignedProvider(Rotation.IDENTITY),
130                                date, Vector3D.PLUS_K, rate);
131         KeplerianOrbit orbit =
132             new KeplerianOrbit(7178000.0, 1.e-4, FastMath.toRadians(50.),
133                               FastMath.toRadians(10.), FastMath.toRadians(20.),
134                               FastMath.toRadians(30.), PositionAngleType.MEAN,
135                               FramesFactory.getEME2000(), date, 3.986004415e14);
136 
137         Propagator propagator = new KeplerianPropagator(orbit, law);
138 
139         double h = 10.0;
140         SpacecraftState sMinus = propagator.propagate(date.shiftedBy(-h));
141         SpacecraftState s0     = propagator.propagate(date);
142         SpacecraftState sPlus  = propagator.propagate(date.shiftedBy(h));
143         Vector3D spin0         = s0.getAttitude().getSpin();
144 
145         // check spin is consistent with attitude evolution
146         double errorAngleMinus     = Rotation.distance(sMinus.shiftedBy(h).getAttitude().getRotation(),
147                                                        s0.getAttitude().getRotation());
148         double evolutionAngleMinus = Rotation.distance(sMinus.getAttitude().getRotation(),
149                                                        s0.getAttitude().getRotation());
150         Assertions.assertTrue(errorAngleMinus <= 1.0e-6 * evolutionAngleMinus);
151         double errorAnglePlus      = Rotation.distance(s0.getAttitude().getRotation(),
152                                                        sPlus.shiftedBy(-h).getAttitude().getRotation());
153         double evolutionAnglePlus  = Rotation.distance(s0.getAttitude().getRotation(),
154                                                        sPlus.getAttitude().getRotation());
155         Assertions.assertTrue(errorAnglePlus <= 1.0e-6 * evolutionAnglePlus);
156 
157         // compute spin axis using finite differences
158         Rotation rM = sMinus.getAttitude().getRotation();
159         Rotation rP = sPlus.getAttitude().getRotation();
160         Vector3D reference = AngularCoordinates.estimateRate(rM, rP, 2 * h);
161 
162         Assertions.assertEquals(2 * FastMath.PI / reference.getNorm(), 2 * FastMath.PI / spin0.getNorm(), 0.05);
163         Assertions.assertEquals(0.0, FastMath.toDegrees(Vector3D.angle(reference, spin0)), 1.0e-10);
164         Assertions.assertEquals(0.0, FastMath.toDegrees(Vector3D.angle(Vector3D.PLUS_K, spin0)), 1.0e-10);
165 
166     }
167 
168     private <T extends CalculusFieldElement<T>> void checkField(final Field<T> field, final AttitudeProvider provider,
169                                                             final Orbit orbit, final AbsoluteDate date,
170                                                             final Frame frame)
171         {
172         Attitude attitudeD = provider.getAttitude(orbit, date, frame);
173         final FieldOrbit<T> orbitF = new FieldSpacecraftState<>(field, new SpacecraftState(orbit)).getOrbit();
174         final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, date);
175         FieldAttitude<T> attitudeF = provider.getAttitude(orbitF, dateF, frame);
176         Assertions.assertEquals(0.0, Rotation.distance(attitudeD.getRotation(), attitudeF.getRotation().toRotation()), 1.0e-15);
177         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getSpin(), attitudeF.getSpin().toVector3D()), 1.0e-15);
178         Assertions.assertEquals(0.0, Vector3D.distance(attitudeD.getRotationAcceleration(), attitudeF.getRotationAcceleration().toVector3D()), 1.0e-15);
179     }
180 
181     @BeforeEach
182     public void setUp() {
183         Utils.setDataRoot("regular-data");
184     }
185 
186 }
187