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