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.propagation.analytical;
18  
19  import org.hamcrest.CoreMatchers;
20  import org.hamcrest.MatcherAssert;
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.junit.jupiter.api.Assertions;
23  import org.junit.jupiter.api.BeforeEach;
24  import org.junit.jupiter.api.Test;
25  import org.mockito.Mockito;
26  import org.orekit.OrekitMatchers;
27  import org.orekit.Utils;
28  import org.orekit.attitudes.FrameAlignedProvider;
29  import org.orekit.attitudes.LofOffset;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.frames.Frame;
32  import org.orekit.frames.FramesFactory;
33  import org.orekit.frames.LOFType;
34  import org.orekit.orbits.KeplerianOrbit;
35  import org.orekit.orbits.Orbit;
36  import org.orekit.orbits.PositionAngleType;
37  import org.orekit.propagation.BoundedPropagator;
38  import org.orekit.propagation.EphemerisGenerator;
39  import org.orekit.propagation.SpacecraftState;
40  import org.orekit.time.AbsoluteDate;
41  import org.orekit.utils.Constants;
42  import org.orekit.utils.TimeSpanMap;
43  
44  import java.util.ArrayList;
45  import java.util.Arrays;
46  import java.util.Collections;
47  import java.util.List;
48  import java.util.NavigableMap;
49  import java.util.TreeMap;
50  
51  /**
52   * Tests for {@link AggregateBoundedPropagator}.
53   *
54   * @author Evan Ward
55   */
56  public class AggregateBoundedPropagatorTest {
57  
58      public static final Frame frame = FramesFactory.getGCRF();
59  
60      /** Set Orekit data. */
61      @BeforeEach
62      public void setUp() {
63          Utils.setDataRoot("regular-data");
64      }
65  
66      /**
67       * Check {@link AbstractAnalyticalPropagator#propagateOrbit(AbsoluteDate)} when the
68       * constituent propagators are exactly adjacent.
69       */
70      @Test
71      public void testAdjacent() {
72          // setup
73          AbsoluteDate date = AbsoluteDate.CCSDS_EPOCH;
74          BoundedPropagator p1 = createPropagator(date, date.shiftedBy(10), 0);
75          BoundedPropagator p2 = createPropagator(date.shiftedBy(10), date.shiftedBy(20), 1);
76  
77          // action
78          AggregateBoundedPropagator actual = new AggregateBoundedPropagator(Arrays.asList(p1, p2));
79  
80          //verify
81          int ulps = 0;
82          MatcherAssert.assertThat(actual.getFrame(), CoreMatchers.is(p1.getFrame()));
83          MatcherAssert.assertThat(actual.getMinDate(), CoreMatchers.is(date));
84          MatcherAssert.assertThat(actual.getMaxDate(), CoreMatchers.is(date.shiftedBy(20)));
85          MatcherAssert.assertThat(
86                  actual.propagate(date).getPVCoordinates(),
87                  OrekitMatchers.pvCloseTo(p1.propagate(date).getPVCoordinates(), ulps));
88          MatcherAssert.assertThat(
89                  actual.propagate(date.shiftedBy(5)).getPVCoordinates(),
90                  OrekitMatchers.pvCloseTo(p1.propagate(date.shiftedBy(5)).getPVCoordinates(), ulps));
91          MatcherAssert.assertThat(
92                  actual.propagate(date.shiftedBy(10)).getPVCoordinates(),
93                  OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(10)).getPVCoordinates(), ulps));
94          MatcherAssert.assertThat(
95                  actual.propagate(date.shiftedBy(15)).getPVCoordinates(),
96                  OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(15)).getPVCoordinates(), ulps));
97          MatcherAssert.assertThat(
98                  actual.propagate(date.shiftedBy(20)).getPVCoordinates(),
99                  OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(20)).getPVCoordinates(), ulps));
100 
101         for (TimeSpanMap.Span<BoundedPropagator> span = actual.getPropagatorsMap().getFirstNonNullSpan();
102              span != null;
103              span = span.next()) {
104             Assertions.assertEquals(span.getStart(), span.getData().getMinDate());
105         }
106 
107     }
108 
109     /**
110      * Check {@link AbstractAnalyticalPropagator#propagateOrbit(AbsoluteDate)} when the
111      * constituent propagators overlap.
112      */
113     @Test
114     public void testOverlap() {
115         // setup
116         AbsoluteDate date = AbsoluteDate.CCSDS_EPOCH;
117         BoundedPropagator p1 = createPropagator(date, date.shiftedBy(25), 0);
118         BoundedPropagator p2 = createPropagator(date.shiftedBy(10), date.shiftedBy(20), 1);
119 
120         // action
121         BoundedPropagator actual = new AggregateBoundedPropagator(Arrays.asList(p1, p2));
122 
123         //verify
124         int ulps = 0;
125         MatcherAssert.assertThat(actual.getFrame(), CoreMatchers.is(p1.getFrame()));
126         MatcherAssert.assertThat(actual.getMinDate(), CoreMatchers.is(date));
127         MatcherAssert.assertThat(actual.getMaxDate(), CoreMatchers.is(date.shiftedBy(20)));
128         MatcherAssert.assertThat(
129                 actual.propagate(date).getPVCoordinates(),
130                 OrekitMatchers.pvCloseTo(p1.propagate(date).getPVCoordinates(), ulps));
131         MatcherAssert.assertThat(
132                 actual.propagate(date.shiftedBy(5)).getPVCoordinates(),
133                 OrekitMatchers.pvCloseTo(p1.propagate(date.shiftedBy(5)).getPVCoordinates(), ulps));
134         MatcherAssert.assertThat(
135                 actual.propagate(date.shiftedBy(10)).getPVCoordinates(),
136                 OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(10)).getPVCoordinates(), ulps));
137         MatcherAssert.assertThat(
138                 actual.propagate(date.shiftedBy(15)).getPVCoordinates(),
139                 OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(15)).getPVCoordinates(), ulps));
140         MatcherAssert.assertThat(
141                 actual.propagate(date.shiftedBy(20)).getPVCoordinates(),
142                 OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(20)).getPVCoordinates(), ulps));
143     }
144 
145     /**
146      * Check {@link AbstractAnalyticalPropagator#propagateOrbit(AbsoluteDate)} with a gap
147      * between the constituent propagators.
148      */
149     @Test
150     public void testGap() {
151         // setup
152         AbsoluteDate date = AbsoluteDate.CCSDS_EPOCH;
153         BoundedPropagator p1 = createPropagator(date, date.shiftedBy(1), 0);
154         BoundedPropagator p2 = createPropagator(date.shiftedBy(10), date.shiftedBy(20), 1);
155 
156         // action
157         BoundedPropagator actual = new AggregateBoundedPropagator(Arrays.asList(p1, p2));
158 
159         //verify
160         int ulps = 0;
161         MatcherAssert.assertThat(actual.getFrame(), CoreMatchers.is(p1.getFrame()));
162         MatcherAssert.assertThat(actual.getMinDate(), CoreMatchers.is(date));
163         MatcherAssert.assertThat(actual.getMaxDate(), CoreMatchers.is(date.shiftedBy(20)));
164         MatcherAssert.assertThat(
165                 actual.propagate(date).getPVCoordinates(),
166                 OrekitMatchers.pvCloseTo(p1.propagate(date).getPVCoordinates(), ulps));
167         MatcherAssert.assertThat(
168                 actual.propagate(date.shiftedBy(10)).getPVCoordinates(),
169                 OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(10)).getPVCoordinates(), ulps));
170         MatcherAssert.assertThat(
171                 actual.propagate(date.shiftedBy(15)).getPVCoordinates(),
172                 OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(15)).getPVCoordinates(), ulps));
173         MatcherAssert.assertThat(
174                 actual.propagate(date.shiftedBy(20)).getPVCoordinates(),
175                 OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(20)).getPVCoordinates(), ulps));
176         try {
177             // may or may not throw an exception depending on the type of propagator.
178             MatcherAssert.assertThat(
179                     actual.propagate(date.shiftedBy(5)).getPVCoordinates(),
180                     OrekitMatchers.pvCloseTo(p1.propagate(date.shiftedBy(5)).getPVCoordinates(), ulps));
181         } catch (OrekitException e) {
182             // expected
183         }
184     }
185 
186     @Test
187     public void testOutsideBounds() {
188         // setup
189         AbsoluteDate date = AbsoluteDate.CCSDS_EPOCH;
190         BoundedPropagator p1 = createPropagator(date, date.shiftedBy(10), 0);
191         BoundedPropagator p2 = createPropagator(date.shiftedBy(10), date.shiftedBy(20), 1);
192 
193         // action
194         BoundedPropagator actual = new AggregateBoundedPropagator(Arrays.asList(p1, p2));
195 
196         // verify
197         int ulps = 0;
198         // before bound of first propagator
199         try {
200             // may or may not throw an exception depending on the type of propagator.
201             MatcherAssert.assertThat(
202                     actual.propagate(date.shiftedBy(-60)).getPVCoordinates(),
203                     OrekitMatchers.pvCloseTo(p1.propagate(date.shiftedBy(-60)).getPVCoordinates(), ulps));
204         } catch (OrekitException e) {
205             // expected
206         }
207         try {
208             // may or may not throw an exception depending on the type of propagator.
209             MatcherAssert.assertThat(
210                     actual.getPVCoordinates(date.shiftedBy(-60), frame),
211                     OrekitMatchers.pvCloseTo(p1.propagate(date.shiftedBy(-60)).getPVCoordinates(), ulps));
212         } catch (OrekitException e) {
213             // expected
214         }
215         // after bound of last propagator
216         try {
217             // may or may not throw an exception depending on the type of propagator.
218             MatcherAssert.assertThat(
219                     actual.propagate(date.shiftedBy(60)).getPVCoordinates(),
220                     OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(60)).getPVCoordinates(), ulps));
221         } catch (OrekitException e) {
222             // expected
223         }
224         try {
225             // may or may not throw an exception depending on the type of propagator.
226             MatcherAssert.assertThat(
227                     actual.getPVCoordinates(date.shiftedBy(60), frame),
228                     OrekitMatchers.pvCloseTo(p2.propagate(date.shiftedBy(60)).getPVCoordinates(), ulps));
229         } catch (OrekitException e) {
230             // expected
231         }
232 
233     }
234 
235     /**
236      * Check that resetting the state is prohibited.
237      */
238     @Test
239     public void testResetState() {
240         // setup
241         AbsoluteDate date = AbsoluteDate.CCSDS_EPOCH;
242         BoundedPropagator p1 = createPropagator(date, date.shiftedBy(10), 0);
243         BoundedPropagator p2 = createPropagator(date.shiftedBy(10), date.shiftedBy(20), 1);
244         SpacecraftState ic = p2.getInitialState();
245 
246         // action
247         BoundedPropagator actual = new AggregateBoundedPropagator(Arrays.asList(p1, p2));
248 
249         // verify
250         try {
251             actual.resetInitialState(ic);
252             Assertions.fail("Expected Exception");
253         } catch (OrekitException e) {
254             // expected
255         }
256     }
257 
258     /**
259      * Check that creating an aggregate propagator from an empty list of propagators is
260      * prohibited.
261      */
262     @Test
263     public void testEmptyList() {
264         // action + verify
265         try {
266             new AggregateBoundedPropagator(Collections.emptyList());
267             Assertions.fail("Expected Exception");
268         } catch (OrekitException e) {
269             // expected
270         }
271     }
272 
273     /**
274      * Check
275      * {@link
276      * AggregateBoundedPropagator#AggregateBoundedPropagator(NavigableMap,
277      * AbsoluteDate, AbsoluteDate)}.
278      */
279     @Test
280     public void testAggregateBoundedPropagator() {
281         // setup
282         NavigableMap<AbsoluteDate, BoundedPropagator> map = new TreeMap<>();
283         AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
284         AbsoluteDate end = date.shiftedBy(20);
285         BoundedPropagator p1 = createPropagator(date, end, 0);
286         BoundedPropagator p2 = createPropagator(date.shiftedBy(10), end, 0);
287         map.put(date, p1);
288         map.put(date.shiftedBy(10), p2);
289         AbsoluteDate min = date.shiftedBy(-10);
290         AbsoluteDate max = end.shiftedBy(10);
291 
292         // action
293         final BoundedPropagator actual =
294                 new AggregateBoundedPropagator(map, min, max);
295 
296         // verify
297         MatcherAssert.assertThat(actual.getMinDate(), CoreMatchers.is(min));
298         MatcherAssert.assertThat(actual.getMaxDate(), CoreMatchers.is(max));
299         MatcherAssert.assertThat(actual.propagate(date).getPVCoordinates(),
300                 OrekitMatchers.pvCloseTo(p1.propagate(date).getPVCoordinates(), 0));
301         MatcherAssert.assertThat(actual.propagate(end).getPVCoordinates(),
302                 OrekitMatchers.pvCloseTo(p2.propagate(end).getPVCoordinates(), 0));
303         MatcherAssert.assertThat(actual.propagate(min).getPVCoordinates(),
304                 OrekitMatchers.pvCloseTo(p1.propagate(min).getPVCoordinates(), 0));
305     }
306 
307     /**
308      * Create a propagator with the given dates.
309      *
310      * @param start date.
311      * @param end   date.
312      * @param v     true anomaly.
313      * @return a bound propagator with the given dates.
314      */
315     private BoundedPropagator createPropagator(AbsoluteDate start,
316                                                AbsoluteDate end,
317                                                double v) {
318         double gm = Constants.EGM96_EARTH_MU;
319         KeplerianPropagator propagator = new KeplerianPropagator(new KeplerianOrbit(
320                 6778137, 0, 0, 0, 0, v, PositionAngleType.TRUE, frame, start, gm));
321         propagator.setAttitudeProvider(new LofOffset(frame, LOFType.LVLH_CCSDS));
322         final EphemerisGenerator generator = propagator.getEphemerisGenerator();
323         propagator.propagate(start, end);
324         return generator.getGeneratedEphemeris();
325     }
326 
327     @Test
328     void testAttitude() {
329         AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
330         AbsoluteDate end = date.shiftedBy(20);
331         BoundedPropagator p1 = createPropagator(date, end, 0);
332         BoundedPropagator p2 = createPropagator(date.shiftedBy(10), end, 0);
333         final BoundedPropagator actual = new AggregateBoundedPropagator(Arrays.asList(p1, p2));
334 
335         // using the attitude providers from the underlying propagator
336         final SpacecraftState s0 = actual.propagate(date.shiftedBy(15));
337         Assertions.assertEquals(0,
338                                 Vector3D.angle(s0.getPosition(),
339                                                s0.getAttitude().getRotation().applyInverseTo(Vector3D.MINUS_K)),
340                                 3.0e-16);
341 
342         // overriding explicitly the global attitude provider
343         actual.setAttitudeProvider(new FrameAlignedProvider(p1.getInitialState().getFrame()));
344         final SpacecraftState s1 = actual.propagate(date.shiftedBy(15));
345         Assertions.assertEquals(0,
346                                 Vector3D.angle(Vector3D.MINUS_K,
347                                                s1.getAttitude().getRotation().applyInverseTo(Vector3D.MINUS_K)),
348                                 3.0e-16);
349         Assertions.assertEquals(1.570796,
350                                 Vector3D.angle(s1.getPosition(),
351                                                s1.getAttitude().getRotation().applyInverseTo(Vector3D.MINUS_K)),
352                                 1.0e-6);
353 
354     }
355 
356     @Test
357     void testPropagateOrbit() {
358         // GIVEN
359         final AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
360         final SpacecraftState mockedState = Mockito.mock(SpacecraftState.class);
361         final Orbit expectedOrbit = Mockito.mock(Orbit.class);
362         Mockito.when(mockedState.getOrbit()).thenReturn(expectedOrbit);
363         final BoundedPropagator mockedBoundedPropagator = mockBoundedPropagator(date, mockedState);
364         final List<BoundedPropagator> boundedPropagatorList = new ArrayList<>();
365         boundedPropagatorList.add(mockedBoundedPropagator);
366         final AggregateBoundedPropagator propagator = new AggregateBoundedPropagator(boundedPropagatorList);
367 
368         // WHEN
369         final Orbit actualOrbit = propagator.propagateOrbit(date);
370 
371         // THEN
372         Assertions.assertEquals(expectedOrbit, actualOrbit);
373     }
374 
375     @Test
376     void testGetPosition() {
377         // GIVEN
378         final AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
379         final Frame mockedFrame = Mockito.mock(Frame.class);
380         final SpacecraftState mockedState = Mockito.mock(SpacecraftState.class);
381         final Vector3D expectedPosition = new Vector3D(1, 2, 3);
382         Mockito.when(mockedState.getPosition(mockedFrame)).thenReturn(expectedPosition);
383         final BoundedPropagator mockedBoundedPropagator = mockBoundedPropagator(date, mockedState);
384         final List<BoundedPropagator> boundedPropagatorList = new ArrayList<>();
385         boundedPropagatorList.add(mockedBoundedPropagator);
386         final AggregateBoundedPropagator propagator = new AggregateBoundedPropagator(boundedPropagatorList);
387 
388         // WHEN
389         final Vector3D actualPosition = propagator.getPosition(date, mockedFrame);
390 
391         // THEN
392         Assertions.assertEquals(expectedPosition, actualPosition);
393     }
394 
395     private BoundedPropagator mockBoundedPropagator(final AbsoluteDate date, final SpacecraftState state) {
396         final BoundedPropagator mockedBoundedPropagator = Mockito.mock(BoundedPropagator.class);
397         Mockito.when(mockedBoundedPropagator.getMinDate()).thenReturn(AbsoluteDate.PAST_INFINITY);
398         Mockito.when(mockedBoundedPropagator.getMinDate()).thenReturn(AbsoluteDate.FUTURE_INFINITY);
399         Mockito.when(mockedBoundedPropagator.propagate(date)).thenReturn(state);
400         Mockito.when(mockedBoundedPropagator.getInitialState()).thenReturn(state);
401         return mockedBoundedPropagator;
402     }
403 
404 }