1   /* Copyright 2022-2025 Romain Serra
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.events.intervals;
18  
19  import org.junit.jupiter.api.Assertions;
20  import org.junit.jupiter.api.Test;
21  import org.orekit.frames.FramesFactory;
22  import org.orekit.orbits.KeplerianOrbit;
23  import org.orekit.orbits.Orbit;
24  import org.orekit.orbits.PositionAngleType;
25  import org.orekit.propagation.Propagator;
26  import org.orekit.propagation.SpacecraftState;
27  import org.orekit.propagation.analytical.KeplerianPropagator;
28  import org.orekit.propagation.events.*;
29  import org.orekit.propagation.events.handlers.StopOnEvent;
30  import org.orekit.time.AbsoluteDate;
31  import org.orekit.utils.Constants;
32  
33  class ApsideDetectionAdaptableIntervalFactoryTest {
34  
35      private static final EventDetectionSettings DEFAULT_SETTINGS = EventDetectionSettings.getDefaultEventDetectionSettings();
36  
37      @Test
38      void testGetForwardApsideDetectionAdaptableInterval() {
39          // GIVEN
40          final Orbit initialOrbit = createOrbit(1.);
41          final AdaptableInterval forwardAdaptableInterval = ApsideDetectionAdaptableIntervalFactory
42                  .getApsideDetectionAdaptableInterval();
43          // WHEN
44          final double value = forwardAdaptableInterval.currentInterval(new SpacecraftState(initialOrbit), true);
45          // THEN
46          Assertions.assertTrue(value <= initialOrbit.getKeplerianPeriod() / 2);
47      }
48  
49      @Test
50      void testGetBackwardApsideDetectionAdaptableInterval() {
51          // GIVEN
52          final Orbit initialOrbit = createOrbit(1.);
53          final AdaptableInterval backwardAdaptableInterval = ApsideDetectionAdaptableIntervalFactory
54                  .getApsideDetectionAdaptableInterval();
55          // WHEN
56          final double value = backwardAdaptableInterval.currentInterval(new SpacecraftState(initialOrbit), true);
57          // THEN
58          Assertions.assertTrue(value <= initialOrbit.getKeplerianPeriod() / 2);
59      }
60  
61      @Test
62      void testGetForwardPeriapsisDetectionAdaptableInterval() {
63          // GIVEN
64          final Orbit initialOrbit = createOrbit(1.);
65          final EventSlopeFilter<ApsideDetector> periapsisDetector = createPeriapsisDetector(initialOrbit);
66          final AdaptableInterval forwardAdaptableInterval = ApsideDetectionAdaptableIntervalFactory
67                  .getPeriapsisDetectionAdaptableInterval();
68          final AdaptableIntervalWithCounter forwardAdaptableIntervalWithCounter = new AdaptableIntervalWithCounter(
69                  forwardAdaptableInterval);
70          final Propagator propagator = createPropagatorWithDetector(initialOrbit,
71                  periapsisDetector.withDetectionSettings(DEFAULT_SETTINGS.withMaxCheckInterval(forwardAdaptableIntervalWithCounter)));
72          // WHEN
73          final AbsoluteDate targetDate = initialOrbit.getDate().shiftedBy(initialOrbit.getKeplerianPeriod() * 2);
74          final SpacecraftState terminalState = propagator.propagate(targetDate);
75          // THEN
76          Assertions.assertNotEquals(targetDate, terminalState.getDate());
77          final int countWithDefaultAdaptableInterval = countWithConstantAdaptableInterval(initialOrbit, targetDate,
78                  periapsisDetector);
79          Assertions.assertTrue(countWithDefaultAdaptableInterval > forwardAdaptableIntervalWithCounter.count);
80      }
81  
82      @Test
83      void testGetBackwardPeriapsisDetectionAdaptableInterval() {
84          // GIVEN
85          final Orbit initialOrbit = createOrbit(6.);
86          final EventSlopeFilter<ApsideDetector> periapsisDetector = createPeriapsisDetector(initialOrbit);
87          final AdaptableInterval backwardAdaptableInterval = ApsideDetectionAdaptableIntervalFactory
88                  .getPeriapsisDetectionAdaptableInterval();
89          final AdaptableIntervalWithCounter backwardAdaptableIntervalWithCounter = new AdaptableIntervalWithCounter(
90                  backwardAdaptableInterval);
91          final Propagator propagator = createPropagatorWithDetector(initialOrbit,
92                  periapsisDetector.withDetectionSettings(DEFAULT_SETTINGS.withMaxCheckInterval(backwardAdaptableIntervalWithCounter)));
93          // WHEN
94          final AbsoluteDate targetDate = initialOrbit.getDate().shiftedBy(-initialOrbit.getKeplerianPeriod() * 2);
95          final SpacecraftState terminalState = propagator.propagate(targetDate);
96          // THEN
97          Assertions.assertNotEquals(targetDate, terminalState.getDate());
98          final int countWithDefaultAdaptableInterval = countWithConstantAdaptableInterval(initialOrbit, targetDate,
99                  periapsisDetector);
100         Assertions.assertTrue(countWithDefaultAdaptableInterval > backwardAdaptableIntervalWithCounter.count);
101     }
102 
103     @Test
104     void testGetForwardApoapsisDetectionAdaptableInterval() {
105         // GIVEN
106         final Orbit initialOrbit = createOrbit(4.);
107         final EventSlopeFilter<ApsideDetector> apoapsisDetector = createApoapsisDetector(initialOrbit);
108         final AdaptableInterval forwardAdaptableInterval = ApsideDetectionAdaptableIntervalFactory
109                 .getApoapsisDetectionAdaptableInterval();
110         final AdaptableIntervalWithCounter forwardAdaptableIntervalWithCounter = new AdaptableIntervalWithCounter(
111                 forwardAdaptableInterval);
112         final Propagator propagator = createPropagatorWithDetector(initialOrbit,
113                 apoapsisDetector.withDetectionSettings(DEFAULT_SETTINGS.withMaxCheckInterval(forwardAdaptableIntervalWithCounter)));
114         // WHEN
115         final AbsoluteDate targetDate = initialOrbit.getDate().shiftedBy(initialOrbit.getKeplerianPeriod() * 2);
116         final SpacecraftState terminalState = propagator.propagate(targetDate);
117         // THEN
118         Assertions.assertNotEquals(targetDate, terminalState.getDate());
119         final int countWithDefaultAdaptableInterval = countWithConstantAdaptableInterval(initialOrbit, targetDate,
120                 apoapsisDetector);
121         Assertions.assertTrue(countWithDefaultAdaptableInterval > forwardAdaptableIntervalWithCounter.count);
122     }
123 
124     @Test
125     void testGetBackwardApoapsisDetectionAdaptableInterval() {
126         // GIVEN
127         final Orbit initialOrbit = createOrbit(3.);
128         final EventSlopeFilter<ApsideDetector> apoapsisDetector = createApoapsisDetector(initialOrbit);
129         final AdaptableInterval backwardAdaptableInterval = ApsideDetectionAdaptableIntervalFactory
130                 .getApoapsisDetectionAdaptableInterval();
131         final AdaptableIntervalWithCounter backwardAdaptableIntervalWithCounter = new AdaptableIntervalWithCounter(
132                 backwardAdaptableInterval);
133         final Propagator propagator = createPropagatorWithDetector(initialOrbit,
134                 apoapsisDetector.withDetectionSettings(DEFAULT_SETTINGS.withMaxCheckInterval(backwardAdaptableIntervalWithCounter)));
135         // WHEN
136         final AbsoluteDate targetDate = initialOrbit.getDate().shiftedBy(-initialOrbit.getKeplerianPeriod() * 2);
137         final SpacecraftState terminalState = propagator.propagate(targetDate);
138         // THEN
139         Assertions.assertNotEquals(targetDate, terminalState.getDate());
140         final int countWithDefaultAdaptableInterval = countWithConstantAdaptableInterval(initialOrbit, targetDate,
141                 apoapsisDetector);
142         Assertions.assertTrue(countWithDefaultAdaptableInterval > backwardAdaptableIntervalWithCounter.count);
143     }
144 
145 
146     private Propagator createPropagatorWithDetector(final Orbit initialOrbit,
147                                                     final EventDetector eventDetector) {
148         final KeplerianPropagator propagator = new KeplerianPropagator(initialOrbit);
149         propagator.addEventDetector(eventDetector);
150         return propagator;
151     }
152 
153     private EventSlopeFilter<ApsideDetector> createPeriapsisDetector(final Orbit initialOrbit) {
154         return new EventSlopeFilter<>(new ApsideDetector(initialOrbit).withHandler(new StopOnEvent()),
155                 FilterType.TRIGGER_ONLY_DECREASING_EVENTS);
156     }
157 
158     private EventSlopeFilter<ApsideDetector> createApoapsisDetector(final Orbit initialOrbit) {
159         return new EventSlopeFilter<>(new ApsideDetector(initialOrbit).withHandler(new StopOnEvent()),
160                 FilterType.TRIGGER_ONLY_INCREASING_EVENTS);
161     }
162 
163     private AdaptableInterval getConstantAdaptableInterval() {
164         return (state, isForward) -> state.getOrbit().getKeplerianPeriod() / 3.;
165     }
166 
167     private Orbit createOrbit(final double meanAnomaly) {
168         return new KeplerianOrbit(1e7, 0.001, 1., 2., 3., meanAnomaly, PositionAngleType.MEAN,
169                 FramesFactory.getGCRF(), AbsoluteDate.ARBITRARY_EPOCH, Constants.EGM96_EARTH_MU);
170     }
171 
172     private int countWithConstantAdaptableInterval(final Orbit initialOrbit, final AbsoluteDate targetDate,
173                                                    final EventSlopeFilter<ApsideDetector> apsideDetector) {
174         final AdaptableIntervalWithCounter adaptableIntervalWithCounter = new AdaptableIntervalWithCounter(
175                 getConstantAdaptableInterval());
176         final Propagator otherPropagator = createPropagatorWithDetector(initialOrbit,
177                 apsideDetector.withDetectionSettings(DEFAULT_SETTINGS.withMaxCheckInterval(adaptableIntervalWithCounter)));
178         otherPropagator.propagate(targetDate);
179         return adaptableIntervalWithCounter.count;
180     }
181 
182     private static class AdaptableIntervalWithCounter implements AdaptableInterval {
183 
184         private final AdaptableInterval interval;
185         int count = 0;
186 
187         AdaptableIntervalWithCounter(final AdaptableInterval adaptableInterval) {
188             this.interval = adaptableInterval;
189         }
190 
191         @Override
192         public double currentInterval(SpacecraftState state, boolean isForward) {
193             count++;
194             return interval.currentInterval(state, isForward);
195         }
196     }
197 
198 }