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