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.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
22  import org.hipparchus.ode.events.Action;
23  import org.hipparchus.ode.nonstiff.AdaptiveStepsizeFieldIntegrator;
24  import org.hipparchus.ode.nonstiff.DormandPrince853FieldIntegrator;
25  import org.hipparchus.util.Binary64;
26  import org.hipparchus.util.Binary64Field;
27  import org.junit.jupiter.api.AfterEach;
28  import org.junit.jupiter.api.Assertions;
29  import org.junit.jupiter.api.BeforeEach;
30  import org.junit.jupiter.api.Test;
31  import org.orekit.TestUtils;
32  import org.orekit.Utils;
33  import org.orekit.bodies.CelestialBodyFactory;
34  import org.orekit.bodies.OneAxisEllipsoid;
35  import org.orekit.frames.FramesFactory;
36  import org.orekit.orbits.FieldCartesianOrbit;
37  import org.orekit.orbits.FieldEquinoctialOrbit;
38  import org.orekit.orbits.FieldOrbit;
39  import org.orekit.orbits.OrbitType;
40  import org.orekit.propagation.FieldSpacecraftState;
41  import org.orekit.propagation.events.handlers.FieldCountAndContinue;
42  import org.orekit.propagation.events.handlers.FieldEventHandler;
43  import org.orekit.propagation.numerical.FieldNumericalPropagator;
44  import org.orekit.time.FieldAbsoluteDate;
45  import org.orekit.time.TimeScalesFactory;
46  import org.orekit.utils.FieldPVCoordinates;
47  
48  import java.util.List;
49  
50  import static org.mockito.Mockito.mock;
51  import static org.mockito.Mockito.when;
52  
53  class FieldEventsLoggerTest {
54  
55      private double               mu;
56      private int                  count;
57  
58  
59      @BeforeEach
60      void setUp() {
61              Utils.setDataRoot("regular-data");
62              mu  = 3.9860047e14;
63      }
64  
65      @Test
66      void testMonitorDetectorClass() {
67          // GIVEN
68          final FieldAbsoluteDate<Binary64> fieldDate = FieldAbsoluteDate.getArbitraryEpoch(Binary64Field.getInstance());
69          final FieldDateDetector<Binary64> dateDetector = new FieldDateDetector<>(fieldDate);
70          final FieldEventsLogger<Binary64> eventsLogger = new FieldEventsLogger<>();
71          // WHEN
72          final FieldEventDetector<Binary64> detector = eventsLogger.monitorDetector(dateDetector);
73          // THEN
74          Assertions.assertInstanceOf(FieldDetectorModifier.class, detector);
75          final FieldDetectorModifier<Binary64> modifier = (FieldDetectorModifier<Binary64>) detector;
76          Assertions.assertEquals(dateDetector, modifier.getDetector());
77      }
78  
79      @Test
80      void testMonitorDetectorHandlerEventOccurred() {
81          // GIVEN
82          final FieldAbsoluteDate<Binary64> fieldDate = FieldAbsoluteDate.getArbitraryEpoch(Binary64Field.getInstance());
83          final FieldCountAndContinue<Binary64> counterHandler = new FieldCountAndContinue<>(0);
84          final FieldDateDetector<Binary64> dateDetector = new FieldDateDetector<>(fieldDate).withHandler(counterHandler);
85          final FieldEventsLogger<Binary64> eventsLogger = new FieldEventsLogger<>();
86          final FieldEventDetector<Binary64> detector = eventsLogger.monitorDetector(dateDetector);
87          final FieldEventHandler<Binary64> handler = detector.getHandler();
88          @SuppressWarnings("unchecked")
89          final FieldSpacecraftState<Binary64> mockedState = mock();
90          when(mockedState.getDate()).thenReturn(fieldDate);
91          // WHEN
92          final Action action = handler.eventOccurred(mockedState, dateDetector, true);
93          // THEN
94          Assertions.assertEquals(Action.CONTINUE, action);
95          final List<FieldEventsLogger.FieldLoggedEvent<Binary64>> loggedEvents = eventsLogger.getLoggedEvents();
96          Assertions.assertEquals(loggedEvents.size(), counterHandler.getCount());
97          final FieldEventsLogger.FieldLoggedEvent<Binary64> event = loggedEvents.get(0);
98          Assertions.assertEquals(mockedState, event.getState());
99          Assertions.assertEquals(mockedState, event.getResetState());
100     }
101 
102     @Test
103     void testMonitorDetectorHandlerResetState() {
104         // GIVEN
105         final FieldAbsoluteDate<Binary64> fieldDate = FieldAbsoluteDate.getArbitraryEpoch(Binary64Field.getInstance());
106         final FieldCountAndContinue<Binary64> counterHandler = new FieldCountAndContinue<>(0);
107         final FieldDateDetector<Binary64> dateDetector = new FieldDateDetector<>(fieldDate).withHandler(counterHandler);
108         final FieldEventsLogger<Binary64> eventsLogger = new FieldEventsLogger<>();
109         final FieldEventDetector<Binary64> detector = eventsLogger.monitorDetector(dateDetector);
110         final FieldEventHandler<Binary64> handler = detector.getHandler();
111         final FieldOrbit<Binary64> fieldOrbit = new FieldCartesianOrbit<>(Binary64Field.getInstance(),
112                 TestUtils.getDefaultOrbit(fieldDate.toAbsoluteDate()));
113         final FieldSpacecraftState<Binary64> fieldState = new FieldSpacecraftState<>(fieldOrbit);
114         // WHEN
115         final Action action = handler.eventOccurred(fieldState, dateDetector, true);
116         // THEN
117         final FieldSpacecraftState<Binary64> state = handler.resetState(dateDetector, fieldState);
118         // THEN
119         if (action == Action.RESET_STATE) {
120             Assertions.assertNotEquals(fieldState, state);
121         } else {
122             Assertions.assertEquals(fieldState, state);
123         }
124     }
125 
126     @Test
127     void testLogUmbra() {
128         doTestLogUmbra(Binary64Field.getInstance());
129     }
130 
131     @Test
132     void testLogPenumbra() {
133         doTestLogPenumbra(Binary64Field.getInstance());
134     }
135 
136     @Test
137     void testLogAll() {
138         doTestLogAll(Binary64Field.getInstance());
139     }
140 
141     @Test
142     void testImmutableList() {
143         doTestImmutableList(Binary64Field.getInstance());
144     }
145 
146     @Test
147     void testClearLog() {
148         doTestClearLog(Binary64Field.getInstance());
149     }
150 
151     private <T extends CalculusFieldElement<T>> void doTestLogUmbra(Field<T> field) {
152 
153         T zero = field.getZero();
154 
155 
156         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add(3492467.560), zero.add(-25767.25680));
157         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685) , zero.add(942.7809215), zero.add(7435.922231));
158         FieldAbsoluteDate<T> iniDate = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
159         final FieldOrbit<T> orbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position,  velocity),
160                                                                 FramesFactory.getEME2000(), iniDate, zero.add(mu));
161         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(orbit);
162         double[] absTolerance = {
163             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
164         };
165         double[] relTolerance = {
166             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
167         };
168         AdaptiveStepsizeFieldIntegrator<T> integrator =
169             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
170         integrator.setInitialStepSize(60);
171         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
172         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
173         propagator.setInitialState(initialState);
174         count = 0;
175         FieldEclipseDetector<T> umbraDetector = buildDetector(field, true);
176         FieldEclipseDetector<T> penumbraDetector = buildDetector(field, false);
177 
178 
179 
180         FieldEventsLogger<T> logger = new FieldEventsLogger<>();
181         FieldEventDetector<T> monitored = logger.monitorDetector(umbraDetector.withMaxIter(200));
182         Assertions.assertEquals(100, umbraDetector.getMaxIterationCount());
183         Assertions.assertEquals(200, monitored.getMaxIterationCount());
184 
185         propagator.addEventDetector(monitored);
186         propagator.addEventDetector(penumbraDetector);
187         count = 0;
188         propagator.propagate(iniDate.shiftedBy(16215)).getDate();
189         Assertions.assertEquals(11, count);
190         checkCounts(logger, 3, 3, 0, 0);
191     }
192 
193     private <T extends CalculusFieldElement<T>> void doTestLogPenumbra(final Field<T> field) {
194 
195 
196         T zero = field.getZero();
197 
198 
199         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add(3492467.560), zero.add(-25767.25680));
200         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685) , zero.add(942.7809215), zero.add(7435.922231));
201         FieldAbsoluteDate<T> iniDate = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
202         final FieldOrbit<T> orbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position,  velocity),
203                                                                 FramesFactory.getEME2000(), iniDate, zero.add(mu));
204         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(orbit);
205         double[] absTolerance = {
206             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
207         };
208         double[] relTolerance = {
209             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
210         };
211         AdaptiveStepsizeFieldIntegrator<T> integrator =
212             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
213         integrator.setInitialStepSize(60);
214         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
215         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
216         propagator.setInitialState(initialState);
217         count = 0;
218         FieldEventDetector<T> umbraDetector = buildDetector(field, true);
219         FieldEventDetector<T> penumbraDetector = buildDetector(field, false);
220 
221         FieldEventsLogger<T> logger = new FieldEventsLogger<>();
222         propagator.addEventDetector(umbraDetector);
223         propagator.addEventDetector(logger.monitorDetector(penumbraDetector));
224         count = 0;
225         propagator.propagate(iniDate.shiftedBy(16215)).getDate();
226         Assertions.assertEquals(11, count);
227         checkCounts(logger, 0, 0, 2, 3);
228     }
229 
230     private <T extends CalculusFieldElement<T>> void doTestLogAll(final Field<T> field) {
231 
232         T zero = field.getZero();
233 
234 
235         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add(3492467.560), zero.add(-25767.25680));
236         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685) , zero.add(942.7809215), zero.add(7435.922231));
237         FieldAbsoluteDate<T> iniDate = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
238         final FieldOrbit<T> orbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position,  velocity),
239                                                                 FramesFactory.getEME2000(), iniDate, zero.add(mu));
240         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(orbit);
241         double[] absTolerance = {
242             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
243         };
244         double[] relTolerance = {
245             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
246         };
247         AdaptiveStepsizeFieldIntegrator<T> integrator =
248             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
249         integrator.setInitialStepSize(60);
250         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
251         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
252         propagator.setInitialState(initialState);
253         count = 0;
254         FieldEventDetector<T> umbraDetector = buildDetector(field, true);
255         FieldEventDetector<T> penumbraDetector = buildDetector(field, false);
256 
257 
258 
259 
260 
261 
262 
263 
264         FieldEventsLogger<T> logger = new FieldEventsLogger<>();
265         propagator.addEventDetector(logger.monitorDetector(umbraDetector));
266         propagator.addEventDetector(logger.monitorDetector(penumbraDetector));
267         count = 0;
268         propagator.propagate(iniDate.shiftedBy(16215));
269         Assertions.assertEquals(11, count);
270         checkCounts(logger, 3, 3, 2, 3);
271     }
272 
273     private <T extends CalculusFieldElement<T>> void doTestImmutableList(final Field<T> field) {
274 
275 
276         T zero = field.getZero();
277 
278 
279         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add(3492467.560), zero.add(-25767.25680));
280         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685) , zero.add(942.7809215), zero.add(7435.922231));
281         FieldAbsoluteDate<T> iniDate = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
282         final FieldOrbit<T> orbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position,  velocity),
283                                                                 FramesFactory.getEME2000(), iniDate, zero.add(mu));
284         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(orbit);
285         double[] absTolerance = {
286             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
287         };
288         double[] relTolerance = {
289             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
290         };
291         AdaptiveStepsizeFieldIntegrator<T> integrator =
292             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
293         integrator.setInitialStepSize(60);
294         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
295         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
296         propagator.setInitialState(initialState);
297         count = 0;
298         FieldEventDetector<T> umbraDetector = buildDetector(field, true);
299         FieldEventDetector<T> penumbraDetector = buildDetector(field, false);
300 
301         FieldEventsLogger<T> logger = new FieldEventsLogger<>();
302         propagator.addEventDetector(logger.monitorDetector(umbraDetector));
303         propagator.addEventDetector(logger.monitorDetector(penumbraDetector));
304         count = 0;
305         propagator.propagate(iniDate.shiftedBy(16215));
306         List<FieldEventsLogger.FieldLoggedEvent<T>> firstList = logger.getLoggedEvents();
307         Assertions.assertEquals(11, firstList.size());
308         propagator.propagate(iniDate.shiftedBy(30000));
309         List<FieldEventsLogger.FieldLoggedEvent<T>> secondList = logger.getLoggedEvents();
310         Assertions.assertEquals(11, firstList.size());
311         Assertions.assertEquals(20, secondList.size());
312         for (int i = 0; i < firstList.size(); ++i) {
313 
314             FieldEventsLogger.FieldLoggedEvent<T> e1 = firstList.get(i);
315             FieldEventsLogger.FieldLoggedEvent<T> e2 = secondList.get(i);
316             FieldPVCoordinates<T> pv1 = e1.getState().getPVCoordinates();
317             FieldPVCoordinates<T> pv2 = e2.getState().getPVCoordinates();
318 
319             Assertions.assertSame(e1.getEventDetector(), e2.getEventDetector());
320             Assertions.assertEquals(0, pv1.getPosition().subtract(pv2.getPosition()).getNorm().getReal(), 1.0e-10);
321             Assertions.assertEquals(0, pv1.getVelocity().subtract(pv2.getVelocity()).getNorm().getReal(), 1.0e-10);
322             Assertions.assertEquals(e1.isIncreasing(), e2.isIncreasing());
323 
324         }
325     }
326 
327     private <T extends CalculusFieldElement<T>> void doTestClearLog(final Field<T> field) {
328 
329 
330 
331 
332         T zero = field.getZero();
333 
334 
335         final FieldVector3D<T> position  = new FieldVector3D<>(zero.add(-6142438.668), zero.add(3492467.560), zero.add(-25767.25680));
336         final FieldVector3D<T> velocity  = new FieldVector3D<>(zero.add(505.8479685) , zero.add(942.7809215), zero.add(7435.922231));
337         FieldAbsoluteDate<T> iniDate = new FieldAbsoluteDate<>(field, 1969, 7, 28, 4, 0, 0.0, TimeScalesFactory.getTT());
338         final FieldOrbit<T> orbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position,  velocity),
339                                                                 FramesFactory.getEME2000(), iniDate, zero.add(mu));
340         FieldSpacecraftState<T> initialState = new FieldSpacecraftState<>(orbit);
341         double[] absTolerance = {
342             0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001
343         };
344         double[] relTolerance = {
345             1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7
346         };
347         AdaptiveStepsizeFieldIntegrator<T> integrator =
348             new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, absTolerance, relTolerance);
349         integrator.setInitialStepSize(60);
350         FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
351         propagator.setOrbitType(OrbitType.EQUINOCTIAL);
352         propagator.setInitialState(initialState);
353         count = 0;
354         FieldEventDetector<T> umbraDetector = buildDetector(field, true);
355         FieldEventDetector<T> penumbraDetector = buildDetector(field, false);
356 
357 
358 
359         FieldEventsLogger<T> logger = new FieldEventsLogger<>();
360         propagator.addEventDetector(logger.monitorDetector(umbraDetector));
361         propagator.addEventDetector(logger.monitorDetector(penumbraDetector));
362         count = 0;
363         propagator.propagate(iniDate.shiftedBy(16215));
364         List<FieldEventsLogger.FieldLoggedEvent<T>> firstList = logger.getLoggedEvents();
365         Assertions.assertEquals(11, firstList.size());
366         logger.clearLoggedEvents();
367         propagator.propagate(iniDate.shiftedBy(30000));
368         List<FieldEventsLogger.FieldLoggedEvent<T>> secondList = logger.getLoggedEvents();
369         Assertions.assertEquals(11, firstList.size());
370         Assertions.assertEquals( 9, secondList.size());
371     }
372 
373     private <T extends CalculusFieldElement<T>> void checkCounts(FieldEventsLogger<T> logger,
374                              int expectedUmbraIncreasingCount, int expectedUmbraDecreasingCount,
375                              int expectedPenumbraIncreasingCount, int expectedPenumbraDecreasingCount) {
376         int umbraIncreasingCount = 0;
377         int umbraDecreasingCount = 0;
378         int penumbraIncreasingCount = 0;
379         int penumbraDecreasingCount = 0;
380         for (FieldEventsLogger.FieldLoggedEvent<T> event : logger.getLoggedEvents()) {
381             final FieldEclipseDetector<T> eclipseDetector = (FieldEclipseDetector<T>) (event.getEventDetector());
382             if (eclipseDetector.getTotalEclipse()) {
383                 if (event.isIncreasing()) {
384                     ++umbraIncreasingCount;
385                 } else {
386                     ++umbraDecreasingCount;
387                 }
388             }
389             else {
390                 if (event.isIncreasing()) {
391                     ++penumbraIncreasingCount;
392                 } else {
393                     ++penumbraDecreasingCount;
394                 }
395             }
396         }
397         Assertions.assertEquals(expectedUmbraIncreasingCount,    umbraIncreasingCount);
398         Assertions.assertEquals(expectedUmbraDecreasingCount,    umbraDecreasingCount);
399         Assertions.assertEquals(expectedPenumbraIncreasingCount, penumbraIncreasingCount);
400         Assertions.assertEquals(expectedPenumbraDecreasingCount, penumbraDecreasingCount);
401     }
402 
403     private <T extends CalculusFieldElement<T>> FieldEclipseDetector<T> buildDetector(Field<T> field, final boolean totalEclipse) {
404 
405         FieldEclipseDetector<T> detector =
406                 new FieldEclipseDetector<>(field, CelestialBodyFactory.getSun(), 696000000,
407                                            new OneAxisEllipsoid(6400000, 0.0, FramesFactory.getGCRF())).
408                 withMaxCheck(60.0).
409                 withThreshold(field.getZero().newInstance(1.0e-3));
410 
411         if (totalEclipse) {
412             detector = detector.withUmbra();
413         } else {
414             detector = detector.withPenumbra();
415         }
416 
417         detector = detector.withHandler((s, detector1, increasing) -> {
418             ++count;
419             return Action.CONTINUE;
420         });
421 
422         return detector;
423 
424     }
425 
426     @AfterEach
427     void tearDown() {
428         count = 0;
429     }
430 
431 }
432