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.forces;
18  
19  import org.hipparchus.Field;
20  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.util.Binary64;
23  import org.hipparchus.util.Binary64Field;
24  import org.hipparchus.util.FastMath;
25  import org.junit.jupiter.api.Assertions;
26  import org.junit.jupiter.api.BeforeEach;
27  import org.junit.jupiter.api.Test;
28  import org.orekit.Utils;
29  import org.orekit.attitudes.LofOffset;
30  import org.orekit.bodies.CelestialBody;
31  import org.orekit.bodies.CelestialBodyFactory;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.frames.FramesFactory;
34  import org.orekit.frames.LOFType;
35  import org.orekit.orbits.CircularOrbit;
36  import org.orekit.orbits.Orbit;
37  import org.orekit.orbits.PositionAngleType;
38  import org.orekit.propagation.FieldSpacecraftState;
39  import org.orekit.propagation.Propagator;
40  import org.orekit.propagation.SpacecraftState;
41  import org.orekit.propagation.analytical.EcksteinHechlerPropagator;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.time.DateComponents;
44  import org.orekit.time.TimeComponents;
45  import org.orekit.time.TimeScalesFactory;
46  
47  public class SlewingPanelTest {
48  
49      @Test
50      void testCorrectFixedRate() {
51  
52          AbsoluteDate initialDate = propagator.getInitialState().getDate();
53          CelestialBody sun = CelestialBodyFactory.getSun();
54          Panel solarArray = new SlewingPanel(Vector3D.PLUS_J, propagator.getInitialState().getOrbit().getKeplerianMeanMotion(),
55                                              initialDate, new Vector3D(0.46565509814462996, 0.0,  0.884966287251619),
56                                              20.0, 0.0, 0.0, 0.0, 0.0);
57  
58          for (double dt = 0; dt < 4000; dt += 60) {
59  
60              SpacecraftState state = propagator.propagate(initialDate.shiftedBy(dt));
61  
62              Vector3D sunInert = sun.getPosition(initialDate, state.getFrame());
63              Vector3D momentum = state.getPVCoordinates().getMomentum();
64              double sunElevation = FastMath.PI / 2 - Vector3D.angle(sunInert, momentum);
65              Assertions.assertEquals(15.1, FastMath.toDegrees(sunElevation), 0.1);
66  
67              Vector3D n = solarArray.getNormal(state);
68              Assertions.assertEquals(0.0, n.getY(), 1.0e-10);
69  
70              // normal misalignment should be entirely due to sun being out of orbital plane
71              Vector3D sunSat = state.getAttitude().getRotation().applyTo(sunInert);
72              double misAlignment = Vector3D.angle(sunSat, n);
73              Assertions.assertEquals(sunElevation, misAlignment, 1.0e-3);
74  
75          }
76      }
77  
78      @Test
79      void testTooSlowFixedRate() {
80  
81              AbsoluteDate initialDate = propagator.getInitialState().getDate();
82              CelestialBody sun = CelestialBodyFactory.getSun();
83              Panel solarArray = new SlewingPanel(Vector3D.PLUS_J,
84                                                  0.1 * propagator.getInitialState().getOrbit().getKeplerianMeanMotion(),
85                                                  initialDate, new Vector3D(0.46565509814462996, 0.0,  0.884966287251619),
86                                                  20.0, 0.0, 0.0, 0.0, 0.0);
87  
88              double maxDelta = 0;
89              for (double dt = 0; dt < 4000; dt += 60) {
90  
91                  SpacecraftState state = propagator.propagate(initialDate.shiftedBy(dt));
92  
93                  Vector3D sunInert = sun.getPosition(initialDate, state.getFrame());
94                  Vector3D momentum = state.getPVCoordinates().getMomentum();
95                  double sunElevation = FastMath.PI / 2 - Vector3D.angle(sunInert, momentum);
96                  Assertions.assertEquals(15.1, FastMath.toDegrees(sunElevation), 0.1);
97  
98                  Vector3D n = solarArray.getNormal(state);
99                  Assertions.assertEquals(0.0, n.getY(), 1.0e-10);
100 
101                 // normal misalignment should become very large as solar array rotation is plain wrong
102                 Vector3D sunSat = state.getAttitude().getRotation().applyTo(sunInert);
103                 double misAlignment = Vector3D.angle(sunSat, n);
104                 maxDelta = FastMath.max(maxDelta, FastMath.abs(sunElevation - misAlignment));
105 
106             }
107             Assertions.assertTrue(FastMath.toDegrees(maxDelta) > 120.0);
108 
109     }
110 
111     @Test
112     void testNormalFixedRateDouble() {
113         AbsoluteDate initialDate = propagator.getInitialState().getDate();
114         SlewingPanel panel = new SlewingPanel(Vector3D.PLUS_J, 1.0e-3,
115                                               initialDate, Vector3D.PLUS_K, 20.0, 0.0, 0.0, 1.0, 0.0);
116         for (double dt = 0; dt < 4000; dt += 60) {
117             AbsoluteDate date = initialDate.shiftedBy(dt);
118             SpacecraftState state = propagator.propagate(date);
119             Vector3D normal = panel.getNormal(state);
120             Assertions.assertEquals(0, Vector3D.dotProduct(normal, Vector3D.PLUS_J), 1.0e-16);
121         }
122     }
123 
124     @Test
125     void testNormalFixedRateField() {
126         AbsoluteDate initialDate = propagator.getInitialState().getDate();
127         SlewingPanel panel = new SlewingPanel(Vector3D.PLUS_J, 1.0e-3,
128                                               initialDate, Vector3D.PLUS_K, 20.0, 0.0, 0.0, 1.0, 0.0);
129         Field<Binary64> field = Binary64Field.getInstance();
130         for (double dt = 0; dt < 4000; dt += 60) {
131             AbsoluteDate date = initialDate.shiftedBy(dt);
132             FieldSpacecraftState<Binary64> fState = new FieldSpacecraftState<>(field, propagator.propagate(date));
133             FieldVector3D<Binary64> normal = panel.getNormal(fState);
134             Assertions.assertEquals(0, FieldVector3D.dotProduct(normal, Vector3D.PLUS_J).getReal(), 1.0e-16);
135         }
136     }
137 
138     @Test
139     @BeforeEach
140     public void setUp() {
141         try {
142         Utils.setDataRoot("regular-data");
143         mu  = 3.9860047e14;
144         double ae  = 6.378137e6;
145         double c20 = -1.08263e-3;
146         double c30 = 2.54e-6;
147         double c40 = 1.62e-6;
148         double c50 = 2.3e-7;
149         double c60 = -5.5e-7;
150 
151         AbsoluteDate date = new AbsoluteDate(new DateComponents(1970, 7, 1),
152                                              new TimeComponents(13, 59, 27.816),
153                                              TimeScalesFactory.getUTC());
154 
155         // Satellite position as circular parameters, raan chosen to have sun elevation with
156         // respect to orbit plane roughly evolving roughly from 15 to 15.2 degrees in the test range
157         Orbit circ =
158             new CircularOrbit(7178000.0, 0.5e-4, -0.5e-4, FastMath.toRadians(50.), FastMath.toRadians(280),
159                                    FastMath.toRadians(10.0), PositionAngleType.MEAN,
160                                    FramesFactory.getEME2000(), date, mu);
161         propagator =
162             new EcksteinHechlerPropagator(circ,
163                                           new LofOffset(circ.getFrame(), LOFType.LVLH_CCSDS),
164                                           ae, mu, c20, c30, c40, c50, c60);
165         } catch (OrekitException oe) {
166             Assertions.fail(oe.getLocalizedMessage());
167         }
168     }
169 
170     private double mu;
171     private Propagator propagator;
172 
173 }