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