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 java.lang.reflect.Array;
20  import java.time.Instant;
21  import java.time.LocalDateTime;
22  import java.time.ZoneId;
23  
24  import org.hipparchus.CalculusFieldElement;
25  import org.hipparchus.Field;
26  import org.hipparchus.complex.Complex;
27  import org.hipparchus.complex.ComplexField;
28  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
29  import org.hipparchus.ode.events.Action;
30  import org.hipparchus.ode.nonstiff.AdaptiveStepsizeFieldIntegrator;
31  import org.hipparchus.ode.nonstiff.DormandPrince853FieldIntegrator;
32  import org.hipparchus.util.Binary64;
33  import org.hipparchus.util.Binary64Field;
34  import org.hipparchus.util.MathArrays;
35  import org.junit.jupiter.api.Assertions;
36  import org.junit.jupiter.api.BeforeEach;
37  import org.junit.jupiter.api.Test;
38  import org.mockito.Mockito;
39  import org.orekit.Utils;
40  import org.orekit.frames.FramesFactory;
41  import org.orekit.orbits.FieldEquinoctialOrbit;
42  import org.orekit.orbits.FieldOrbit;
43  import org.orekit.orbits.OrbitType;
44  import org.orekit.propagation.FieldPropagator;
45  import org.orekit.propagation.FieldSpacecraftState;
46  import org.orekit.propagation.analytical.tle.FieldTLE;
47  import org.orekit.propagation.analytical.tle.FieldTLEPropagator;
48  import org.orekit.propagation.events.handlers.FieldContinueOnEvent;
49  import org.orekit.propagation.events.handlers.FieldEventHandler;
50  import org.orekit.propagation.events.handlers.FieldStopOnEvent;
51  import org.orekit.propagation.integration.FieldAdditionalDerivativesProvider;
52  import org.orekit.propagation.integration.FieldCombinedDerivatives;
53  import org.orekit.propagation.numerical.FieldNumericalPropagator;
54  import org.orekit.propagation.sampling.FieldOrekitStepInterpolator;
55  import org.orekit.time.AbsoluteDate;
56  import org.orekit.time.FieldAbsoluteDate;
57  import org.orekit.time.FieldTimeStamped;
58  import org.orekit.time.TimeScalesFactory;
59  import org.orekit.utils.FieldPVCoordinates;
60  
61  public class FieldDateDetectorTest {
62  
63      private int evtno = 0;
64      private double minGap;
65      private double threshold;
66      private double dt;
67      private double mu;
68      private AbsoluteDate nodeDate;
69  
70      @Test
71      public void testSimpleTimer() {
72          doTestSimpleTimer(Binary64Field.getInstance());
73      }
74  
75      @Test
76      public void testEmbeddedTimer() {
77          doTestEmbeddedTimer(Binary64Field.getInstance());
78      }
79  
80      @Test
81      public void testAutoEmbeddedTimer() {
82          doTestAutoEmbeddedTimer(Binary64Field.getInstance());
83      }
84  
85      @Test
86      public void testExceptionTimer() {
87          Assertions.assertThrows(IllegalArgumentException.class, () -> {
88              doTestExceptionTimer(Binary64Field.getInstance());
89          });
90      }
91  
92      @Test
93      public void testGenericHandler() {
94          doTestGenericHandler(Binary64Field.getInstance());
95      }
96  
97      @Test
98      @SuppressWarnings("unchecked")
99      void testGetDefaultDetectionSettings() {
100         // GIVEN
101         final FieldDateDetector<Complex> fieldDateDetector = new FieldDateDetector<>(FieldAbsoluteDate.getArbitraryEpoch(ComplexField.getInstance()));
102         // WHEN
103         final FieldEventDetectionSettings<Complex> fieldEventDetectionSettings = fieldDateDetector.getDetectionSettings();
104         // THEN
105         Assertions.assertEquals(DateDetector.DEFAULT_THRESHOLD, fieldEventDetectionSettings.getThreshold().getReal());
106         Assertions.assertEquals(DateDetector.DEFAULT_MAX_ITER, fieldEventDetectionSettings.getMaxIterationCount());
107         Assertions.assertEquals(DateDetector.DEFAULT_MAX_CHECK, fieldEventDetectionSettings.getMaxCheckInterval()
108                 .currentInterval(Mockito.mock(FieldSpacecraftState.class), true));
109     }
110 
111     @Test
112     void testConstructor() {
113         // GIVEN
114         final AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
115         final Binary64Field field = Binary64Field.getInstance();
116         final FieldAbsoluteDate<Binary64> fieldDate = new FieldAbsoluteDate<>(field, date);
117         // WHEN
118         final FieldDateDetector<Binary64> fieldDateDetector = new FieldDateDetector<>(fieldDate);
119         // THEN
120         Assertions.assertEquals(fieldDate, fieldDateDetector.getDate());
121         final EventDetectionSettings expectedSettings = new DateDetector().getDetectionSettings();
122         Assertions.assertEquals(expectedSettings.getThreshold(), fieldDateDetector.getThreshold().getReal());
123         Assertions.assertEquals(expectedSettings.getMaxIterationCount(), fieldDateDetector.getMaxIterationCount());
124     }
125 
126     @Test
127     public void testIssue935() {
128         doTestIssue935(Binary64Field.getInstance());
129     }
130 
131     private <T extends CalculusFieldElement<T>> void doTestSimpleTimer(final Field<T> field) {
132         T zero = field.getZero();
133         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add( 3492467.560), zero.add( -25767.25680));
134         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685), zero.add(942.7809215), zero.add(7435.922231));
135         FieldAbsoluteDate<T> iniDate  = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
136         FieldOrbit<T> iniOrbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position, velocity),
137                                                              FramesFactory.getEME2000(), iniDate, zero.add(mu));
138         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(iniOrbit);
139         double[] absTolerance = {
140             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
141         };
142         double[] relTolerance = {
143             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
144         };
145         AdaptiveStepsizeFieldIntegrator<T> integrator =
146             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
147         integrator.setInitialStepSize(60);
148         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
149         propagator.addAdditionalDerivativesProvider(new FieldAdditionalDerivativesProvider<T>() {
150             public String getName()                              { return "dummy"; }
151             public int    getDimension()                         { return 1; }
152             public FieldCombinedDerivatives<T> combinedDerivatives(FieldSpacecraftState<T> s) {
153                 return new FieldCombinedDerivatives<>(MathArrays.buildArray(field, 1), null);
154                 }
155         });
156         propagator.getMultiplexer().add(interpolator -> {
157             FieldSpacecraftState<T> prev = interpolator.getPreviousState();
158             FieldSpacecraftState<T> curr = interpolator.getCurrentState();
159             T dt = curr.getDate().durationFrom(prev.getDate());
160             FieldOrekitStepInterpolator<T> restricted =
161                             interpolator.restrictStep(prev.shiftedBy(dt.multiply(+0.25)),
162                                                       curr.shiftedBy(dt.multiply(-0.25)));
163             FieldSpacecraftState<T> restrictedPrev = restricted.getPreviousState();
164             FieldSpacecraftState<T> restrictedCurr = restricted.getCurrentState();
165             T restrictedDt = restrictedCurr.getDate().durationFrom(restrictedPrev.getDate());
166             Assertions.assertEquals(dt.multiply(0.5).getReal(), restrictedDt.getReal(), 1.0e-10);
167         });
168         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
169         propagator.setInitialState(initialState.addAdditionalData("dummy", MathArrays.buildArray(field, 1)));
170 
171         FieldDateDetector<T>  dateDetector = new FieldDateDetector<>(field, toArray(iniDate.shiftedBy(2.0*dt))).
172                         withMinGap(minGap).withThreshold(field.getZero().newInstance(threshold));
173         Assertions.assertEquals(2 * dt, dateDetector.getDate().durationFrom(iniDate).getReal(), 1.0e-10);
174         propagator.addEventDetector(dateDetector);
175         final FieldSpacecraftState<T> finalState = propagator.propagate(iniDate.shiftedBy(100.*dt));
176 
177         Assertions.assertEquals(2.0*dt, finalState.getDate().durationFrom(iniDate).getReal(), threshold);
178     }
179 
180 
181     private <T extends CalculusFieldElement<T>> void doTestEmbeddedTimer(Field<T> field) {
182         T zero = field.getZero();
183         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add( 3492467.560), zero.add( -25767.25680));
184         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685), zero.add(942.7809215), zero.add(7435.922231));
185         FieldAbsoluteDate<T> iniDate  = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
186         FieldOrbit<T> iniOrbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position, velocity),
187                                                              FramesFactory.getEME2000(), iniDate, zero.add(mu));
188         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(iniOrbit);
189         double[] absTolerance = {
190             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
191         };
192         double[] relTolerance = {
193             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
194         };
195         AdaptiveStepsizeFieldIntegrator<T> integrator =
196             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
197         integrator.setInitialStepSize(60);
198         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
199         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
200         propagator.setInitialState(initialState);
201         @SuppressWarnings("unchecked")
202         FieldDateDetector<T> dateDetector = new FieldDateDetector<>(field, (FieldTimeStamped<T>[]) Array.newInstance(FieldTimeStamped.class, 0)).
203                         withMinGap(minGap).withThreshold(field.getZero().newInstance(threshold));
204         Assertions.assertNull(dateDetector.getDate());
205         FieldEventDetector<T> nodeDetector = new FieldNodeDetector<>(iniOrbit, iniOrbit.getFrame()).
206                 withHandler(new FieldContinueOnEvent<T>() {
207                     public Action eventOccurred(FieldSpacecraftState<T> s, FieldEventDetector<T> nd, boolean increasing) {
208                         if (increasing) {
209                             nodeDate = s.getDate().toAbsoluteDate();
210                             dateDetector.addEventDate(s.getDate().shiftedBy(dt));
211                         }
212                         return Action.CONTINUE;
213                     }
214                 });
215 
216         propagator.addEventDetector(nodeDetector);
217         propagator.addEventDetector(dateDetector);
218         final FieldSpacecraftState<T> finalState = propagator.propagate(iniDate.shiftedBy(100.*dt));
219 
220         Assertions.assertEquals(dt, finalState.getDate().durationFrom(nodeDate).getReal(), threshold);
221     }
222 
223 
224     private <T extends CalculusFieldElement<T>> void doTestAutoEmbeddedTimer(Field<T> field) {
225         T zero = field.getZero();
226         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add( 3492467.560), zero.add( -25767.25680));
227         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685), zero.add(942.7809215), zero.add(7435.922231));
228         FieldAbsoluteDate<T> iniDate  = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
229         FieldOrbit<T> iniOrbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position, velocity),
230                                                              FramesFactory.getEME2000(), iniDate, zero.add(mu));
231         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(iniOrbit);
232         double[] absTolerance = {
233             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
234         };
235         double[] relTolerance = {
236             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
237         };
238         AdaptiveStepsizeFieldIntegrator<T> integrator =
239             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
240         integrator.setInitialStepSize(60);
241         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
242         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
243         propagator.setInitialState(initialState);
244 
245         FieldDateDetector<T> dateDetector = new FieldDateDetector<>(field, toArray(iniDate.shiftedBy(-dt))).
246                         withMinGap(minGap).
247                         withThreshold(field.getZero().newInstance(threshold)).
248                         withHandler(new FieldContinueOnEvent<T >() {
249                             public Action eventOccurred(FieldSpacecraftState<T> s, FieldEventDetector<T>  dd,  boolean increasing) {
250                                 FieldAbsoluteDate<T> nextDate = s.getDate().shiftedBy(-dt);
251                                 ((FieldDateDetector<T>) dd).addEventDate(nextDate);
252                                 ++evtno;
253                                 return Action.CONTINUE;
254                             }
255                         });
256         propagator.addEventDetector(dateDetector);
257         propagator.propagate(iniDate.shiftedBy(-100.*dt));
258 
259         Assertions.assertEquals(100, evtno);
260     }
261 
262     private <T extends CalculusFieldElement<T>> void doTestExceptionTimer(Field<T> field) {
263         T zero = field.getZero();
264         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add( 3492467.560), zero.add( -25767.25680));
265         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685), zero.add(942.7809215), zero.add(7435.922231));
266         FieldAbsoluteDate<T> iniDate  = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
267         FieldOrbit<T> iniOrbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position, velocity),
268                                                              FramesFactory.getEME2000(), iniDate, zero.add(mu));
269         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(iniOrbit);
270         double[] absTolerance = {
271             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
272         };
273         double[] relTolerance = {
274             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
275         };
276         AdaptiveStepsizeFieldIntegrator<T> integrator =
277             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
278         integrator.setInitialStepSize(60);
279         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
280         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
281         propagator.setInitialState(initialState);
282 
283         FieldDateDetector<T> dateDetector = new FieldDateDetector<>(field, toArray(iniDate.shiftedBy(dt))).
284                         withMinGap(minGap).
285                         withThreshold(field.getZero().newInstance(threshold)).
286                         withHandler(new FieldContinueOnEvent<T>() {
287                             public Action eventOccurred(FieldSpacecraftState<T> s, FieldEventDetector<T>  dd, boolean increasing)
288                             {
289                                 double step = (evtno % 2 == 0) ? 2.*minGap : minGap/2.;
290                                 FieldAbsoluteDate<T> nextDate = s.getDate().shiftedBy(step);
291                                 ((FieldDateDetector<T>) dd).addEventDate(nextDate);
292                                 ++evtno;
293                                 return Action.CONTINUE;
294                             }
295                         });
296         propagator.addEventDetector(dateDetector);
297         propagator.propagate(iniDate.shiftedBy(100.*dt));
298     }
299 
300     /**
301      * Check that a generic event handler can be used with an event detector.
302      */
303 
304     private <T extends CalculusFieldElement<T>> void doTestGenericHandler(Field<T> field) {
305         T zero = field.getZero();
306         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add( 3492467.560), zero.add( -25767.25680));
307         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685), zero.add(942.7809215), zero.add(7435.922231));
308         FieldAbsoluteDate<T> iniDate  = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
309         FieldOrbit<T> iniOrbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position, velocity),
310                                                              FramesFactory.getEME2000(), iniDate, zero.add(mu));
311         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(iniOrbit);
312         double[] absTolerance = {
313             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
314         };
315         double[] relTolerance = {
316             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
317         };
318         AdaptiveStepsizeFieldIntegrator<T> integrator =
319             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
320         integrator.setInitialStepSize(60);
321         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
322         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
323         propagator.setInitialState(initialState);
324 
325         //setup
326         final FieldDateDetector<T> dateDetector = new FieldDateDetector<>(field, toArray(iniDate.shiftedBy(dt))).
327                         withMinGap(minGap).withThreshold(field.getZero().newInstance(threshold));
328         // generic event handler that works with all detectors.
329         FieldEventHandler<T> handler = new FieldEventHandler<T>() {
330             @Override
331             public Action eventOccurred(FieldSpacecraftState<T> s,
332                                         FieldEventDetector<T> detector,
333                                         boolean increasing) {
334                 return Action.STOP;
335             }
336 
337             @Override
338             public FieldSpacecraftState<T> resetState(FieldEventDetector<T> detector,
339                                               FieldSpacecraftState<T> oldState) {
340                 throw new RuntimeException("Should not be called");
341             }
342         };
343 
344         //action
345         final FieldDateDetector<T> dateDetector2;
346 
347         dateDetector2 = dateDetector.withHandler(handler);
348 
349         propagator.addEventDetector(dateDetector2);
350         FieldSpacecraftState<T> finalState = propagator.propagate(iniDate.shiftedBy(100 * dt));
351 
352         //verify
353         Assertions.assertEquals(dt, finalState.getDate().durationFrom(iniDate).getReal(), threshold);
354     }
355 
356     private <T extends CalculusFieldElement<T>> void doTestIssue935(Field<T> field) {
357 
358         // startTime, endTime
359         long start = 1570802400000L;
360         long end = 1570838399000L;
361 
362         // Build propagator
363         FieldTLE<T> tle = new FieldTLE<>(field,
364                                          "1 43197U 18015F   19284.07336221  .00000533  00000-0  24811-4 0  9998",
365                                          "2 43197  97.4059  50.1428 0017543 265.5429 181.0400 15.24136761 93779");
366         FieldPropagator<T> propagator = FieldTLEPropagator.selectExtrapolator(tle, tle.getParameters(field));
367 
368         // Min gap to seconds
369         int maxCheck = (int) ((end - start) / 2000);
370         FieldDateDetector<T> dateDetector = new FieldDateDetector<>(field, getAbsoluteDateFromTimestamp(field, start)).
371                                             withMinGap(maxCheck).
372                                             withThreshold(field.getZero().newInstance(1.0e-6)).
373                                             withHandler(new FieldStopOnEvent<>());
374         dateDetector.addEventDate(getAbsoluteDateFromTimestamp(field, end));
375 
376         // Add event detectors to orbit
377         propagator.addEventDetector(dateDetector);
378 
379         // Propagate
380         final FieldAbsoluteDate<T> startDate = getAbsoluteDateFromTimestamp(field, start);
381         final FieldAbsoluteDate<T> endDate   = getAbsoluteDateFromTimestamp(field, end);
382         FieldSpacecraftState<T> lastState = propagator.propagate(startDate, endDate.shiftedBy(1));
383         Assertions.assertEquals(0.0, lastState.getDate().durationFrom(endDate).getReal(), 1.0e-15);
384 
385     }
386 
387     public static <T extends CalculusFieldElement<T>> FieldAbsoluteDate<T> getAbsoluteDateFromTimestamp(final Field<T> field,
388                                                                                                         final long timestamp) {
389         LocalDateTime utcDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp),
390                                                         ZoneId.of("UTC"));
391         int year = utcDate.getYear();
392         int month = utcDate.getMonthValue();
393         int day = utcDate.getDayOfMonth();
394         int hour = utcDate.getHour();
395         int minute = utcDate.getMinute();
396         double second = utcDate.getSecond();
397         double millis = utcDate.getNano() / 1e9;
398         return new FieldAbsoluteDate<>(field, year, month, day, hour, minute, second, TimeScalesFactory.getUTC()).shiftedBy(millis);
399     }
400 
401     private <T extends CalculusFieldElement<T>> FieldTimeStamped<T>[] toArray(final FieldAbsoluteDate<T> date) {
402         @SuppressWarnings("unchecked")
403         final FieldTimeStamped<T>[] array = (FieldTimeStamped<T>[]) Array.newInstance(FieldTimeStamped.class, 1);
404         array[0] = date;
405         return array;
406     }
407 
408     @BeforeEach
409     public void setUp() {
410             Utils.setDataRoot("regular-data");
411             mu = 3.9860047e14;
412             dt = 60.;
413             minGap  = 10.;
414             threshold = 10.e-7;
415             evtno = 0;
416     }
417 
418 }