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.events;
18  
19  import org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.ode.events.Action;
21  import org.hipparchus.ode.nonstiff.AdaptiveStepsizeIntegrator;
22  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
23  import org.junit.jupiter.api.AfterEach;
24  import org.junit.jupiter.api.Assertions;
25  import org.junit.jupiter.api.BeforeEach;
26  import org.junit.jupiter.api.Test;
27  import org.mockito.Mockito;
28  import org.orekit.Utils;
29  import org.orekit.errors.OrekitException;
30  import org.orekit.frames.FramesFactory;
31  import org.orekit.orbits.EquinoctialOrbit;
32  import org.orekit.orbits.Orbit;
33  import org.orekit.orbits.OrbitType;
34  import org.orekit.propagation.Propagator;
35  import org.orekit.propagation.SpacecraftState;
36  import org.orekit.propagation.analytical.tle.TLE;
37  import org.orekit.propagation.analytical.tle.TLEPropagator;
38  import org.orekit.propagation.events.handlers.ContinueOnEvent;
39  import org.orekit.propagation.events.handlers.EventHandler;
40  import org.orekit.propagation.events.handlers.StopOnEvent;
41  import org.orekit.propagation.integration.AdditionalDerivativesProvider;
42  import org.orekit.propagation.integration.CombinedDerivatives;
43  import org.orekit.propagation.numerical.NumericalPropagator;
44  import org.orekit.propagation.sampling.OrekitStepInterpolator;
45  import org.orekit.time.AbsoluteDate;
46  import org.orekit.time.TimeScalesFactory;
47  import org.orekit.utils.PVCoordinates;
48  
49  import java.time.Instant;
50  import java.time.LocalDateTime;
51  import java.time.ZoneId;
52  
53  public class DateDetectorTest {
54  
55      private int evtno = 0;
56      private double maxCheck;
57      private double threshold;
58      private double dt;
59      private Orbit iniOrbit;
60      private AbsoluteDate iniDate;
61      private AbsoluteDate nodeDate;
62      private DateDetector dateDetector;
63      private NumericalPropagator propagator;
64  
65      @Test
66      public void testIssue1676() {
67          final double expectedMinGap = 0.001;
68          Assertions.assertEquals(expectedMinGap, new DateDetector(expectedMinGap, iniDate).getMinGap(), 1.0e-10);
69  
70      }
71  
72      @Test
73      public void testSimpleTimer() {
74          DateDetector dateDetector = new DateDetector(iniDate.shiftedBy(2.0*dt)).
75                                      withMaxCheck(maxCheck).
76                                      withThreshold(threshold);
77          Assertions.assertEquals(2 * dt, dateDetector.getDate().durationFrom(iniDate), 1.0e-10);
78          propagator.addAdditionalDerivativesProvider(new AdditionalDerivativesProvider() {
79              public String   getName()                      { return "dummy"; }
80              public int      getDimension()                 { return 1; }
81              public CombinedDerivatives combinedDerivatives(SpacecraftState s) {
82                  return new CombinedDerivatives(new double[1], null);
83              }
84          });
85          propagator.setInitialState(propagator.getInitialState().addAdditionalData("dummy", new double[1]));
86          propagator.getMultiplexer().add(interpolator -> {
87              SpacecraftState prev = interpolator.getPreviousState();
88              SpacecraftState curr = interpolator.getCurrentState();
89              double dt = curr.getDate().durationFrom(prev.getDate());
90              OrekitStepInterpolator restricted =
91                              interpolator.restrictStep(prev.shiftedBy(dt * +0.25),
92                                                        curr.shiftedBy(dt * -0.25));
93              SpacecraftState restrictedPrev = restricted.getPreviousState();
94              SpacecraftState restrictedCurr = restricted.getCurrentState();
95              double restrictedDt = restrictedCurr.getDate().durationFrom(restrictedPrev.getDate());
96              Assertions.assertEquals(dt * 0.5, restrictedDt, 1.0e-10);
97          });
98          propagator.setOrbitType(OrbitType.EQUINOCTIAL);
99          propagator.addEventDetector(dateDetector);
100         final SpacecraftState finalState = propagator.propagate(iniDate.shiftedBy(100.*dt));
101 
102         Assertions.assertEquals(2.0*dt, finalState.getDate().durationFrom(iniDate), threshold);
103     }
104 
105     @Test
106     void testDefaultDetectionSettings() {
107         // GIVEN
108         final SpacecraftState mockedState = Mockito.mock(SpacecraftState.class);
109         // WHEN
110         final DateDetector detector = new DateDetector();
111         // THEN
112         Assertions.assertEquals(DateDetector.DEFAULT_MAX_ITER, detector.getDetectionSettings().getMaxIterationCount());
113         Assertions.assertEquals(DateDetector.DEFAULT_THRESHOLD, detector.getDetectionSettings().getThreshold());
114         Assertions.assertEquals(DateDetector.DEFAULT_MAX_CHECK,
115                 detector.getMaxCheckInterval().currentInterval(mockedState, true));
116     }
117 
118     @Test
119     public void testEmbeddedTimer() {
120         dateDetector = new DateDetector();
121         Assertions.assertNull(dateDetector.getDate());
122         EventDetector nodeDetector = new NodeDetector(iniOrbit, iniOrbit.getFrame()).
123                 withHandler(new ContinueOnEvent() {
124                     public Action eventOccurred(SpacecraftState s, EventDetector nd, boolean increasing) {
125                         if (increasing) {
126                             nodeDate = s.getDate();
127                             dateDetector.addEventDate(nodeDate.shiftedBy(dt));
128                         }
129                         return Action.CONTINUE;
130                     }
131                 });
132 
133         propagator.addEventDetector(nodeDetector);
134         propagator.addEventDetector(dateDetector);
135         final SpacecraftState finalState = propagator.propagate(iniDate.shiftedBy(100.*dt));
136 
137         Assertions.assertEquals(dt, finalState.getDate().durationFrom(nodeDate), threshold);
138     }
139 
140     @Test
141     public void testAutoEmbeddedTimer() {
142         dateDetector = new DateDetector(iniDate.shiftedBy(-dt)).
143                         withMaxCheck(maxCheck).
144                         withThreshold(threshold).
145                         withHandler(new ContinueOnEvent() {
146                             public Action eventOccurred(SpacecraftState s, EventDetector dd,  boolean increasing) {
147                                 AbsoluteDate nextDate = s.getDate().shiftedBy(-dt);
148                                 ((DateDetector) dd).addEventDate(nextDate);
149                                 ++evtno;
150                                 return Action.CONTINUE;
151                             }
152                         });
153         propagator.addEventDetector(dateDetector);
154         propagator.propagate(iniDate.shiftedBy(-100.*dt));
155 
156         Assertions.assertEquals(100, evtno);
157     }
158 
159     @Test
160     public void testExceptionTimer() {
161         Assertions.assertThrows(IllegalArgumentException.class, () -> {
162             dateDetector = new DateDetector(iniDate.shiftedBy(dt)).
163                             withMaxCheck(maxCheck).
164                             withMinGap(maxCheck).
165                             withThreshold(threshold).
166                             withHandler(new ContinueOnEvent() {
167                                 public Action eventOccurred(SpacecraftState s, EventDetector dd, boolean increasing) {
168                                     double step = (evtno % 2 == 0) ? 2.*maxCheck : maxCheck/2.;
169                                     AbsoluteDate nextDate = s.getDate().shiftedBy(step);
170                                     ((DateDetector) dd).addEventDate(nextDate);
171                                     ++evtno;
172                                     return Action.CONTINUE;
173                                 }
174                             });
175             propagator.addEventDetector(dateDetector);
176             propagator.propagate(iniDate.shiftedBy(100.*dt));
177         });
178     }
179 
180     /**
181      * Check that a generic event handler can be used with an event detector.
182      */
183     @Test
184     public void testGenericHandler() {
185         //setup
186         dateDetector = new DateDetector(iniDate.shiftedBy(dt)).
187                         withMaxCheck(maxCheck).
188                         withThreshold(threshold);
189         // generic event handler that works with all detectors.
190         EventHandler handler = new EventHandler() {
191             @Override
192             public Action eventOccurred(SpacecraftState s,
193                                         EventDetector detector,
194                                         boolean increasing)
195                     {
196                 Assertions.assertSame(dateDetector, detector);
197                 return Action.STOP;
198             }
199 
200             @Override
201             public SpacecraftState resetState(EventDetector detector,
202                                               SpacecraftState oldState)
203                     {
204                 throw new RuntimeException("Should not be called");
205             }
206         };
207 
208         //action
209         dateDetector = dateDetector.withHandler(handler);
210         propagator.addEventDetector(dateDetector);
211         SpacecraftState finalState = propagator.propagate(iniDate.shiftedBy(100 * dt));
212 
213         //verify
214         Assertions.assertEquals(dt, finalState.getDate().durationFrom(iniDate), threshold);
215     }
216 
217     @Test
218     public void testIssue935() {
219 
220         // startTime, endTime
221         long start = 1570802400000L;
222         long end = 1570838399000L;
223 
224         // Build propagator
225         TLE tle = new TLE("1 43197U 18015F   19284.07336221  .00000533  00000-0  24811-4 0  9998",
226             "2 43197  97.4059  50.1428 0017543 265.5429 181.0400 15.24136761 93779");
227         Propagator propagator = TLEPropagator.selectExtrapolator(tle);
228 
229         // Max check to seconds
230         int maxCheck = (int) ((end - start) / 2000);
231         DateDetector dateDetector = new DateDetector(getAbsoluteDateFromTimestamp(start)).
232                         withMaxCheck(maxCheck).
233                         withThreshold(1.0e-6).
234                         withHandler(new StopOnEvent());
235         dateDetector.addEventDate(getAbsoluteDateFromTimestamp(end));
236 
237         // Add event detectors to orbit
238         propagator.addEventDetector(dateDetector);
239 
240         // Propagate
241         final AbsoluteDate startDate = getAbsoluteDateFromTimestamp(start);
242         final AbsoluteDate endDate   = getAbsoluteDateFromTimestamp(end);
243         SpacecraftState lastState = propagator.propagate(startDate, endDate.shiftedBy(1));
244         Assertions.assertEquals(0.0, lastState.getDate().durationFrom(endDate), 1.0e-15);
245 
246     }
247 
248     public static AbsoluteDate getAbsoluteDateFromTimestamp(final long timestamp) {
249         LocalDateTime utcDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp),
250                                                         ZoneId.of("UTC"));
251         int year = utcDate.getYear();
252         int month = utcDate.getMonthValue();
253         int day = utcDate.getDayOfMonth();
254         int hour = utcDate.getHour();
255         int minute = utcDate.getMinute();
256         double second = utcDate.getSecond();
257         double millis = utcDate.getNano() / 1e9;
258         return new AbsoluteDate(year, month, day, hour, minute, second, TimeScalesFactory.getUTC()).shiftedBy(millis);
259     }
260 
261     @BeforeEach
262     public void setUp() {
263         try {
264             Utils.setDataRoot("regular-data");
265             final double mu = 3.9860047e14;
266             final Vector3D position  = new Vector3D(-6142438.668, 3492467.560, -25767.25680);
267             final Vector3D velocity  = new Vector3D(505.8479685, 942.7809215, 7435.922231);
268             iniDate  = new AbsoluteDate(1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
269             iniOrbit = new EquinoctialOrbit(new PVCoordinates(position, velocity),
270                                             FramesFactory.getEME2000(), iniDate, mu);
271             SpacecraftState initialState = new SpacecraftState(iniOrbit);
272             double[] absTolerance = {
273                 0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
274             };
275             double[] relTolerance = {
276                 1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
277             };
278             AdaptiveStepsizeIntegrator integrator =
279                 new DormandPrince853Integrator(0.001, 1000, absTolerance, relTolerance);
280             integrator.setInitialStepSize(60);
281             propagator = new NumericalPropagator(integrator);
282             propagator.setInitialState(initialState);
283             dt = 60.;
284             maxCheck  = 10.;
285             threshold = 10.e-10;
286             evtno = 0;
287         } catch (OrekitException oe) {
288             Assertions.fail(oe.getLocalizedMessage());
289         }
290     }
291 
292     @AfterEach
293     public void tearDown() {
294         iniDate = null;
295         propagator = null;
296         dateDetector = null;
297     }
298 
299 }