1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.propagation.events;
18
19 import java.util.Locale;
20 import java.util.function.Function;
21
22 import org.hamcrest.CoreMatchers;
23 import org.hamcrest.MatcherAssert;
24 import org.hipparchus.exception.LocalizedCoreFormats;
25 import org.hipparchus.geometry.euclidean.threed.Vector3D;
26 import org.hipparchus.ode.ODEIntegrator;
27 import org.hipparchus.ode.events.Action;
28 import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
29 import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
30 import org.hipparchus.util.FastMath;
31 import org.junit.jupiter.api.Assertions;
32 import org.junit.jupiter.api.BeforeEach;
33 import org.junit.jupiter.api.Test;
34 import org.mockito.Mockito;
35 import org.orekit.Utils;
36 import org.orekit.errors.OrekitException;
37 import org.orekit.frames.Frame;
38 import org.orekit.frames.FramesFactory;
39 import org.orekit.orbits.CircularOrbit;
40 import org.orekit.orbits.EquinoctialOrbit;
41 import org.orekit.orbits.KeplerianOrbit;
42 import org.orekit.orbits.Orbit;
43 import org.orekit.orbits.OrbitType;
44 import org.orekit.orbits.PositionAngleType;
45 import org.orekit.propagation.Propagator;
46 import org.orekit.propagation.SpacecraftState;
47 import org.orekit.propagation.ToleranceProvider;
48 import org.orekit.propagation.analytical.KeplerianPropagator;
49 import org.orekit.propagation.events.handlers.ContinueOnEvent;
50 import org.orekit.propagation.events.handlers.EventHandler;
51 import org.orekit.propagation.events.handlers.StopOnEvent;
52 import org.orekit.propagation.events.intervals.AdaptableInterval;
53 import org.orekit.propagation.numerical.NumericalPropagator;
54 import org.orekit.propagation.sampling.OrekitFixedStepHandler;
55 import org.orekit.time.AbsoluteDate;
56 import org.orekit.time.TimeOffset;
57 import org.orekit.time.TimeScale;
58 import org.orekit.time.TimeScalesFactory;
59 import org.orekit.utils.Constants;
60 import org.orekit.utils.PVCoordinates;
61 import org.orekit.utils.PVCoordinatesProvider;
62
63 public class EventDetectorTest {
64
65 private double mu;
66
67 @Test
68 void testFinish() {
69
70 final FinishingHandler handler = new FinishingHandler();
71 final EventDetector detector =
72 new DummyDetector(new EventDetectionSettings(1.0, 1.0e-10, 100), handler);
73
74 detector.finish(Mockito.mock(SpacecraftState.class));
75
76 Assertions.assertTrue(handler.isFinished);
77 }
78
79 private static class FinishingHandler extends ContinueOnEvent {
80 boolean isFinished = false;
81
82 @Override
83 public void finish(SpacecraftState finalState, EventDetector detector) {
84 isFinished = true;
85 }
86 }
87
88 private static class DummyDetector implements EventDetector {
89
90 private final EventDetectionSettings detectionSettings;
91 private final EventHandler handler;
92
93 public DummyDetector(final EventDetectionSettings detectionSettings, final EventHandler handler) {
94 this.detectionSettings = detectionSettings;
95 this.handler = handler;
96 }
97
98 @Override
99 public EventDetectionSettings getDetectionSettings() {
100 return detectionSettings;
101 }
102
103 @Override
104 public EventHandler getHandler() {
105 return handler;
106 }
107
108 public double g(final SpacecraftState s) {
109 return 0;
110 }
111
112 }
113
114 @Test
115 public void testEventHandlerInit() {
116 final TimeScale utc = TimeScalesFactory.getUTC();
117 final Vector3D position = new Vector3D(-6142438.668, 3492467.56, -25767.257);
118 final Vector3D velocity = new Vector3D(505.848, 942.781, 7435.922);
119 final AbsoluteDate date = new AbsoluteDate(2003, 9, 16, utc);
120 final Orbit orbit = new CircularOrbit(new PVCoordinates(position, velocity),
121 FramesFactory.getEME2000(), date, mu);
122
123 final boolean[] eventOccurred = new boolean[1];
124 EventHandler handler = new EventHandler() {
125 private boolean initCalled;
126 @Override
127 public Action eventOccurred(SpacecraftState s,
128 EventDetector detector,
129 boolean increasing) {
130 if (!initCalled) {
131 throw new RuntimeException("init() not called before eventOccurred()");
132 }
133 eventOccurred[0] = true;
134 return Action.STOP;
135 }
136
137 @Override
138 public void init(SpacecraftState initialState,
139 AbsoluteDate target,
140 EventDetector detector) {
141 initCalled = true;
142 }
143 };
144
145 Propagator propagator = new KeplerianPropagator(orbit);
146 double stepSize = 60.0;
147 propagator.addEventDetector(new DateDetector(date.shiftedBy(5.25 * stepSize)).withHandler(handler));
148 propagator.propagate(date.shiftedBy(10 * stepSize));
149 Assertions.assertTrue(eventOccurred[0]);
150
151 }
152
153 @Test
154 public void testBasicScheduling() {
155
156 final TimeScale utc = TimeScalesFactory.getUTC();
157 final Vector3D position = new Vector3D(-6142438.668, 3492467.56, -25767.257);
158 final Vector3D velocity = new Vector3D(505.848, 942.781, 7435.922);
159 final AbsoluteDate date = new AbsoluteDate(2003, 9, 16, utc);
160 final Orbit orbit = new CircularOrbit(new PVCoordinates(position, velocity),
161 FramesFactory.getEME2000(), date, mu);
162
163 Propagator propagator = new KeplerianPropagator(orbit);
164 double stepSize = 60.0;
165 OutOfOrderChecker checker = new OutOfOrderChecker(stepSize);
166 propagator.addEventDetector(new DateDetector(date.shiftedBy(5.25 * stepSize)).withHandler(checker));
167 propagator.setStepHandler(stepSize, checker);
168 propagator.propagate(date.shiftedBy(10 * stepSize));
169 Assertions.assertTrue(checker.outOfOrderCallDetected());
170
171 }
172
173 private static class OutOfOrderChecker implements EventHandler, OrekitFixedStepHandler {
174
175 private AbsoluteDate triggerDate;
176 private boolean outOfOrderCallDetected;
177 private final double stepSize;
178
179 public OutOfOrderChecker(final double stepSize) {
180 triggerDate = null;
181 outOfOrderCallDetected = false;
182 this.stepSize = stepSize;
183 }
184
185 public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing) {
186 triggerDate = s.getDate();
187 return Action.CONTINUE;
188 }
189
190 public void handleStep(SpacecraftState currentState) {
191
192
193
194 if (triggerDate != null) {
195 double dt = currentState.getDate().durationFrom(triggerDate);
196 if (dt < 0) {
197 outOfOrderCallDetected = true;
198 Assertions.assertTrue(FastMath.abs(dt) < (2 * stepSize));
199 }
200 }
201 }
202
203 public boolean outOfOrderCallDetected() {
204 return outOfOrderCallDetected;
205 }
206
207 }
208
209 @Test
210 public void testIssue108Numerical() {
211 final TimeScale utc = TimeScalesFactory.getUTC();
212 final Vector3D position = new Vector3D(-6142438.668, 3492467.56, -25767.257);
213 final Vector3D velocity = new Vector3D(505.848, 942.781, 7435.922);
214 final AbsoluteDate date = new AbsoluteDate(2003, 9, 16, utc);
215 final Orbit orbit = new CircularOrbit(new PVCoordinates(position, velocity),
216 FramesFactory.getEME2000(), date, mu);
217 final double step = 60.0;
218 final int n = 100;
219 NumericalPropagator propagator = new NumericalPropagator(new ClassicalRungeKuttaIntegrator(step));
220 propagator.resetInitialState(new SpacecraftState(orbit));
221 GCallsCounter counter = new GCallsCounter(AdaptableInterval.of(100000.0), 1.0e-6, 20, new StopOnEvent());
222 propagator.addEventDetector(counter);
223 propagator.propagate(date.shiftedBy(n * step));
224 Assertions.assertEquals(n + 1, counter.getCount());
225 }
226
227 @Test
228 public void testIssue108Analytical() {
229 final TimeScale utc = TimeScalesFactory.getUTC();
230 final Vector3D position = new Vector3D(-6142438.668, 3492467.56, -25767.257);
231 final Vector3D velocity = new Vector3D(505.848, 942.781, 7435.922);
232 final AbsoluteDate date = new AbsoluteDate(2003, 9, 16, utc);
233 final Orbit orbit = new CircularOrbit(new PVCoordinates(position, velocity),
234 FramesFactory.getEME2000(), date, mu);
235 final double step = 60.0;
236 final int n = 100;
237 KeplerianPropagator propagator = new KeplerianPropagator(orbit);
238 GCallsCounter counter = new GCallsCounter(AdaptableInterval.of(100000.0), 1.0e-6, 20, new StopOnEvent());
239 propagator.addEventDetector(counter);
240 propagator.setStepHandler(step, currentState -> {});
241 propagator.propagate(date.shiftedBy(n * step));
242
243 Assertions.assertEquals(2, counter.getCount());
244 }
245
246 private static class GCallsCounter extends AbstractDetector<GCallsCounter> {
247
248 private int count;
249
250 public GCallsCounter(final AdaptableInterval maxCheck, final double threshold,
251 final int maxIter, final EventHandler handler) {
252 super(new EventDetectionSettings(maxCheck, threshold, maxIter), handler);
253 count = 0;
254 }
255
256 protected GCallsCounter create(final EventDetectionSettings detectionSettings, final EventHandler newHandler) {
257 return new GCallsCounter(detectionSettings.getMaxCheckInterval(), detectionSettings.getThreshold(),
258 detectionSettings.getMaxIterationCount(), newHandler);
259 }
260
261 public int getCount() {
262 return count;
263 }
264
265 public double g(SpacecraftState s) {
266 count++;
267 return 1.0;
268 }
269
270 }
271
272 @Test
273 public void testNoisyGFunction() {
274
275
276 Frame eme2000 = FramesFactory.getEME2000();
277 TimeScale utc = TimeScalesFactory.getUTC();
278 AbsoluteDate initialDate = new AbsoluteDate(2011, 5, 11, utc);
279 AbsoluteDate startDate = new AbsoluteDate(2032, 10, 17, utc);
280 AbsoluteDate interruptDate = new AbsoluteDate(2032, 10, 18, utc);
281 AbsoluteDate targetDate = new AbsoluteDate(2211, 5, 11, utc);
282 KeplerianPropagator k1 =
283 new KeplerianPropagator(new EquinoctialOrbit(new PVCoordinates(new Vector3D(4008462.4706055815, -3155502.5373837613, -5044275.9880020910),
284 new Vector3D(-5012.9298276860990, 1920.3567095973078, -5172.7403501801580)),
285 eme2000, initialDate, Constants.WGS84_EARTH_MU));
286 KeplerianPropagator k2 =
287 new KeplerianPropagator(new EquinoctialOrbit(new PVCoordinates(new Vector3D(4008912.4039522274, -3155453.3125615157, -5044297.6484738905),
288 new Vector3D(-5012.5883854112530, 1920.6332221785074, -5172.2177085540500)),
289 eme2000, initialDate, Constants.WGS84_EARTH_MU));
290 k2.addEventDetector(new CloseApproachDetector(new EventDetectionSettings(AdaptableInterval.of(2015.243454166727), 0.0001, 100),
291 new ContinueOnEvent(), k1));
292 k2.addEventDetector(new DateDetector(interruptDate).
293 withMaxCheck(Constants.JULIAN_DAY).
294 withThreshold(1.0e-6));
295 SpacecraftState s = k2.propagate(startDate, targetDate);
296 Assertions.assertEquals(0.0, interruptDate.durationFrom(s.getDate()), 1.1e-6);
297 }
298
299 private static class CloseApproachDetector extends AbstractDetector<CloseApproachDetector> {
300
301 private final PVCoordinatesProvider provider;
302
303 public CloseApproachDetector(EventDetectionSettings detectionSettings, final EventHandler handler,
304 PVCoordinatesProvider provider) {
305 super(detectionSettings, handler);
306 this.provider = provider;
307 }
308
309 public double g(final SpacecraftState s) {
310 PVCoordinates pv1 = provider.getPVCoordinates(s.getDate(), s.getFrame());
311 PVCoordinates pv2 = s.getPVCoordinates();
312 Vector3D deltaP = pv1.getPosition().subtract(pv2.getPosition());
313 Vector3D deltaV = pv1.getVelocity().subtract(pv2.getVelocity());
314 return Vector3D.dotProduct(deltaP.normalize(), deltaV);
315 }
316
317 protected CloseApproachDetector create(final EventDetectionSettings detectionSettings,
318 final EventHandler newHandler) {
319 return new CloseApproachDetector(detectionSettings, newHandler, provider);
320 }
321
322 }
323
324 @Test
325 public void testWrappedException() {
326 final Throwable dummyCause = new RuntimeException();
327 try {
328
329 Frame eme2000 = FramesFactory.getEME2000();
330 TimeScale utc = TimeScalesFactory.getUTC();
331 final AbsoluteDate initialDate = new AbsoluteDate(2011, 5, 11, utc);
332 final AbsoluteDate exceptionDate = initialDate.shiftedBy(3600.0);
333 KeplerianPropagator k =
334 new KeplerianPropagator(new EquinoctialOrbit(new PVCoordinates(new Vector3D(4008462.4706055815, -3155502.5373837613, -5044275.9880020910),
335 new Vector3D(-5012.9298276860990, 1920.3567095973078, -5172.7403501801580)),
336 eme2000, initialDate, Constants.WGS84_EARTH_MU));
337 k.addEventDetector(new DateDetector(initialDate.shiftedBy(Constants.JULIAN_DAY)) {
338 @Override
339 public double g(final SpacecraftState s) {
340 final double dt = s.getDate().durationFrom(exceptionDate);
341 if (FastMath.abs(dt) < 1.0) {
342 throw new OrekitException(dummyCause, LocalizedCoreFormats.SIMPLE_MESSAGE, "dummy");
343 }
344 return dt;
345 }
346 });
347 k.propagate(initialDate.shiftedBy(Constants.JULIAN_YEAR));
348 Assertions.fail("an exception should have been thrown");
349 } catch (OrekitException oe) {
350 Assertions.assertSame(OrekitException.class, oe.getClass());
351 Assertions.assertSame(dummyCause, oe.getCause().getCause());
352 String expected = "failed to find root between 2011-05-11T00:00:00.000Z " +
353 "(g=-3.6E3) and 2012-05-10T06:00:00.000Z (g=3.1554E7)\n" +
354 "Last iteration at 2011-05-11T00:00:00.000Z (g=-3.6E3)";
355 MatcherAssert.assertThat(oe.getMessage(Locale.US),
356 CoreMatchers.containsString(expected));
357 }
358 }
359
360 @Test
361 public void testDefaultMethods() {
362 EventDetector dummyDetector = new EventDetector() {
363
364 @Override
365 public EventDetectionSettings getDetectionSettings() {
366 return new EventDetectionSettings(60, 1e-10, 100);
367 }
368
369 @Override
370 public double g(SpacecraftState s) {
371 return s.getDate().durationFrom(AbsoluteDate.J2000_EPOCH);
372 }
373
374 @Override
375 public EventHandler getHandler() {
376 return (state, detector, increasing) -> Action.RESET_STATE;
377 }
378 };
379
380
381 dummyDetector.init(null, null);
382
383
384 SpacecraftState s = new SpacecraftState(new KeplerianOrbit(7e6, 0.01, 0.3, 0, 0, 0,
385 PositionAngleType.TRUE, FramesFactory.getEME2000(),
386 AbsoluteDate.J2000_EPOCH, Constants.EIGEN5C_EARTH_MU));
387 Assertions.assertSame(s, dummyDetector.getHandler().resetState(dummyDetector, s));
388
389 }
390
391 @Test
392 public void testNumericalNoiseAtIntervalEnd() {
393
394 Frame eme2000 = FramesFactory.getEME2000();
395 TimeScale utc = TimeScalesFactory.getUTC();
396 final AbsoluteDate initialDate = new AbsoluteDate(2011, 5, 11, utc);
397 final Orbit orbit = new EquinoctialOrbit(new PVCoordinates(new Vector3D(4008462.4706055815, -3155502.5373837613, -5044275.9880020910),
398 new Vector3D(-5012.9298276860990, 1920.3567095973078, -5172.7403501801580)),
399 eme2000, initialDate, Constants.WGS84_EARTH_MU);
400 Propagator propagator = new KeplerianPropagator(orbit);
401 double base = 3600.0;
402 TimeOffset noise = new TimeOffset(4, TimeOffset.ATTOSECOND);
403
404 AbsoluteDate finalTime = initialDate.shiftedBy(base).shiftedBy(new TimeOffset(2, noise));
405 AbsoluteDate eventTime = finalTime.shiftedBy(noise.negate());
406 propagator.addEventDetector(new DateDetector(eventTime).withMaxCheck(base).withThreshold(noise.toDouble() / 2));
407 SpacecraftState finalState = propagator.propagate(finalTime);
408 Assertions.assertEquals(0.0, finalState.getDate().durationFrom(eventTime), noise.toDouble());
409
410 }
411
412 @Test
413 public void testForwardAnalytical() {
414 doTestScheduling(0.0, 1.0, 21, this::buildAnalytical);
415 }
416
417 @Test
418 public void testBackwardAnalytical() {
419 doTestScheduling(1.0, 0.0, 21, this::buildAnalytical);
420 }
421
422 @Test
423 public void testForwardNumerical() {
424 doTestScheduling(0.0, 1.0, 23, this::buildNumerical);
425 }
426
427 @Test
428 public void testBackwardNumerical() {
429 doTestScheduling(1.0, 0.0, 23, this::buildNumerical);
430 }
431
432 private Propagator buildAnalytical(final Orbit orbit) {
433 return new KeplerianPropagator(orbit);
434 }
435
436 private Propagator buildNumerical(final Orbit orbit) {
437 OrbitType type = OrbitType.CARTESIAN;
438 double[][] tol = ToleranceProvider.getDefaultToleranceProvider(0.0001).getTolerances(orbit, type);
439 ODEIntegrator integrator = new DormandPrince853Integrator(0.0001, 10.0, tol[0], tol[1]);
440 NumericalPropagator propagator = new NumericalPropagator(integrator);
441 propagator.setOrbitType(type);
442 propagator.setInitialState(new SpacecraftState(orbit));
443 return propagator;
444 }
445
446 private void doTestScheduling(final double start, final double stop, final int expectedCalls,
447 final Function<Orbit, Propagator> propagatorBuilder) {
448
449
450 Frame eme2000 = FramesFactory.getEME2000();
451 TimeScale utc = TimeScalesFactory.getUTC();
452 final AbsoluteDate initialDate = new AbsoluteDate(2011, 5, 11, utc);
453 final Orbit orbit = new EquinoctialOrbit(new PVCoordinates(new Vector3D(4008462.4706055815, -3155502.5373837613, -5044275.9880020910),
454 new Vector3D(-5012.9298276860990, 1920.3567095973078, -5172.7403501801580)),
455 eme2000, initialDate, Constants.WGS84_EARTH_MU);
456 Propagator propagator = propagatorBuilder.apply(orbit.shiftedBy(start));
457
458
459
460 final ScheduleChecker checker = new ScheduleChecker(initialDate.shiftedBy(start),
461 initialDate.shiftedBy(stop));
462 propagator.setStepHandler((interpolator) -> checker.callDate(interpolator.getCurrentState().getDate()));
463
464 for (int i = 0; i < 10; ++i) {
465 propagator.addEventDetector(new DateDetector(initialDate.shiftedBy(0.0625 * (i + 1))).
466 withHandler((state, detector, increasing) -> {
467 checker.callDate(state.getDate());
468 return Action.CONTINUE;
469 }));
470 }
471
472 propagator.propagate(initialDate.shiftedBy(start), initialDate.shiftedBy(stop));
473
474 Assertions.assertEquals(expectedCalls, checker.calls);
475
476 }
477
478
479 private static class ScheduleChecker {
480
481 private final AbsoluteDate start;
482 private final AbsoluteDate stop;
483 private AbsoluteDate last;
484 private int calls;
485
486 ScheduleChecker(final AbsoluteDate start, final AbsoluteDate stop) {
487 this.start = start;
488 this.stop = stop;
489 this.last = null;
490 this.calls = 0;
491 }
492
493 void callDate(final AbsoluteDate date) {
494 if (last != null) {
495
496 if (start.isBefore(stop)) {
497
498 Assertions.assertTrue(date.isAfterOrEqualTo(start));
499 Assertions.assertTrue(date.isBeforeOrEqualTo(stop));
500 Assertions.assertTrue(date.isAfterOrEqualTo(last));
501 } else {
502
503 Assertions.assertTrue(date.isBeforeOrEqualTo(start));
504 Assertions.assertTrue(date.isAfterOrEqualTo(stop));
505 Assertions.assertTrue(date.isBeforeOrEqualTo(last));
506 }
507 }
508 last = date;
509 ++calls;
510 }
511
512 }
513
514 @Test
515 void testGetDetectionSettings() {
516
517 final EventDetector detector = new TestDetector();
518
519 final EventDetectionSettings actualSettings = detector.getDetectionSettings();
520
521 final EventDetectionSettings expectedSettings = EventDetectionSettings.getDefaultEventDetectionSettings();
522 final SpacecraftState mockedState = Mockito.mock(SpacecraftState.class);
523 Assertions.assertEquals(expectedSettings.getMaxCheckInterval().currentInterval(mockedState, true),
524 actualSettings.getMaxCheckInterval().currentInterval(mockedState, true));
525 Assertions.assertEquals(expectedSettings.getMaxIterationCount(), actualSettings.getMaxIterationCount());
526 Assertions.assertEquals(expectedSettings.getThreshold(), actualSettings.getThreshold());
527 }
528
529 private static class TestDetector implements EventDetector {
530
531 @Override
532 public double g(SpacecraftState s) {
533 return 0;
534 }
535
536 @Override
537 public EventHandler getHandler() {
538 return null;
539 }
540 }
541
542 @BeforeEach
543 public void setUp() {
544 Utils.setDataRoot("regular-data");
545 mu = Constants.EIGEN5C_EARTH_MU;
546 }
547
548 }
549