1   /*
2    * Licensed to the Hipparchus project under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF 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.ode.events.Action;
20  import org.hipparchus.util.FastMath;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.BeforeAll;
23  import org.junit.jupiter.api.Test;
24  import org.orekit.Utils;
25  import org.orekit.bodies.OneAxisEllipsoid;
26  import org.orekit.frames.Frame;
27  import org.orekit.frames.FramesFactory;
28  import org.orekit.orbits.KeplerianOrbit;
29  import org.orekit.orbits.PositionAngleType;
30  import org.orekit.propagation.Propagator;
31  import org.orekit.propagation.SpacecraftState;
32  import org.orekit.propagation.events.handlers.ContinueOnEvent;
33  import org.orekit.propagation.events.handlers.EventHandler;
34  import org.orekit.propagation.events.handlers.RecordAndContinue;
35  import org.orekit.propagation.events.handlers.RecordAndContinue.Event;
36  import org.orekit.propagation.events.handlers.StopOnEvent;
37  import org.orekit.propagation.sampling.OrekitStepHandler;
38  import org.orekit.propagation.sampling.OrekitStepInterpolator;
39  import org.orekit.time.AbsoluteDate;
40  import org.orekit.utils.Constants;
41  
42  import java.util.ArrayList;
43  import java.util.Arrays;
44  import java.util.List;
45  
46  /**
47   * Check events are detected correctly when the event times are close.
48   *
49   * @author Evan Ward
50   */
51  public abstract class CloseEventsAbstractTest {
52  
53      public static final AbsoluteDate epoch = AbsoluteDate.J2000_EPOCH;
54      public static final double mu = Constants.EIGEN5C_EARTH_MU;
55      public static final Frame eci = FramesFactory.getGCRF();
56      public static final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, eci);
57      public static final KeplerianOrbit initialOrbit = new KeplerianOrbit(
58              6378137 + 500e3, 0, 0, 0, 0, 0, PositionAngleType.TRUE,
59              eci, epoch, mu);
60  
61      @BeforeAll
62      public static void setUpBefore() {
63          Utils.setDataRoot("regular-data");
64      }
65  
66      /**
67       * Create a propagator using the {@link #initialOrbit}.
68       *
69       * @param stepSize   required minimum step of integrator.
70       * @return a usable propagator.
71       */
72      public abstract Propagator getPropagator(double stepSize);
73  
74      @Test
75      public void testCloseEventsFirstOneIsReset() {
76          // setup
77          // a fairly rare state to reproduce this bug. Two dates, d1 < d2, that
78          // are very close. Event triggers on d1 will reset state to break out of
79          // event handling loop in AbstractIntegrator.acceptStep(). At this point
80          // detector2 has g0Positive == true but the event time is set to just
81          // before the event so g(t0) is negative. Now on processing the
82          // next step the root solver checks the sign of the start, midpoint,
83          // and end of the interval so we need another event less than half a max
84          // check interval after d2 so that the g function will be negative at
85          // all three times. Then we get a non bracketing exception.
86          Propagator propagator = getPropagator(10.0);
87  
88          double t1 = 49, t2 = FastMath.nextUp(t1), t3 = t1 + 4.9;
89          List<Event> events = new ArrayList<>();
90          TimeDetector detector1 = new TimeDetector(t1)
91                  .withHandler(new Handler(events, Action.RESET_DERIVATIVES))
92                  .withMaxCheck(10)
93                  .withThreshold(1e-9);
94          propagator.addEventDetector(detector1);
95          TimeDetector detector2 = new TimeDetector(t2, t3)
96                  .withHandler(new RecordAndContinue(events))
97                  .withMaxCheck(11)
98                  .withThreshold(1e-9);
99          propagator.addEventDetector(detector2);
100 
101         // action
102         propagator.propagate(epoch.shiftedBy(60));
103 
104         // verify
105         Assertions.assertEquals(1, events.size());
106         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
107         Assertions.assertEquals(detector1, events.get(0).getDetector());
108     }
109 
110     @Test
111     public void testCloseEvents() {
112         // setup
113         double tolerance = 1;
114         Propagator propagator = getPropagator(10);
115 
116         RecordAndContinue handler = new RecordAndContinue();
117         TimeDetector detector1 = new TimeDetector(5)
118                 .withHandler(handler)
119                 .withMaxCheck(10)
120                 .withThreshold(tolerance);
121         propagator.addEventDetector(detector1);
122         TimeDetector detector2 = new TimeDetector(5.5)
123                 .withHandler(handler)
124                 .withMaxCheck(10)
125                 .withThreshold(tolerance);
126         propagator.addEventDetector(detector2);
127 
128         // action
129         propagator.propagate(epoch.shiftedBy(20));
130 
131         // verify
132         List<Event> events = handler.getEvents();
133         Assertions.assertEquals(2, events.size());
134         Assertions.assertEquals(5, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
135         Assertions.assertEquals(detector1, events.get(0).getDetector());
136         Assertions.assertEquals(5.5, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
137         Assertions.assertEquals(detector2, events.get(1).getDetector());
138     }
139 
140     @Test
141     public void testSimultaneousEvents() {
142         // setup
143         Propagator propagator = getPropagator(10);
144 
145         RecordAndContinue handler1 = new RecordAndContinue();
146         TimeDetector detector1 = new TimeDetector(5)
147                 .withHandler(handler1)
148                 .withMaxCheck(10)
149                 .withThreshold(1);
150         propagator.addEventDetector(detector1);
151         RecordAndContinue handler2 = new RecordAndContinue();
152         TimeDetector detector2 = new TimeDetector(5)
153                 .withHandler(handler2)
154                 .withMaxCheck(10)
155                 .withThreshold(1);
156         propagator.addEventDetector(detector2);
157 
158         // action
159         propagator.propagate(epoch.shiftedBy(20));
160 
161         // verify
162         List<Event> events1 = handler1.getEvents();
163         Assertions.assertEquals(1, events1.size());
164         Assertions.assertEquals(5, events1.get(0).getState().getDate().durationFrom(epoch), 0.0);
165         List<Event> events2 = handler2.getEvents();
166         Assertions.assertEquals(1, events2.size());
167         Assertions.assertEquals(5, events2.get(0).getState().getDate().durationFrom(epoch), 0.0);
168     }
169 
170     /**
171      * Previously there were some branches when tryAdvance() returned false but did not
172      * set {@code t0 = t}. This allowed the order of events to not be chronological and to
173      * detect events that should not have occurred, both of which are problems.
174      */
175     @Test
176     public void testSimultaneousEventsReset() {
177         // setup
178         double tol = 1e-10;
179         Propagator propagator = getPropagator(10);
180         boolean[] firstEventOccurred = {false};
181         List<Event> events = new ArrayList<>();
182 
183         TimeDetector detector1 = new TimeDetector(5)
184                 .withMaxCheck(10)
185                 .withThreshold(tol)
186                 .withHandler(new Handler(events, Action.RESET_STATE) {
187                     @Override
188                     public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing) {
189                         firstEventOccurred[0] = true;
190                         return super.eventOccurred(s, detector, increasing);
191                     }
192 
193                     @Override
194                     public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
195                         return oldState;
196                     }
197                 });
198         propagator.addEventDetector(detector1);
199         // this detector changes it's g function definition when detector1 fires
200         FunctionalDetector detector2 = new FunctionalDetector()
201                 .withMaxCheck(1)
202                 .withThreshold(tol)
203                 .withHandler(new RecordAndContinue(events))
204                 .withFunction(state -> {
205                             if (firstEventOccurred[0]) {
206                                 return new TimeDetector(1, 3, 5).g(state);
207                             }
208                             return new TimeDetector(5).g(state);
209                         }
210                 );
211         propagator.addEventDetector(detector2);
212 
213         // action
214         propagator.propagate(epoch.shiftedBy(20));
215 
216         // verify
217         // order is important to make sure the test checks what it is supposed to
218         Assertions.assertEquals(5, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
219         Assertions.assertTrue(events.get(0).isIncreasing());
220         Assertions.assertEquals(detector1, events.get(0).getDetector());
221         Assertions.assertEquals(5, events.get(1).getState().getDate().durationFrom(epoch), 0.0);
222         Assertions.assertTrue(events.get(1).isIncreasing());
223         Assertions.assertEquals(detector2, events.get(1).getDetector());
224         Assertions.assertEquals(2, events.size());
225     }
226 
227     /**
228      * When two event detectors have a discontinuous event caused by a {@link
229      * Action#RESET_STATE} or {@link Action#RESET_DERIVATIVES}. The two event detectors
230      * would each say they had an event that had to be handled before the other one, but
231      * neither would actually back up at all. For #684.
232      */
233     @Test
234     public void testSimultaneousDiscontinuousEventsAfterReset() {
235         // setup
236         double t = FastMath.PI;
237         double tol = 1e-10;
238         Propagator propagator = getPropagator(10);
239         List<Event> events = new ArrayList<>();
240         SpacecraftState newState = new SpacecraftState(new KeplerianOrbit(
241                 42e6, 0, 0, 0, 0, 0, PositionAngleType.TRUE, eci, epoch.shiftedBy(t), mu));
242 
243         TimeDetector resetDetector = new TimeDetector(t)
244                 .withHandler(new ResetHandler(events, newState))
245                 .withMaxCheck(10)
246                 .withThreshold(tol);
247         propagator.addEventDetector(resetDetector);
248         List<EventDetector> detectors = new ArrayList<>();
249         for (int i = 0; i < 2; i++) {
250             FunctionalDetector detector1 = new FunctionalDetector()
251                     .withFunction(s -> s.getOrbit().getA() - 10e6)
252                     .withThreshold(tol)
253                     .withMaxCheck(10)
254                     .withHandler(new RecordAndContinue(events));
255             propagator.addEventDetector(detector1);
256             detectors.add(detector1);
257         }
258 
259         // action
260         propagator.propagate(epoch.shiftedBy(10));
261 
262         // verify
263         Assertions.assertEquals(t, events.get(0).getState().getDate().durationFrom(epoch), tol);
264         Assertions.assertTrue(events.get(0).isIncreasing());
265         Assertions.assertEquals(resetDetector, events.get(0).getDetector());
266         // next two events can occur in either order
267         Assertions.assertEquals(t, events.get(1).getState().getDate().durationFrom(epoch), tol);
268         Assertions.assertTrue(events.get(1).isIncreasing());
269         Assertions.assertEquals(detectors.get(0), events.get(1).getDetector());
270         Assertions.assertEquals(t, events.get(2).getState().getDate().durationFrom(epoch), tol);
271         Assertions.assertTrue(events.get(2).isIncreasing());
272         Assertions.assertEquals(detectors.get(1), events.get(2).getDetector());
273         Assertions.assertEquals(events.size(), 3);
274     }
275 
276     /**
277      * test the g function switching with a period shorter than the tolerance. We don't
278      * need to find any of the events, but we do need to not crash. And we need to
279      * preserve the alternating increasing / decreasing sequence.
280      */
281     @Test
282     public void testFastSwitching() {
283         // setup
284         // step size of 10 to land in between two events we would otherwise miss
285         Propagator propagator = getPropagator(10);
286 
287         RecordAndContinue handler = new RecordAndContinue();
288         TimeDetector detector1 = new TimeDetector(9.9, 10.1, 12)
289                 .withHandler(handler)
290                 .withMaxCheck(10)
291                 .withThreshold(0.2);
292         propagator.addEventDetector(detector1);
293 
294         // action
295         propagator.propagate(epoch.shiftedBy(20));
296 
297         //verify
298         // finds one or three events. Not 2.
299         List<Event> events1 = handler.getEvents();
300         Assertions.assertEquals(1, events1.size());
301         Assertions.assertEquals(9.9, events1.get(0).getState().getDate().durationFrom(epoch), 0.1);
302         Assertions.assertEquals(true, events1.get(0).isIncreasing());
303     }
304 
305     /** "A Tricky Problem" from bug #239. */
306     @Test
307     public void testTrickyCaseLower() {
308         // setup
309         double maxCheck = 10;
310         double tolerance = 1e-6;
311         double t1 = 1.0, t2 = 15, t3 = 16, t4 = 17, t5 = 18;
312         // shared event list so we know the order in which they occurred
313         List<Event> events = new ArrayList<>();
314         TimeDetector detectorA = new TimeDetector(t3)
315                 .withHandler(new RecordAndContinue(events))
316                 .withMaxCheck(maxCheck)
317                 .withThreshold(tolerance);
318         TimeDetector detectorB = new TimeDetector(-10, t1, t2, t5)
319                 .withHandler(new RecordAndContinue(events))
320                 .withMaxCheck(maxCheck)
321                 .withThreshold(tolerance);
322         TimeDetector detectorC = new TimeDetector(t4)
323                 .withHandler(new Handler(events, Action.RESET_DERIVATIVES))
324                 .withMaxCheck(maxCheck)
325                 .withThreshold(tolerance);
326 
327         Propagator propagator = getPropagator(10);
328         propagator.addEventDetector(detectorA);
329         propagator.addEventDetector(detectorB);
330         propagator.addEventDetector(detectorC);
331 
332         // action
333         propagator.propagate(epoch.shiftedBy(30));
334 
335         //verify
336         // really we only care that the Rules of Event Handling are not violated,
337         // but I only know one way to do that in this case.
338         Assertions.assertEquals(5, events.size());
339         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
340         Assertions.assertEquals(false, events.get(0).isIncreasing());
341         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
342         Assertions.assertEquals(true, events.get(1).isIncreasing());
343         Assertions.assertEquals(t3, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
344         Assertions.assertEquals(true, events.get(2).isIncreasing());
345         Assertions.assertEquals(t4, events.get(3).getState().getDate().durationFrom(epoch), tolerance);
346         Assertions.assertEquals(true, events.get(3).isIncreasing());
347         Assertions.assertEquals(t5, events.get(4).getState().getDate().durationFrom(epoch), tolerance);
348         Assertions.assertEquals(false, events.get(4).isIncreasing());
349     }
350 
351     /**
352      * Test case for two event detectors. DetectorA has event at t2, DetectorB at t3, but
353      * due to the root finding tolerance DetectorB's event occurs at t1. With t1 < t2 <
354      * t3.
355      */
356     @Test
357     public void testRootFindingTolerance() {
358         //setup
359         double maxCheck = 10;
360         double t2 = 11, t3 = t2 + 1e-5;
361         List<Event> events = new ArrayList<>();
362         TimeDetector detectorA = new TimeDetector(t2)
363                 .withHandler(new RecordAndContinue(events))
364                 .withMaxCheck(maxCheck)
365                 .withThreshold(1e-6);
366         FlatDetector detectorB = new FlatDetector(t3)
367                 .withHandler(new RecordAndContinue(events))
368                 .withMaxCheck(maxCheck)
369                 .withThreshold(0.5);
370         Propagator propagator = getPropagator(10);
371         propagator.addEventDetector(detectorA);
372         propagator.addEventDetector(detectorB);
373 
374         // action
375         propagator.propagate(epoch.shiftedBy(30));
376 
377         // verify
378         // if these fail the event finding did its job,
379         // but this test isn't testing what it is supposed to be
380         Assertions.assertSame(detectorB, events.get(0).getDetector());
381         Assertions.assertSame(detectorA, events.get(1).getDetector());
382         Assertions.assertTrue(events.get(0).getState().getDate().compareTo(
383                 events.get(1).getState().getDate()) < 0);
384 
385         // check event detection worked
386         Assertions.assertEquals(2, events.size());
387         Assertions.assertEquals(t3, events.get(0).getState().getDate().durationFrom(epoch), 0.5);
388         Assertions.assertEquals(true, events.get(0).isIncreasing());
389         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), 1e-6);
390         Assertions.assertEquals(true, events.get(1).isIncreasing());
391     }
392 
393     /** check when g(t < root) < 0,  g(root + convergence) < 0. */
394     @Test
395     public void testRootPlusToleranceHasWrongSign() {
396         // setup
397         double maxCheck = 10;
398         double tolerance = 1e-6;
399         final double toleranceB = 0.3;
400         double t1 = 11, t2 = 11.1, t3 = 11.2;
401         // shared event list so we know the order in which they occurred
402         List<Event> events = new ArrayList<>();
403         TimeDetector detectorA = new TimeDetector(t2)
404                 .withHandler(new RecordAndContinue(events))
405                 .withMaxCheck(maxCheck)
406                 .withThreshold(1e-6);
407         TimeDetector detectorB = new TimeDetector(t1, t3)
408                 .withHandler(new RecordAndContinue(events))
409                 .withMaxCheck(maxCheck)
410                 .withThreshold(toleranceB);
411         Propagator propagator = getPropagator(10);
412         propagator.addEventDetector(detectorA);
413         propagator.addEventDetector(detectorB);
414 
415         // action
416         propagator.propagate(epoch.shiftedBy(30));
417 
418         // verify
419         // we only care that the rules are satisfied, there are other solutions
420         Assertions.assertEquals(3, events.size());
421         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), toleranceB);
422         Assertions.assertEquals(true, events.get(0).isIncreasing());
423         Assertions.assertSame(detectorB, events.get(0).getDetector());
424         Assertions.assertEquals(t3, events.get(1).getState().getDate().durationFrom(epoch), toleranceB);
425         Assertions.assertEquals(false, events.get(1).isIncreasing());
426         Assertions.assertSame(detectorB, events.get(1).getDetector());
427         Assertions.assertEquals(t2, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
428         Assertions.assertEquals(true, events.get(2).isIncreasing());
429         Assertions.assertSame(detectorA, events.get(2).getDetector());
430         // chronological
431         for (int i = 1; i < events.size(); i++) {
432             Assertions.assertTrue(events.get(i).getState().getDate().compareTo(
433                     events.get(i - 1).getState().getDate()) >= 0);
434         }
435     }
436 
437     /** check when g(t < root) < 0,  g(root + convergence) < 0. */
438     @Test
439     public void testRootPlusToleranceHasWrongSignAndLessThanTb() {
440         // setup
441         // test is fragile w.r.t. implementation and these parameters
442         double maxCheck = 10;
443         double tolerance = 0.5;
444         double t1 = 11, t2 = 11.4, t3 = 12.0;
445         // shared event list so we know the order in which they occurred
446         List<Event> events = new ArrayList<>();
447         FlatDetector detectorB = new FlatDetector(t1, t2, t3)
448                 .withHandler(new RecordAndContinue(events))
449                 .withMaxCheck(maxCheck)
450                 .withThreshold(tolerance);
451         Propagator propagator = getPropagator(10);
452         propagator.addEventDetector(detectorB);
453 
454         // action
455         propagator.propagate(epoch.shiftedBy(30));
456 
457         // verify
458         // allowed to find t1 or t3.
459         Assertions.assertEquals(1, events.size());
460         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
461         Assertions.assertEquals(true, events.get(0).isIncreasing());
462         Assertions.assertSame(detectorB, events.get(0).getDetector());
463     }
464 
465     /**
466      * Check when g(t) has a multiple root. e.g. g(t < root) < 0, g(root) = 0, g(t > root)
467      * < 0.
468      */
469     @Test
470     public void testDoubleRoot() {
471         // setup
472         double maxCheck = 10;
473         double tolerance = 1e-6;
474         double t1 = 11;
475         // shared event list so we know the order in which they occurred
476         List<Event> events = new ArrayList<>();
477         TimeDetector detectorA = new TimeDetector(t1)
478                 .withHandler(new RecordAndContinue(events))
479                 .withMaxCheck(maxCheck)
480                 .withThreshold(tolerance);
481         TimeDetector detectorB = new TimeDetector(t1, t1)
482                 .withHandler(new RecordAndContinue(events))
483                 .withMaxCheck(maxCheck)
484                 .withThreshold(tolerance);
485         Propagator propagator = getPropagator(10);
486         propagator.addEventDetector(detectorA);
487         propagator.addEventDetector(detectorB);
488 
489         // action
490         propagator.propagate(epoch.shiftedBy(30));
491 
492         // verify
493         Assertions.assertEquals(1, events.size());
494         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
495         Assertions.assertEquals(true, events.get(0).isIncreasing());
496         Assertions.assertSame(detectorA, events.get(0).getDetector());
497         // detector worked correctly
498         Assertions.assertTrue(detectorB.g(state(t1)) == 0.0);
499         Assertions.assertTrue(detectorB.g(state(t1 - 1e-6)) < 0);
500         Assertions.assertTrue(detectorB.g(state(t1 + 1e-6)) < 0);
501     }
502 
503     /**
504      * Check when g(t) has a multiple root. e.g. g(t < root) > 0, g(root) = 0, g(t > root)
505      * > 0.
506      */
507     @Test
508     public void testDoubleRootOppositeSign() {
509         // setup
510         double maxCheck = 10;
511         double tolerance = 1e-6;
512         double t1 = 11;
513         // shared event list so we know the order in which they occurred
514         List<Event> events = new ArrayList<>();
515         TimeDetector detectorA = new TimeDetector(t1)
516                 .withHandler(new RecordAndContinue(events))
517                 .withMaxCheck(maxCheck)
518                 .withThreshold(tolerance);
519         ContinuousDetector detectorB = new ContinuousDetector(-20, t1, t1)
520                 .withHandler(new RecordAndContinue(events))
521                 .withMaxCheck(maxCheck)
522                 .withThreshold(tolerance);
523         Propagator propagator = getPropagator(10);
524         propagator.addEventDetector(detectorA);
525         propagator.addEventDetector(detectorB);
526 
527         // action
528         propagator.propagate(epoch.shiftedBy(30));
529 
530         // verify
531         Assertions.assertEquals(1, events.size());
532         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
533         Assertions.assertEquals(true, events.get(0).isIncreasing());
534         Assertions.assertSame(detectorA, events.get(0).getDetector());
535         // detector worked correctly
536         Assertions.assertEquals(0.0, detectorB.g(state(t1)), 0.0);
537         Assertions.assertTrue(detectorB.g(state(t1 - 1e-6)) > 0);
538         Assertions.assertTrue(detectorB.g(state(t1 + 1e-6)) > 0);
539     }
540 
541     /** check root finding when zero at both ends. */
542     @Test
543     public void testZeroAtBeginningAndEndOfInterval() {
544         // setup
545         double maxCheck = 10;
546         double tolerance = 1e-6;
547         double t1 = 10, t2 = 20;
548         // shared event list so we know the order in which they occurred
549         List<Event> events = new ArrayList<>();
550         ContinuousDetector detectorA = new ContinuousDetector(t1, t2)
551                 .withHandler(new RecordAndContinue(events))
552                 .withMaxCheck(maxCheck)
553                 .withThreshold(tolerance);
554         Propagator propagator = getPropagator(10);
555         propagator.addEventDetector(detectorA);
556         // action
557         propagator.propagate(epoch.shiftedBy(30));
558 
559         // verify
560         Assertions.assertEquals(2, events.size());
561         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
562         Assertions.assertEquals(true, events.get(0).isIncreasing());
563         Assertions.assertSame(detectorA, events.get(0).getDetector());
564         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
565         Assertions.assertEquals(false, events.get(1).isIncreasing());
566         Assertions.assertSame(detectorA, events.get(1).getDetector());
567     }
568 
569     /** check root finding when zero at both ends. */
570     @Test
571     public void testZeroAtBeginningAndEndOfIntervalOppositeSign() {
572         // setup
573         double maxCheck = 10;
574         double tolerance = 1e-6;
575         double t1 = 10, t2 = 20;
576         // shared event list so we know the order in which they occurred
577         List<Event> events = new ArrayList<>();
578         ContinuousDetector detectorA = new ContinuousDetector(-10, t1, t2)
579                 .withHandler(new RecordAndContinue(events))
580                 .withMaxCheck(maxCheck)
581                 .withThreshold(tolerance);
582         Propagator propagator = getPropagator(10);
583         propagator.addEventDetector(detectorA);
584 
585         // action
586         propagator.propagate(epoch.shiftedBy(30));
587 
588         // verify
589         Assertions.assertEquals(2, events.size());
590         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
591         Assertions.assertEquals(false, events.get(0).isIncreasing());
592         Assertions.assertSame(detectorA, events.get(0).getDetector());
593         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
594         Assertions.assertEquals(true, events.get(1).isIncreasing());
595         Assertions.assertSame(detectorA, events.get(1).getDetector());
596     }
597 
598     /** Test where an event detector has to back up multiple times. */
599     @Test
600     public void testMultipleBackups() {
601         // setup
602         double maxCheck = 10;
603         double tolerance = 1e-6;
604         double t1 = 1.0, t2 = 2, t3 = 3, t4 = 4, t5 = 5, t6 = 6.5, t7 = 7;
605         // shared event list so we know the order in which they occurred
606         List<Event> events = new ArrayList<>();
607         ContinuousDetector detectorA = new ContinuousDetector(t6)
608                 .withHandler(new RecordAndContinue(events))
609                 .withMaxCheck(maxCheck)
610                 .withThreshold(tolerance);
611         FlatDetector detectorB = new FlatDetector(t1, t3, t4, t7)
612                 .withHandler(new RecordAndContinue(events))
613                 .withMaxCheck(maxCheck)
614                 .withThreshold(tolerance);
615         ContinuousDetector detectorC = new ContinuousDetector(t2, t5)
616                 .withHandler(new RecordAndContinue(events))
617                 .withMaxCheck(maxCheck)
618                 .withThreshold(tolerance);
619 
620         Propagator propagator = getPropagator(10);
621         propagator.addEventDetector(detectorA);
622         propagator.addEventDetector(detectorB);
623         propagator.addEventDetector(detectorC);
624 
625         // action
626         propagator.propagate(epoch.shiftedBy(30));
627 
628         //verify
629         // need at least 5 events to check that multiple backups occurred
630         Assertions.assertEquals(5, events.size());
631         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
632         Assertions.assertEquals(true, events.get(0).isIncreasing());
633         Assertions.assertEquals(detectorB, events.get(0).getDetector());
634         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
635         Assertions.assertEquals(true, events.get(1).isIncreasing());
636         Assertions.assertEquals(detectorC, events.get(1).getDetector());
637         // reporting t3 and t4 is optional, seeing them is not.
638         // we know a root was found at t3 because events are reported at t2 and t5.
639         /*
640         Assertions.assertEquals(t3, events.get(2).getT(), tolerance);
641         Assertions.assertEquals(false, events.get(2).isIncreasing());
642         Assertions.assertEquals(detectorB, events.get(2).getHandler());
643         Assertions.assertEquals(t4, events.get(3).getT(), tolerance);
644         Assertions.assertEquals(true, events.get(3).isIncreasing());
645         Assertions.assertEquals(detectorB, events.get(3).getHandler());
646         */
647         Assertions.assertEquals(t5, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
648         Assertions.assertEquals(false, events.get(2).isIncreasing());
649         Assertions.assertEquals(detectorC, events.get(2).getDetector());
650         Assertions.assertEquals(t6, events.get(3).getState().getDate().durationFrom(epoch), tolerance);
651         Assertions.assertEquals(true, events.get(3).isIncreasing());
652         Assertions.assertEquals(detectorA, events.get(3).getDetector());
653         Assertions.assertEquals(t7, events.get(4).getState().getDate().durationFrom(epoch), tolerance);
654         Assertions.assertEquals(false, events.get(4).isIncreasing());
655         Assertions.assertEquals(detectorB, events.get(4).getDetector());
656     }
657 
658     /** Test a reset event triggering another event at the same time. */
659     @Test
660     public void testEventCausedByStateReset() {
661         // setup
662         double maxCheck = 10;
663         double tolerance = 1e-6;
664         double t1 = 15.0;
665         SpacecraftState newState = new SpacecraftState(new KeplerianOrbit(
666                 6378137 + 500e3, 0, FastMath.PI / 2, 0, 0,
667                 FastMath.PI / 2, PositionAngleType.TRUE, eci, epoch.shiftedBy(t1), mu));
668         // shared event list so we know the order in which they occurred
669         List<Event> events = new ArrayList<>();
670         TimeDetector detectorA = new TimeDetector(t1)
671                 .withHandler(new Handler(events, Action.RESET_STATE) {
672                     @Override
673                     public SpacecraftState resetState(EventDetector detector,
674                                                       SpacecraftState oldState) {
675                         return newState;
676                     }
677                 })
678                 .withMaxCheck(maxCheck)
679                 .withThreshold(tolerance);
680         LatitudeCrossingDetector detectorB =
681                 new LatitudeCrossingDetector(earth, FastMath.toRadians(80))
682                         .withHandler(new RecordAndContinue(events))
683                         .withMaxCheck(maxCheck)
684                         .withThreshold(tolerance);
685 
686         Propagator propagator = getPropagator(10);
687         propagator.addEventDetector(detectorA);
688         propagator.addEventDetector(detectorB);
689 
690         // action
691         propagator.propagate(epoch.shiftedBy(40.0));
692 
693         //verify
694         // really we only care that the Rules of Event Handling are not violated,
695         Assertions.assertEquals(2, events.size());
696         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
697         Assertions.assertEquals(true, events.get(0).isIncreasing());
698         Assertions.assertEquals(detectorA, events.get(0).getDetector());
699         Assertions.assertEquals(t1, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
700         Assertions.assertEquals(true, events.get(1).isIncreasing());
701         Assertions.assertEquals(detectorB, events.get(1).getDetector());
702     }
703 
704     /** check when t + tolerance == t. */
705     @Test
706     public void testConvergenceTooTight() {
707         // setup
708         double maxCheck = 10;
709         double tolerance = 1e-18;
710         double t1 = 15;
711         // shared event list so we know the order in which they occurred
712         List<Event> events = new ArrayList<>();
713         ContinuousDetector detectorA = new ContinuousDetector(t1)
714                 .withHandler(new RecordAndContinue(events))
715                 .withMaxCheck(maxCheck)
716                 .withThreshold(tolerance);
717         Propagator propagator = getPropagator(10);
718         propagator.addEventDetector(detectorA);
719 
720         // action
721         propagator.propagate(epoch.shiftedBy(30));
722 
723         // verify
724         Assertions.assertEquals(1, events.size());
725         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
726         Assertions.assertEquals(true, events.get(0).isIncreasing());
727         Assertions.assertSame(detectorA, events.get(0).getDetector());
728     }
729 
730     /**
731      * test when one event detector changes the definition of another's g function before
732      * the end of the step as a result of a continue action. Not sure if this should be
733      * officially supported, but it is used in Orekit's DateDetector, it's useful, and not
734      * too hard to implement.
735      */
736     @Test
737     public void testEventChangesGFunctionDefinition() {
738         // setup
739         double maxCheck = 5;
740         double tolerance = 1e-6;
741         double t1 = 11, t2 = 19;
742         // shared event list so we know the order in which they occurred
743         List<Event> events = new ArrayList<>();
744         // mutable boolean
745         boolean[] swap = new boolean[1];
746         final ContinuousDetector detectorA = new ContinuousDetector(t1)
747                 .withThreshold(maxCheck)
748                 .withThreshold(tolerance)
749                 .withHandler(new RecordAndContinue(events) {
750                     @Override
751                     public Action eventOccurred(SpacecraftState s,
752                                                 EventDetector detector,
753                                                 boolean increasing) {
754                         swap[0] = true;
755                         return super.eventOccurred(s, detector, increasing);
756                     }
757                 });
758         ContinuousDetector detectorB = new ContinuousDetector(t2);
759         EventDetector detectorC = new FunctionalDetector()
760                 .withMaxCheck(maxCheck)
761                 .withThreshold(tolerance)
762                 .withMaxIter(100)
763                 .withHandler(new RecordAndContinue(events))
764                 .withFunction(s -> swap[0] ? detectorB.g(s) : -1);
765         Propagator propagator = getPropagator(10);
766         propagator.addEventDetector(detectorA);
767         propagator.addEventDetector(detectorC);
768 
769         // action
770         propagator.propagate(epoch.shiftedBy(30));
771 
772         // verify
773         Assertions.assertEquals(2, events.size());
774         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
775         Assertions.assertEquals(true, events.get(0).isIncreasing());
776         Assertions.assertSame(detectorA, events.get(0).getDetector());
777         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
778         Assertions.assertEquals(true, events.get(1).isIncreasing());
779         Assertions.assertSame(detectorC, events.get(1).getDetector());
780     }
781 
782     /**
783      * test when one event detector changes the definition of another's g function before
784      * the end of the step as a result of an event occurring. In this case the change
785      * cancels the occurrence of the event.
786      */
787     @Test
788     public void testEventChangesGFunctionDefinitionCancel() {
789         // setup
790         double maxCheck = 5;
791         double tolerance = 1e-6;
792         double t1 = 11, t2 = 11.1;
793         // shared event list so we know the order in which they occurred
794         List<Event> events = new ArrayList<>();
795         // mutable boolean
796         boolean[] swap = new boolean[1];
797         final ContinuousDetector detectorA = new ContinuousDetector(t1)
798                 .withMaxCheck(maxCheck)
799                 .withThreshold(tolerance)
800                 .withHandler(new RecordAndContinue(events) {
801                     @Override
802                     public Action eventOccurred(SpacecraftState state,
803                                                 EventDetector detector,
804                                                 boolean increasing) {
805                         swap[0] = true;
806                         super.eventOccurred(state, detector, increasing);
807                         return Action.RESET_EVENTS;
808                     }
809                 });
810         final ContinuousDetector detectorB = new ContinuousDetector(t2);
811         EventDetector detectorC = new FunctionalDetector()
812                 .withMaxCheck(maxCheck)
813                 .withThreshold(tolerance)
814                 .withMaxIter(100)
815                 .withHandler(new RecordAndContinue(events))
816                 .withFunction(state -> swap[0] ? -1 : detectorB.g(state));
817         Propagator propagator = getPropagator(10);
818         propagator.addEventDetector(detectorA);
819         propagator.addEventDetector(detectorC);
820 
821         // action
822         propagator.propagate(epoch.shiftedBy(30));
823 
824         // verify
825         Assertions.assertEquals(1, events.size());
826         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
827         Assertions.assertEquals(true, events.get(0).isIncreasing());
828         Assertions.assertSame(detectorA, events.get(0).getDetector());
829     }
830 
831     /**
832      * test when one event detector changes the definition of another's g function before
833      * the end of the step as a result of an event occurring. In this case the change
834      * delays the occurrence of the event.
835      */
836     @Test
837     public void testEventChangesGFunctionDefinitionDelay() {
838         // setup
839         double maxCheck = 5;
840         double tolerance = 1e-6;
841         double t1 = 11, t2 = 11.1, t3 = 11.2;
842         // shared event list so we know the order in which they occurred
843         List<Event> events = new ArrayList<>();
844         // mutable boolean
845         boolean[] swap = new boolean[1];
846         final ContinuousDetector detectorA = new ContinuousDetector(t1)
847                 .withMaxCheck(maxCheck)
848                 .withThreshold(tolerance)
849                 .withHandler(new RecordAndContinue(events) {
850                     @Override
851                     public Action eventOccurred(SpacecraftState state,
852                                                 EventDetector detector,
853                                                 boolean increasing) {
854                         swap[0] = true;
855                         super.eventOccurred(state, detector, increasing);
856                         return Action.RESET_EVENTS;
857                     }
858                 });
859         final ContinuousDetector detectorB = new ContinuousDetector(t2);
860         final ContinuousDetector detectorD = new ContinuousDetector(t3);
861         EventDetector detectorC = new FunctionalDetector()
862                 .withMaxCheck(maxCheck)
863                 .withThreshold(tolerance)
864                 .withMaxIter(100)
865                 .withHandler(new RecordAndContinue(events))
866                 .withFunction(state -> swap[0] ? detectorD.g(state) : detectorB.g(state));
867         Propagator propagator = getPropagator(10);
868         propagator.addEventDetector(detectorA);
869         propagator.addEventDetector(detectorC);
870 
871         // action
872         propagator.propagate(epoch.shiftedBy(30));
873 
874         // verify
875         Assertions.assertEquals(2, events.size());
876         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
877         Assertions.assertEquals(true, events.get(0).isIncreasing());
878         Assertions.assertSame(detectorA, events.get(0).getDetector());
879         Assertions.assertEquals(t3, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
880         Assertions.assertEquals(true, events.get(1).isIncreasing());
881         Assertions.assertSame(detectorC, events.get(1).getDetector());
882     }
883 
884     /**
885      * test when one event detector changes the definition of another's g function before
886      * the end of the step as a result of an event occurring. In this case the change
887      * causes the event to happen sooner than originally expected.
888      */
889     @Test
890     public void testEventChangesGFunctionDefinitionAccelerate() {
891         // setup
892         double maxCheck = 5;
893         double tolerance = 1e-6;
894         double t1 = 11, t2 = 11.1, t3 = 11.2;
895         // shared event list so we know the order in which they occurred
896         List<Event> events = new ArrayList<>();
897         // mutable boolean
898         boolean[] swap = new boolean[1];
899         final ContinuousDetector detectorA = new ContinuousDetector(t1)
900                 .withMaxCheck(maxCheck)
901                 .withThreshold(tolerance)
902                 .withHandler(new RecordAndContinue(events) {
903                     @Override
904                     public Action eventOccurred(SpacecraftState state,
905                                                 EventDetector detector,
906                                                 boolean increasing) {
907                         swap[0] = true;
908                         super.eventOccurred(state, detector, increasing);
909                         return Action.RESET_EVENTS;
910                     }
911                 });
912         final ContinuousDetector detectorB = new ContinuousDetector(t2);
913         final ContinuousDetector detectorD = new ContinuousDetector(t3);
914         EventDetector detectorC = new FunctionalDetector()
915                 .withMaxCheck(maxCheck)
916                 .withThreshold(tolerance)
917                 .withMaxIter(100)
918                 .withHandler(new RecordAndContinue(events))
919                 .withFunction(state -> swap[0] ? detectorB.g(state) : detectorD.g(state));
920         Propagator propagator = getPropagator(10);
921         propagator.addEventDetector(detectorA);
922         propagator.addEventDetector(detectorC);
923 
924         // action
925         propagator.propagate(epoch.shiftedBy(30));
926 
927         // verify
928         Assertions.assertEquals(2, events.size());
929         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
930         Assertions.assertEquals(true, events.get(0).isIncreasing());
931         Assertions.assertSame(detectorA, events.get(0).getDetector());
932         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
933         Assertions.assertEquals(true, events.get(1).isIncreasing());
934         Assertions.assertSame(detectorC, events.get(1).getDetector());
935     }
936 
937     /** check when root finding tolerance > event finding tolerance. */
938     @Test
939     public void testToleranceStop() {
940         // setup
941         double maxCheck = 10;
942         double tolerance = 1e-18; // less than 1 ulp
943         double t1 = 15.1;
944         // shared event list so we know the order in which they occurred
945         List<Event> events = new ArrayList<>();
946         FlatDetector detectorA = new FlatDetector(t1)
947                 .withHandler(new Handler(events, Action.STOP))
948                 .withMaxCheck(maxCheck)
949                 .withThreshold(tolerance);
950         Propagator propagator = getPropagator(10);
951         propagator.addEventDetector(detectorA);
952 
953         // action
954         SpacecraftState finalState = propagator.propagate(epoch.shiftedBy(30));
955 
956         // verify
957         Assertions.assertEquals(1, events.size());
958         // use root finder tolerance instead of event finder tolerance.
959         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
960         Assertions.assertEquals(true, events.get(0).isIncreasing());
961         Assertions.assertSame(detectorA, events.get(0).getDetector());
962         Assertions.assertEquals(t1, finalState.getDate().durationFrom(epoch), tolerance);
963 
964         // try to resume propagation
965         finalState = propagator.propagate(epoch.shiftedBy(30));
966 
967         // verify it got to the end
968         Assertions.assertEquals(30.0, finalState.getDate().durationFrom(epoch), 0.0);
969     }
970 
971     /**
972      * The root finder requires the start point to be in the interval (a, b) which is hard
973      * when there aren't many numbers between a and b. This test uses a second event
974      * detector to force a very small window for the first event detector.
975      */
976     @Test
977     public void testShortBracketingInterval() {
978         // setup
979         double maxCheck = 10;
980         double tolerance = 1e-6;
981         final double t1 = FastMath.nextUp(10.0), t2 = 10.5;
982         // shared event list so we know the order in which they occurred
983         List<Event> events = new ArrayList<>();
984         // never zero so there is no easy way out
985         EventDetector detectorA = new FunctionalDetector()
986                 .withMaxCheck(maxCheck)
987                 .withThreshold(tolerance)
988                 .withMaxIter(100)
989                 .withHandler(new RecordAndContinue(events))
990                 .withFunction(state -> {
991                     final AbsoluteDate t = state.getDate();
992                     if (t.compareTo(epoch.shiftedBy(t1)) < 0) {
993                         return -1;
994                     } else if (t.compareTo(epoch.shiftedBy(t2)) < 0) {
995                         return 1;
996                     } else {
997                         return -1;
998                     }
999                 });
1000         TimeDetector detectorB = new TimeDetector(t1)
1001                 .withHandler(new RecordAndContinue(events))
1002                 .withMaxCheck(maxCheck)
1003                 .withThreshold(tolerance);
1004         Propagator propagator = getPropagator(10);
1005         propagator.addEventDetector(detectorA);
1006         propagator.addEventDetector(detectorB);
1007 
1008         // action
1009         propagator.propagate(epoch.shiftedBy(30.0));
1010 
1011         // verify
1012         Assertions.assertEquals(3, events.size());
1013         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1014         Assertions.assertEquals(true, events.get(0).isIncreasing());
1015         Assertions.assertSame(detectorA, events.get(0).getDetector());
1016         Assertions.assertEquals(t1, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
1017         Assertions.assertEquals(true, events.get(1).isIncreasing());
1018         Assertions.assertSame(detectorB, events.get(1).getDetector());
1019         Assertions.assertEquals(t2, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
1020         Assertions.assertEquals(false, events.get(2).isIncreasing());
1021         Assertions.assertSame(detectorA, events.get(2).getDetector());
1022     }
1023 
1024     /** check when root finding tolerance > event finding tolerance. */
1025     @Test
1026     public void testToleranceMaxIterations() {
1027         // setup
1028         double maxCheck = 10;
1029         double tolerance = 1e-18; // less than 1 ulp
1030         AbsoluteDate t1 = epoch.shiftedBy(15).shiftedBy(FastMath.ulp(15.0) / 8);
1031         // shared event list so we know the order in which they occurred
1032         List<Event> events = new ArrayList<>();
1033         FlatDetector detectorA = new FlatDetector(t1)
1034                 .withHandler(new RecordAndContinue(events))
1035                 .withMaxCheck(maxCheck)
1036                 .withThreshold(tolerance);
1037         Propagator propagator = getPropagator(10);
1038         propagator.addEventDetector(detectorA);
1039 
1040         // action
1041         propagator.propagate(epoch.shiftedBy(30));
1042 
1043         // verify
1044         Assertions.assertEquals(1, events.size());
1045         // use root finder tolerance instead of event finder tolerance.
1046         Assertions.assertEquals(t1.durationFrom(epoch),
1047                 events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1048         Assertions.assertEquals(true, events.get(0).isIncreasing());
1049         Assertions.assertSame(detectorA, events.get(0).getDetector());
1050     }
1051 
1052     /** Check that steps are restricted correctly with a continue event. */
1053     @Test
1054     public void testEventStepHandler() {
1055         // setup
1056         double tolerance = 1e-18;
1057         Propagator propagator = getPropagator(10);
1058         propagator.addEventDetector(new TimeDetector(5)
1059                 .withHandler(new Handler(Action.CONTINUE))
1060                 .withThreshold(tolerance));
1061         StepHandler stepHandler = new StepHandler();
1062         propagator.setStepHandler(stepHandler);
1063         AbsoluteDate target = epoch.shiftedBy(10);
1064 
1065         // action
1066         SpacecraftState finalState = propagator.propagate(target);
1067 
1068         // verify
1069         Assertions.assertEquals(10.0, finalState.getDate().durationFrom(epoch), tolerance);
1070         Assertions.assertEquals(0.0,
1071                 stepHandler.initialState.getDate().durationFrom(epoch), tolerance);
1072         Assertions.assertEquals(10.0, stepHandler.targetDate.durationFrom(epoch), tolerance);
1073         Assertions.assertEquals(10.0,
1074                 stepHandler.finalState.getDate().durationFrom(epoch), tolerance);
1075         OrekitStepInterpolator interpolator = stepHandler.interpolators.get(0);
1076         Assertions.assertEquals(0.0,
1077                 interpolator.getPreviousState().getDate().durationFrom(epoch), tolerance);
1078         Assertions.assertEquals(5.0,
1079                 interpolator.getCurrentState().getDate().durationFrom(epoch), tolerance);
1080         interpolator = stepHandler.interpolators.get(1);
1081         Assertions.assertEquals(5.0,
1082                 interpolator.getPreviousState().getDate().durationFrom(epoch), tolerance);
1083         Assertions.assertEquals(10.0,
1084                 interpolator.getCurrentState().getDate().durationFrom(epoch), tolerance);
1085         Assertions.assertEquals(2, stepHandler.interpolators.size());
1086     }
1087 
1088     /**
1089      * Test {@link EventHandler#resetState(EventDetector, SpacecraftState)} returns {@code
1090      * null}.
1091      */
1092     @Test
1093     public void testEventCausedByDerivativesReset() {
1094         // setup
1095         TimeDetector detectorA = new TimeDetector(15.0)
1096                 .withHandler(new Handler(Action.RESET_STATE){
1097                     @Override
1098                     public SpacecraftState resetState(EventDetector d, SpacecraftState s) {
1099                         return null;
1100                     }
1101                 })
1102                 .withMaxCheck(10)
1103                 .withThreshold(1e-6);
1104         Propagator propagator = getPropagator(10);
1105         propagator.addEventDetector(detectorA);
1106 
1107         try {
1108             // action
1109             propagator.propagate(epoch.shiftedBy(20.0));
1110             Assertions.fail("Expected Exception");
1111         } catch (NullPointerException e) {
1112             // expected
1113         }
1114     }
1115 
1116 
1117     /* The following tests are copies of the above tests, except that they propagate in
1118      * the reverse direction and all the signs on the time values are negated.
1119      */
1120 
1121 
1122     @Test
1123     public void testCloseEventsFirstOneIsResetReverse() {
1124         // setup
1125         // a fairly rare state to reproduce this bug. Two dates, d1 < d2, that
1126         // are very close. Event triggers on d1 will reset state to break out of
1127         // event handling loop in AbstractIntegrator.acceptStep(). At this point
1128         // detector2 has g0Positive == true but the event time is set to just
1129         // before the event so g(t0) is negative. Now on processing the
1130         // next step the root solver checks the sign of the start, midpoint,
1131         // and end of the interval so we need another event less than half a max
1132         // check interval after d2 so that the g function will be negative at
1133         // all three times. Then we get a non bracketing exception.
1134         Propagator propagator = getPropagator(10.0);
1135 
1136         // switched for 9 to 1 to be close to the start of the step
1137         double t1 = -1;
1138         Handler handler1 = new Handler(Action.RESET_DERIVATIVES);
1139         TimeDetector detector1 = new TimeDetector(t1)
1140                 .withHandler(handler1)
1141                 .withMaxCheck(10)
1142                 .withThreshold(1e-9);
1143         propagator.addEventDetector(detector1);
1144         RecordAndContinue handler2 = new RecordAndContinue();
1145         TimeDetector detector2 = new TimeDetector(t1 - 1e-15, t1 - 4.9)
1146                 .withHandler(handler2)
1147                 .withMaxCheck(11)
1148                 .withThreshold(1e-9);
1149         propagator.addEventDetector(detector2);
1150 
1151         // action
1152         propagator.propagate(epoch.shiftedBy(-20));
1153 
1154         // verify
1155         List<Event> events1 = handler1.getEvents();
1156         Assertions.assertEquals(1, events1.size());
1157         Assertions.assertEquals(t1, events1.get(0).getState().getDate().durationFrom(epoch), 0.0);
1158         List<Event> events2 = handler2.getEvents();
1159         Assertions.assertEquals(0, events2.size());
1160     }
1161 
1162     @Test
1163     public void testCloseEventsReverse() {
1164         // setup
1165         double tolerance = 1;
1166         Propagator propagator = getPropagator(10);
1167 
1168         RecordAndContinue handler1 = new RecordAndContinue();
1169         TimeDetector detector1 = new TimeDetector(-5)
1170                 .withHandler(handler1)
1171                 .withMaxCheck(10)
1172                 .withThreshold(tolerance);
1173         propagator.addEventDetector(detector1);
1174         RecordAndContinue handler2 = new RecordAndContinue();
1175         TimeDetector detector2 = new TimeDetector(-5.5)
1176                 .withHandler(handler2)
1177                 .withMaxCheck(10)
1178                 .withThreshold(tolerance);
1179         propagator.addEventDetector(detector2);
1180 
1181         // action
1182         propagator.propagate(epoch.shiftedBy(-20));
1183 
1184         // verify
1185         List<Event> events1 = handler1.getEvents();
1186         Assertions.assertEquals(1, events1.size());
1187         Assertions.assertEquals(-5, events1.get(0).getState().getDate().durationFrom(epoch), tolerance);
1188         List<Event> events2 = handler2.getEvents();
1189         Assertions.assertEquals(1, events2.size());
1190         Assertions.assertEquals(-5.5, events2.get(0).getState().getDate().durationFrom(epoch), tolerance);
1191     }
1192 
1193     @Test
1194     public void testSimultaneousEventsReverse() {
1195         // setup
1196         Propagator propagator = getPropagator(10);
1197 
1198         RecordAndContinue handler1 = new RecordAndContinue();
1199         TimeDetector detector1 = new TimeDetector(-5)
1200                 .withHandler(handler1)
1201                 .withMaxCheck(10)
1202                 .withThreshold(1);
1203         propagator.addEventDetector(detector1);
1204         RecordAndContinue handler2 = new RecordAndContinue();
1205         TimeDetector detector2 = new TimeDetector(-5)
1206                 .withHandler(handler2)
1207                 .withMaxCheck(10)
1208                 .withThreshold(1);
1209         propagator.addEventDetector(detector2);
1210 
1211         // action
1212         propagator.propagate(epoch.shiftedBy(-20));
1213 
1214         // verify
1215         List<Event> events1 = handler1.getEvents();
1216         Assertions.assertEquals(1, events1.size());
1217         Assertions.assertEquals(-5, events1.get(0).getState().getDate().durationFrom(epoch), 0.0);
1218         List<Event> events2 = handler2.getEvents();
1219         Assertions.assertEquals(1, events2.size());
1220         Assertions.assertEquals(-5, events2.get(0).getState().getDate().durationFrom(epoch), 0.0);
1221     }
1222 
1223     /**
1224      * Previously there were some branches when tryAdvance() returned false but did not
1225      * set {@code t0 = t}. This allowed the order of events to not be chronological and to
1226      * detect events that should not have occurred, both of which are problems.
1227      */
1228     @Test
1229     public void testSimultaneousEventsResetReverse() {
1230         // setup
1231         double tol = 1e-10;
1232         Propagator propagator = getPropagator(10);
1233         boolean[] firstEventOccurred = {false};
1234         List<Event> events = new ArrayList<>();
1235 
1236         TimeDetector detector1 = new TimeDetector(-5)
1237                 .withMaxCheck(10)
1238                 .withThreshold(tol)
1239                 .withHandler(new Handler(events, Action.RESET_STATE) {
1240                     @Override
1241                     public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing) {
1242                         firstEventOccurred[0] = true;
1243                         return super.eventOccurred(s, detector, increasing);
1244                     }
1245 
1246                     @Override
1247                     public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
1248                         return oldState;
1249                     }
1250                 });
1251         propagator.addEventDetector(detector1);
1252         // this detector changes it's g function definition when detector1 fires
1253         FunctionalDetector detector2 = new FunctionalDetector()
1254                 .withMaxCheck(1)
1255                 .withThreshold(tol)
1256                 .withHandler(new RecordAndContinue(events))
1257                 .withFunction(state -> {
1258                             if (firstEventOccurred[0]) {
1259                                 return new TimeDetector(-1, -3, -5).g(state);
1260                             }
1261                             return new TimeDetector(-5).g(state);
1262                         }
1263                 );
1264         propagator.addEventDetector(detector2);
1265 
1266         // action
1267         propagator.propagate(epoch.shiftedBy(-20));
1268 
1269         // verify
1270         // order is important to make sure the test checks what it is supposed to
1271         Assertions.assertEquals(-5, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
1272         Assertions.assertTrue(events.get(0).isIncreasing());
1273         Assertions.assertEquals(detector1, events.get(0).getDetector());
1274         Assertions.assertEquals(-5, events.get(1).getState().getDate().durationFrom(epoch), 0.0);
1275         Assertions.assertTrue(events.get(1).isIncreasing());
1276         Assertions.assertEquals(detector2, events.get(1).getDetector());
1277         Assertions.assertEquals(2, events.size());
1278     }
1279 
1280     /**
1281      * test the g function switching with a period shorter than the tolerance. We don't
1282      * need to find any of the events, but we do need to not crash. And we need to
1283      * preserve the alternating increasing / decreasing sequence.
1284      */
1285     @Test
1286     public void testFastSwitchingReverse() {
1287         // setup
1288         // step size of 10 to land in between two events we would otherwise miss
1289         Propagator propagator = getPropagator(10);
1290 
1291         List<Event> events = new ArrayList<>();
1292         TimeDetector detector1 = new TimeDetector(-9.9, -10.1, -12)
1293                 .withHandler(new RecordAndContinue(events))
1294                 .withMaxCheck(10)
1295                 .withThreshold(0.2);
1296         propagator.addEventDetector(detector1);
1297 
1298         // action
1299         propagator.propagate(epoch.shiftedBy(-20));
1300 
1301         //verify
1302         // finds one or three events. Not 2.
1303         Assertions.assertEquals(1, events.size());
1304         Assertions.assertEquals(-9.9, events.get(0).getState().getDate().durationFrom(epoch), 0.2);
1305         Assertions.assertEquals(true, events.get(0).isIncreasing());
1306     }
1307 
1308     /** "A Tricky Problem" from bug #239. */
1309     @Test
1310     public void testTrickyCaseLowerReverse() {
1311         // setup
1312         double maxCheck = 10;
1313         double tolerance = 1e-6;
1314         double t1 = -1.0, t2 = -15, t3 = -16, t4 = -17, t5 = -18;
1315         // shared event list so we know the order in which they occurred
1316         List<Event> events = new ArrayList<>();
1317         TimeDetector detectorA = new TimeDetector(t3)
1318                 .withHandler(new RecordAndContinue(events))
1319                 .withMaxCheck(maxCheck)
1320                 .withThreshold(tolerance);
1321         TimeDetector detectorB = new TimeDetector(-50, t1, t2, t5)
1322                 .withHandler(new RecordAndContinue(events))
1323                 .withMaxCheck(maxCheck)
1324                 .withThreshold(tolerance);
1325         TimeDetector detectorC = new TimeDetector(t4)
1326                 .withHandler(new Handler(events, Action.RESET_DERIVATIVES))
1327                 .withMaxCheck(maxCheck)
1328                 .withThreshold(tolerance);
1329 
1330         Propagator propagator = getPropagator(10);
1331         propagator.addEventDetector(detectorA);
1332         propagator.addEventDetector(detectorB);
1333         propagator.addEventDetector(detectorC);
1334 
1335         // action
1336         propagator.propagate(epoch.shiftedBy(-30));
1337 
1338         //verify
1339         // really we only care that the Rules of Event Handling are not violated,
1340         // but I only know one way to do that in this case.
1341         Assertions.assertEquals(5, events.size());
1342         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1343         Assertions.assertEquals(false, events.get(0).isIncreasing());
1344         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
1345         Assertions.assertEquals(true, events.get(1).isIncreasing());
1346         Assertions.assertEquals(t3, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
1347         Assertions.assertEquals(true, events.get(2).isIncreasing());
1348         Assertions.assertEquals(t4, events.get(3).getState().getDate().durationFrom(epoch), tolerance);
1349         Assertions.assertEquals(true, events.get(3).isIncreasing());
1350         Assertions.assertEquals(t5, events.get(4).getState().getDate().durationFrom(epoch), tolerance);
1351         Assertions.assertEquals(false, events.get(4).isIncreasing());
1352     }
1353 
1354     /**
1355      * Test case for two event detectors. DetectorA has event at t2, DetectorB at t3, but
1356      * due to the root finding tolerance DetectorB's event occurs at t1. With t1 < t2 <
1357      * t3.
1358      */
1359     @Test
1360     public void testRootFindingToleranceReverse() {
1361         //setup
1362         double maxCheck = 10;
1363         double t2 = -11, t3 = t2 - 1e-5;
1364         List<Event> events = new ArrayList<>();
1365         TimeDetector detectorA = new TimeDetector(t2)
1366                 .withHandler(new RecordAndContinue(events))
1367                 .withMaxCheck(maxCheck)
1368                 .withThreshold(1e-6);
1369         FlatDetector detectorB = new FlatDetector(t3)
1370                 .withHandler(new RecordAndContinue(events))
1371                 .withMaxCheck(maxCheck)
1372                 .withThreshold(0.5);
1373         Propagator propagator = getPropagator(10);
1374         propagator.addEventDetector(detectorA);
1375         propagator.addEventDetector(detectorB);
1376 
1377         // action
1378         propagator.propagate(epoch.shiftedBy(-30.0));
1379 
1380         // verify
1381         // if these fail the event finding did its job,
1382         // but this test isn't testing what it is supposed to be
1383         Assertions.assertSame(detectorB, events.get(0).getDetector());
1384         Assertions.assertSame(detectorA, events.get(1).getDetector());
1385         Assertions.assertTrue(events.get(0).getState().getDate().compareTo(
1386                 events.get(1).getState().getDate()) > 0);
1387 
1388         // check event detection worked
1389         Assertions.assertEquals(2, events.size());
1390         Assertions.assertEquals(t3, events.get(0).getState().getDate().durationFrom(epoch), 0.5);
1391         Assertions.assertEquals(true, events.get(0).isIncreasing());
1392         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), 1e-6);
1393         Assertions.assertEquals(true, events.get(1).isIncreasing());
1394     }
1395 
1396     /** check when g(t < root) < 0,  g(root + convergence) < 0. */
1397     @Test
1398     public void testRootPlusToleranceHasWrongSignReverse() {
1399         // setup
1400         double maxCheck = 10;
1401         double tolerance = 1e-6;
1402         final double toleranceB = 0.3;
1403         double t1 = -11, t2 = -11.1, t3 = -11.2;
1404         // shared event list so we know the order in which they occurred
1405         List<Event> events = new ArrayList<>();
1406         TimeDetector detectorA = new TimeDetector(t2)
1407                 .withHandler(new RecordAndContinue(events))
1408                 .withMaxCheck(maxCheck)
1409                 .withThreshold(tolerance);
1410         TimeDetector detectorB = new TimeDetector(-50, t1, t3)
1411                 .withHandler(new RecordAndContinue(events))
1412                 .withMaxCheck(maxCheck)
1413                 .withThreshold(toleranceB);
1414         Propagator propagator = getPropagator(10);
1415         propagator.addEventDetector(detectorA);
1416         propagator.addEventDetector(detectorB);
1417 
1418         // action
1419         propagator.propagate(epoch.shiftedBy(-30.0));
1420 
1421         // verify
1422         // we only care that the rules are satisfied. There are multiple solutions.
1423         Assertions.assertEquals(3, events.size());
1424         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), toleranceB);
1425         Assertions.assertEquals(true, events.get(0).isIncreasing());
1426         Assertions.assertSame(detectorB, events.get(0).getDetector());
1427         Assertions.assertEquals(t3, events.get(1).getState().getDate().durationFrom(epoch), toleranceB);
1428         Assertions.assertEquals(false, events.get(1).isIncreasing());
1429         Assertions.assertSame(detectorB, events.get(1).getDetector());
1430         Assertions.assertEquals(t2, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
1431         Assertions.assertEquals(true, events.get(2).isIncreasing());
1432         Assertions.assertSame(detectorA, events.get(2).getDetector());
1433         // ascending order
1434         Assertions.assertTrue(events.get(0).getState().getDate().compareTo(
1435                 events.get(1).getState().getDate()) >= 0);
1436         Assertions.assertTrue(events.get(1).getState().getDate().compareTo(
1437                 events.get(2).getState().getDate()) >= 0);
1438     }
1439 
1440     /** check when g(t < root) < 0,  g(root + convergence) < 0. */
1441     @Test
1442     public void testRootPlusToleranceHasWrongSignAndLessThanTbReverse() {
1443         // setup
1444         // test is fragile w.r.t. implementation and these parameters
1445         double maxCheck = 10;
1446         double tolerance = 0.5;
1447         double t1 = -11, t2 = -11.4, t3 = -12.0;
1448         // shared event list so we know the order in which they occurred
1449         List<Event> events = new ArrayList<>();
1450         FlatDetector detectorB = new FlatDetector(t1, t2, t3)
1451                 .withHandler(new RecordAndContinue(events))
1452                 .withMaxCheck(maxCheck)
1453                 .withThreshold(tolerance);
1454         Propagator propagator = getPropagator(10);
1455         propagator.addEventDetector(detectorB);
1456 
1457         // action
1458         propagator.propagate(epoch.shiftedBy(-30.0));
1459 
1460         // verify
1461         // allowed to report t1 or t3.
1462         Assertions.assertEquals(1, events.size());
1463         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1464         Assertions.assertEquals(true, events.get(0).isIncreasing());
1465         Assertions.assertSame(detectorB, events.get(0).getDetector());
1466     }
1467 
1468     /**
1469      * Check when g(t) has a multiple root. e.g. g(t < root) < 0, g(root) = 0, g(t > root)
1470      * < 0.
1471      */
1472     @Test
1473     public void testDoubleRootReverse() {
1474         // setup
1475         double maxCheck = 10;
1476         double tolerance = 1e-6;
1477         double t1 = -11;
1478         // shared event list so we know the order in which they occurred
1479         List<Event> events = new ArrayList<>();
1480         TimeDetector detectorA = new TimeDetector(t1)
1481                 .withHandler(new RecordAndContinue(events))
1482                 .withMaxCheck(maxCheck)
1483                 .withThreshold(tolerance);
1484         TimeDetector detectorB = new TimeDetector(t1, t1)
1485                 .withHandler(new RecordAndContinue(events))
1486                 .withMaxCheck(maxCheck)
1487                 .withThreshold(tolerance);
1488         Propagator propagator = getPropagator(10);
1489         propagator.addEventDetector(detectorA);
1490         propagator.addEventDetector(detectorB);
1491 
1492         // action
1493         propagator.propagate(epoch.shiftedBy(-30.0));
1494 
1495         // verify
1496         Assertions.assertEquals(1, events.size());
1497         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
1498         Assertions.assertEquals(true, events.get(0).isIncreasing());
1499         Assertions.assertSame(detectorA, events.get(0).getDetector());
1500         // detector worked correctly
1501         Assertions.assertTrue(detectorB.g(state(t1)) == 0.0);
1502         Assertions.assertTrue(detectorB.g(state(t1 + 1e-6)) < 0);
1503         Assertions.assertTrue(detectorB.g(state(t1 - 1e-6)) < 0);
1504     }
1505 
1506     /**
1507      * Check when g(t) has a multiple root. e.g. g(t < root) > 0, g(root) = 0, g(t > root)
1508      * > 0.
1509      */
1510     @Test
1511     public void testDoubleRootOppositeSignReverse() {
1512         // setup
1513         double maxCheck = 10;
1514         double tolerance = 1e-6;
1515         double t1 = -11;
1516         // shared event list so we know the order in which they occurred
1517         List<Event> events = new ArrayList<>();
1518         TimeDetector detectorA = new TimeDetector(t1)
1519                 .withHandler(new RecordAndContinue(events))
1520                 .withMaxCheck(maxCheck)
1521                 .withThreshold(tolerance);
1522         ContinuousDetector detectorB = new ContinuousDetector(-50, t1, t1)
1523                 .withHandler(new RecordAndContinue(events))
1524                 .withMaxCheck(maxCheck)
1525                 .withThreshold(tolerance);
1526         detectorB.g(state(t1));
1527         Propagator propagator = getPropagator(10);
1528         propagator.addEventDetector(detectorA);
1529         propagator.addEventDetector(detectorB);
1530 
1531         // action
1532         propagator.propagate(epoch.shiftedBy(-30.0));
1533 
1534         // verify
1535         Assertions.assertEquals(1, events.size());
1536         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
1537         Assertions.assertEquals(true, events.get(0).isIncreasing());
1538         Assertions.assertSame(detectorA, events.get(0).getDetector());
1539         // detector worked correctly
1540         Assertions.assertEquals(0.0, detectorB.g(state(t1)), 0.0);
1541         Assertions.assertTrue(detectorB.g(state(t1 + 1e-6)) > 0);
1542         Assertions.assertTrue(detectorB.g(state(t1 - 1e-6)) > 0);
1543     }
1544 
1545     /** check root finding when zero at both ends. */
1546     @Test
1547     public void testZeroAtBeginningAndEndOfIntervalReverse() {
1548         // setup
1549         double maxCheck = 10;
1550         double tolerance = 1e-6;
1551         double t1 = -10, t2 = -20;
1552         // shared event list so we know the order in which they occurred
1553         List<Event> events = new ArrayList<>();
1554         ContinuousDetector detectorA = new ContinuousDetector(-50, t1, t2)
1555                 .withHandler(new RecordAndContinue(events))
1556                 .withMaxCheck(maxCheck)
1557                 .withThreshold(tolerance);
1558         Propagator propagator = getPropagator(10);
1559         propagator.addEventDetector(detectorA);
1560 
1561         // action
1562         propagator.propagate(epoch.shiftedBy(-30.0));
1563 
1564         // verify
1565         Assertions.assertEquals(2, events.size());
1566         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1567         Assertions.assertEquals(true, events.get(0).isIncreasing());
1568         Assertions.assertSame(detectorA, events.get(0).getDetector());
1569         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
1570         Assertions.assertEquals(false, events.get(1).isIncreasing());
1571         Assertions.assertSame(detectorA, events.get(1).getDetector());
1572     }
1573 
1574     /** check root finding when zero at both ends. */
1575     @Test
1576     public void testZeroAtBeginningAndEndOfIntervalOppositeSignReverse() {
1577         // setup
1578         double maxCheck = 10;
1579         double tolerance = 1e-6;
1580         double t1 = -10, t2 = -20;
1581         // shared event list so we know the order in which they occurred
1582         List<Event> events = new ArrayList<>();
1583         ContinuousDetector detectorA = new ContinuousDetector(t1, t2)
1584                 .withHandler(new RecordAndContinue(events))
1585                 .withMaxCheck(maxCheck)
1586                 .withThreshold(tolerance);
1587         Propagator propagator = getPropagator(10);
1588         propagator.addEventDetector(detectorA);
1589 
1590         // action
1591         propagator.propagate(epoch.shiftedBy(-30));
1592 
1593         // verify
1594         Assertions.assertEquals(2, events.size());
1595         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
1596         Assertions.assertEquals(false, events.get(0).isIncreasing());
1597         Assertions.assertSame(detectorA, events.get(0).getDetector());
1598         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), 0.0);
1599         Assertions.assertEquals(true, events.get(1).isIncreasing());
1600         Assertions.assertSame(detectorA, events.get(1).getDetector());
1601     }
1602 
1603     /** Test where an event detector has to back up multiple times. */
1604     @Test
1605     public void testMultipleBackupsReverse() {
1606         // setup
1607         double maxCheck = 10;
1608         double tolerance = 1e-6;
1609         double t1 = -1.0, t2 = -2, t3 = -3, t4 = -4, t5 = -5, t6 = -6.5, t7 = -7;
1610         // shared event list so we know the order in which they occurred
1611         List<Event> events = new ArrayList<>();
1612         ContinuousDetector detectorA = new ContinuousDetector(t6)
1613                 .withHandler(new RecordAndContinue(events))
1614                 .withMaxCheck(maxCheck)
1615                 .withThreshold(tolerance);
1616         ContinuousDetector detectorB = new ContinuousDetector(-50, t1, t3, t4, t7)
1617                 .withHandler(new RecordAndContinue(events))
1618                 .withMaxCheck(maxCheck)
1619                 .withThreshold(tolerance);
1620         ContinuousDetector detectorC = new ContinuousDetector(-50, t2, t5)
1621                 .withHandler(new RecordAndContinue(events))
1622                 .withMaxCheck(maxCheck)
1623                 .withThreshold(tolerance);
1624 
1625         Propagator propagator = getPropagator(10);
1626         propagator.addEventDetector(detectorA);
1627         propagator.addEventDetector(detectorB);
1628         propagator.addEventDetector(detectorC);
1629 
1630         // action
1631         propagator.propagate(epoch.shiftedBy(-30.0));
1632 
1633         //verify
1634         // really we only care that the Rules of Event Handling are not violated,
1635         Assertions.assertEquals(5, events.size());
1636         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1637         Assertions.assertEquals(true, events.get(0).isIncreasing());
1638         Assertions.assertEquals(detectorB, events.get(0).getDetector());
1639         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
1640         Assertions.assertEquals(true, events.get(1).isIncreasing());
1641         Assertions.assertEquals(detectorC, events.get(1).getDetector());
1642         // reporting t3 and t4 is optional, seeing them is not.
1643         // we know a root was found at t3 because events are reported at t2 and t5.
1644         /*
1645         Assertions.assertEquals(t3, events.get(2).getT(), tolerance);
1646         Assertions.assertEquals(false, events.get(2).isIncreasing());
1647         Assertions.assertEquals(detectorB, events.get(2).getHandler());
1648         Assertions.assertEquals(t4, events.get(3).getT(), tolerance);
1649         Assertions.assertEquals(true, events.get(3).isIncreasing());
1650         Assertions.assertEquals(detectorB, events.get(3).getHandler());
1651         */
1652         Assertions.assertEquals(t5, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
1653         Assertions.assertEquals(false, events.get(2).isIncreasing());
1654         Assertions.assertEquals(detectorC, events.get(2).getDetector());
1655         Assertions.assertEquals(t6, events.get(3).getState().getDate().durationFrom(epoch), tolerance);
1656         Assertions.assertEquals(true, events.get(3).isIncreasing());
1657         Assertions.assertEquals(detectorA, events.get(3).getDetector());
1658         Assertions.assertEquals(t7, events.get(4).getState().getDate().durationFrom(epoch), tolerance);
1659         Assertions.assertEquals(false, events.get(4).isIncreasing());
1660         Assertions.assertEquals(detectorB, events.get(4).getDetector());
1661     }
1662 
1663     /** Test a reset event triggering another event at the same time. */
1664     @Test
1665     public void testEventCausedByStateResetReverse() {
1666         // setup
1667         double maxCheck = 10;
1668         double tolerance = 1e-6;
1669         double t1 = -15.0;
1670         SpacecraftState newState = new SpacecraftState(new KeplerianOrbit(
1671                 6378137 + 500e3, 0, FastMath.PI / 2, 0, 0,
1672                 FastMath.PI / 2, PositionAngleType.TRUE, eci, epoch.shiftedBy(t1), mu));
1673         // shared event list so we know the order in which they occurred
1674         List<Event> events = new ArrayList<>();
1675         TimeDetector detectorA = new TimeDetector(t1)
1676                 .withHandler(new Handler(events, Action.RESET_STATE) {
1677                     @Override
1678                     public SpacecraftState resetState(EventDetector detector,
1679                                                       SpacecraftState oldState) {
1680                         return newState;
1681                     }
1682                 })
1683                 .withMaxCheck(maxCheck)
1684                 .withThreshold(tolerance);
1685         LatitudeCrossingDetector detectorB =
1686                 new LatitudeCrossingDetector(earth, FastMath.toRadians(80))
1687                         .withHandler(new RecordAndContinue(events))
1688                         .withMaxCheck(maxCheck)
1689                         .withThreshold(tolerance);
1690 
1691         Propagator propagator = getPropagator(10);
1692         propagator.addEventDetector(detectorA);
1693         propagator.addEventDetector(detectorB);
1694 
1695         // action
1696         propagator.propagate(epoch.shiftedBy(-40.0));
1697 
1698         //verify
1699         // really we only care that the Rules of Event Handling are not violated,
1700         Assertions.assertEquals(2, events.size());
1701         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1702         Assertions.assertEquals(true, events.get(0).isIncreasing());
1703         Assertions.assertEquals(detectorA, events.get(0).getDetector());
1704         Assertions.assertEquals(t1, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
1705         Assertions.assertEquals(false, events.get(1).isIncreasing());
1706         Assertions.assertEquals(detectorB, events.get(1).getDetector());
1707     }
1708 
1709     /** check when t + tolerance == t. */
1710     @Test
1711     public void testConvergenceTooTightReverse() {
1712         // setup
1713         double maxCheck = 10;
1714         double tolerance = 1e-18;
1715         double t1 = -15;
1716         // shared event list so we know the order in which they occurred
1717         List<Event> events = new ArrayList<>();
1718         ContinuousDetector detectorA = new ContinuousDetector(t1)
1719                 .withHandler(new RecordAndContinue(events))
1720                 .withMaxCheck(maxCheck)
1721                 .withThreshold(tolerance);
1722         Propagator propagator = getPropagator(10);
1723         propagator.addEventDetector(detectorA);
1724 
1725         // action
1726         propagator.propagate(epoch.shiftedBy(-30.0));
1727 
1728         // verify
1729         Assertions.assertEquals(1, events.size());
1730         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), 0.0);
1731         Assertions.assertEquals(true, events.get(0).isIncreasing());
1732         Assertions.assertSame(detectorA, events.get(0).getDetector());
1733     }
1734 
1735     /**
1736      * test when one event detector changes the definition of another's g function before
1737      * the end of the step as a result of a continue action. Not sure if this should be
1738      * officially supported, but it is used in Orekit's DateDetector, it's useful, and not
1739      * too hard to implement.
1740      */
1741     @Test
1742     public void testEventChangesGFunctionDefinitionReverse() {
1743         // setup
1744         double maxCheck = 5;
1745         double tolerance = 1e-6;
1746         double t1 = -11, t2 = -19;
1747         // shared event list so we know the order in which they occurred
1748         List<Event> events = new ArrayList<>();
1749         // mutable boolean
1750         boolean[] swap = new boolean[1];
1751         ContinuousDetector detectorA = new ContinuousDetector(t1)
1752                 .withMaxCheck(maxCheck)
1753                 .withThreshold(tolerance)
1754                 .withHandler(new RecordAndContinue(events) {
1755                     @Override
1756                     public Action eventOccurred(SpacecraftState s,
1757                                                 EventDetector detector,
1758                                                 boolean increasing) {
1759                         swap[0] = true;
1760                         return super.eventOccurred(s, detector, increasing);
1761                     }
1762                 });
1763         ContinuousDetector detectorB = new ContinuousDetector(t2);
1764         EventDetector detectorC = new FunctionalDetector()
1765                 .withMaxCheck(maxCheck)
1766                 .withThreshold(tolerance)
1767                 .withMaxIter(100)
1768                 .withHandler(new RecordAndContinue(events))
1769                 .withFunction(state -> swap[0] ? detectorB.g(state) : 1);
1770         Propagator propagator = getPropagator(10);
1771         propagator.addEventDetector(detectorA);
1772         propagator.addEventDetector(detectorC);
1773 
1774         // action
1775         propagator.propagate(epoch.shiftedBy(-30.0));
1776 
1777         // verify
1778         Assertions.assertEquals(2, events.size());
1779         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1780         Assertions.assertEquals(true, events.get(0).isIncreasing());
1781         Assertions.assertSame(detectorA, events.get(0).getDetector());
1782         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
1783         Assertions.assertEquals(true, events.get(1).isIncreasing());
1784         Assertions.assertSame(detectorC, events.get(1).getDetector());
1785     }
1786 
1787     /**
1788      * test when one event detector changes the definition of another's g function before
1789      * the end of the step as a result of an event occurring. In this case the change
1790      * cancels the occurrence of the event.
1791      */
1792     @Test
1793     public void testEventChangesGFunctionDefinitionCancelReverse() {
1794         // setup
1795         double maxCheck = 5;
1796         double tolerance = 1e-6;
1797         double t1 = -11, t2 = -11.1;
1798         // shared event list so we know the order in which they occurred
1799         List<Event> events = new ArrayList<>();
1800         // mutable boolean
1801         boolean[] swap = new boolean[1];
1802         final ContinuousDetector detectorA = new ContinuousDetector(t1)
1803                 .withMaxCheck(maxCheck)
1804                 .withThreshold(tolerance)
1805                 .withHandler(new RecordAndContinue(events) {
1806                     @Override
1807                     public Action eventOccurred(SpacecraftState state,
1808                                                 EventDetector detector,
1809                                                 boolean increasing) {
1810                         swap[0] = true;
1811                         super.eventOccurred(state, detector, increasing);
1812                         return Action.RESET_EVENTS;
1813                     }
1814                 });
1815         final ContinuousDetector detectorB = new ContinuousDetector(t2);
1816         EventDetector detectorC = new FunctionalDetector()
1817         .withMaxCheck(maxCheck)
1818                 .withThreshold(tolerance)
1819                 .withMaxIter(100)
1820                 .withHandler(new RecordAndContinue(events))
1821                 .withFunction(state -> swap[0] ? 1 : detectorB.g(state));
1822         Propagator propagator = getPropagator(10);
1823         propagator.addEventDetector(detectorA);
1824         propagator.addEventDetector(detectorC);
1825 
1826         // action
1827         propagator.propagate(epoch.shiftedBy(-30));
1828 
1829         // verify
1830         Assertions.assertEquals(1, events.size());
1831         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1832         Assertions.assertEquals(true, events.get(0).isIncreasing());
1833         Assertions.assertSame(detectorA, events.get(0).getDetector());
1834     }
1835 
1836     /**
1837      * test when one event detector changes the definition of another's g function before
1838      * the end of the step as a result of an event occurring. In this case the change
1839      * delays the occurrence of the event.
1840      */
1841     @Test
1842     public void testEventChangesGFunctionDefinitionDelayReverse() {
1843         // setup
1844         double maxCheck = 5;
1845         double tolerance = 1e-6;
1846         double t1 = -11, t2 = -11.1, t3 = -11.2;
1847         // shared event list so we know the order in which they occurred
1848         List<Event> events = new ArrayList<>();
1849         // mutable boolean
1850         boolean[] swap = new boolean[1];
1851         final ContinuousDetector detectorA = new ContinuousDetector(t1)
1852                 .withMaxCheck(maxCheck)
1853                 .withThreshold(tolerance)
1854                 .withHandler(new RecordAndContinue(events) {
1855                     @Override
1856                     public Action eventOccurred(SpacecraftState state,
1857                                                 EventDetector detector,
1858                                                 boolean increasing) {
1859                         swap[0] = true;
1860                         super.eventOccurred(state, detector, increasing);
1861                         return Action.RESET_EVENTS;
1862                     }
1863                 });
1864         final ContinuousDetector detectorB = new ContinuousDetector(t2);
1865         final ContinuousDetector detectorD = new ContinuousDetector(t3);
1866         EventDetector detectorC = new FunctionalDetector()
1867                 .withMaxCheck(maxCheck)
1868                 .withThreshold(tolerance)
1869                 .withMaxIter(100)
1870                 .withHandler(new RecordAndContinue(events))
1871                 .withFunction(state -> swap[0] ? detectorD.g(state) : detectorB.g(state));
1872         Propagator propagator = getPropagator(10);
1873         propagator.addEventDetector(detectorA);
1874         propagator.addEventDetector(detectorC);
1875 
1876         // action
1877         propagator.propagate(epoch.shiftedBy(-30));
1878 
1879         // verify
1880         Assertions.assertEquals(2, events.size());
1881         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1882         Assertions.assertEquals(true, events.get(0).isIncreasing());
1883         Assertions.assertSame(detectorA, events.get(0).getDetector());
1884         Assertions.assertEquals(t3, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
1885         Assertions.assertEquals(true, events.get(1).isIncreasing());
1886         Assertions.assertSame(detectorC, events.get(1).getDetector());
1887     }
1888 
1889     /**
1890      * test when one event detector changes the definition of another's g function before
1891      * the end of the step as a result of an event occurring. In this case the change
1892      * causes the event to happen sooner than originally expected.
1893      */
1894     @Test
1895     public void testEventChangesGFunctionDefinitionAccelerateReverse() {
1896         // setup
1897         double maxCheck = 5;
1898         double tolerance = 1e-6;
1899         double t1 = -11, t2 = -11.1, t3 = -11.2;
1900         // shared event list so we know the order in which they occurred
1901         List<Event> events = new ArrayList<>();
1902         // mutable boolean
1903         boolean[] swap = new boolean[1];
1904         final ContinuousDetector detectorA = new ContinuousDetector(t1)
1905                 .withMaxCheck(maxCheck)
1906                 .withThreshold(tolerance)
1907                 .withHandler(new RecordAndContinue(events) {
1908                     @Override
1909                     public Action eventOccurred(SpacecraftState state,
1910                                                 EventDetector detector,
1911                                                 boolean increasing) {
1912                         swap[0] = true;
1913                         super.eventOccurred(state, detector, increasing);
1914                         return Action.RESET_EVENTS;
1915                     }
1916                 });
1917         final ContinuousDetector detectorB = new ContinuousDetector(t2);
1918         final ContinuousDetector detectorD = new ContinuousDetector(t3);
1919         EventDetector detectorC = new FunctionalDetector()
1920                 .withMaxCheck(maxCheck)
1921                 .withThreshold(tolerance)
1922                 .withMaxIter(100)
1923                 .withHandler(new RecordAndContinue(events))
1924                 .withFunction(state -> swap[0] ? detectorB.g(state) : detectorD.g(state));
1925         Propagator propagator = getPropagator(10);
1926         propagator.addEventDetector(detectorA);
1927         propagator.addEventDetector(detectorC);
1928 
1929         // action
1930         propagator.propagate(epoch.shiftedBy(-30));
1931 
1932         // verify
1933         Assertions.assertEquals(2, events.size());
1934         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1935         Assertions.assertEquals(true, events.get(0).isIncreasing());
1936         Assertions.assertSame(detectorA, events.get(0).getDetector());
1937         Assertions.assertEquals(t2, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
1938         Assertions.assertEquals(true, events.get(1).isIncreasing());
1939         Assertions.assertSame(detectorC, events.get(1).getDetector());
1940     }
1941 
1942     /** check when root finding tolerance > event finding tolerance. */
1943     @Test
1944     public void testToleranceStopReverse() {
1945         // setup
1946         double maxCheck = 10;
1947         double tolerance = 1e-18; // less than 1 ulp
1948         double t1 = -15.1;
1949         // shared event list so we know the order in which they occurred
1950         List<Event> events = new ArrayList<>();
1951         FlatDetector detectorA = new FlatDetector(t1)
1952                 .withHandler(new Handler(events, Action.STOP))
1953                 .withMaxCheck(maxCheck)
1954                 .withThreshold(tolerance);
1955         Propagator propagator = getPropagator(10);
1956         propagator.addEventDetector(detectorA);
1957 
1958         // action
1959         SpacecraftState finalState = propagator.propagate(epoch.shiftedBy(-30.0));
1960 
1961         // verify
1962         Assertions.assertEquals(1, events.size());
1963         // use root finder tolerance instead of event finder tolerance.
1964         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
1965         Assertions.assertEquals(true, events.get(0).isIncreasing());
1966         Assertions.assertSame(detectorA, events.get(0).getDetector());
1967         Assertions.assertEquals(t1, finalState.getDate().durationFrom(epoch), tolerance);
1968 
1969         // try to resume propagation
1970         finalState = propagator.propagate(epoch.shiftedBy(-30.0));
1971 
1972         // verify it got to the end
1973         Assertions.assertEquals(-30.0, finalState.getDate().durationFrom(epoch), 0.0);
1974     }
1975 
1976     /**
1977      * The root finder requires the start point to be in the interval (a, b) which is hard
1978      * when there aren't many numbers between a and b. This test uses a second event
1979      * detector to force a very small window for the first event detector.
1980      */
1981     @Test
1982     public void testShortBracketingIntervalReverse() {
1983         // setup
1984         double maxCheck = 10;
1985         double tolerance = 1e-6;
1986         final double t1 = FastMath.nextDown(-10.0), t2 = -10.5;
1987         // shared event list so we know the order in which they occurred
1988         List<Event> events = new ArrayList<>();
1989         // never zero so there is no easy way out
1990         EventDetector detectorA = new FunctionalDetector()
1991                 .withMaxCheck(maxCheck)
1992                 .withThreshold(tolerance)
1993                 .withMaxIter(100)
1994                 .withHandler(new RecordAndContinue(events))
1995                 .withFunction(state -> {
1996                     final AbsoluteDate t = state.getDate();
1997                     if (t.compareTo(epoch.shiftedBy(t1)) > 0) {
1998                         return -1;
1999                     } else if (t.compareTo(epoch.shiftedBy(t2)) > 0) {
2000                         return 1;
2001                     } else {
2002                         return -1;
2003                     }
2004                 });
2005         TimeDetector detectorB = new TimeDetector(t1)
2006                 .withHandler(new RecordAndContinue(events))
2007                 .withMaxCheck(maxCheck)
2008                 .withThreshold(tolerance);
2009         Propagator propagator = getPropagator(10);
2010         propagator.addEventDetector(detectorA);
2011         propagator.addEventDetector(detectorB);
2012 
2013         // action
2014         propagator.propagate(epoch.shiftedBy(-30.0));
2015 
2016         // verify
2017         Assertions.assertEquals(3, events.size());
2018         Assertions.assertEquals(t1, events.get(0).getState().getDate().durationFrom(epoch), tolerance);
2019         Assertions.assertEquals(false, events.get(0).isIncreasing());
2020         Assertions.assertSame(detectorA, events.get(0).getDetector());
2021         Assertions.assertEquals(t1, events.get(1).getState().getDate().durationFrom(epoch), tolerance);
2022         Assertions.assertEquals(true, events.get(1).isIncreasing());
2023         Assertions.assertSame(detectorB, events.get(1).getDetector());
2024         Assertions.assertEquals(t2, events.get(2).getState().getDate().durationFrom(epoch), tolerance);
2025         Assertions.assertEquals(true, events.get(2).isIncreasing());
2026         Assertions.assertSame(detectorA, events.get(2).getDetector());
2027     }
2028 
2029     /** check when root finding tolerance > event finding tolerance. */
2030     @Test
2031     public void testToleranceMaxIterationsReverse() {
2032         // setup
2033         double maxCheck = 10;
2034         double tolerance = 1e-18; // less than 1 ulp
2035         AbsoluteDate t1 = epoch.shiftedBy(-15).shiftedBy(FastMath.ulp(-15.0) / 8);
2036         // shared event list so we know the order in which they occurred
2037         List<Event> events = new ArrayList<>();
2038         FlatDetector detectorA = new FlatDetector(t1)
2039                 .withHandler(new RecordAndContinue(events))
2040                 .withMaxCheck(maxCheck)
2041                 .withThreshold(tolerance);
2042         Propagator propagator = getPropagator(10);
2043         propagator.addEventDetector(detectorA);
2044 
2045         // action
2046         propagator.propagate(epoch.shiftedBy(-30));
2047 
2048         // verify
2049         Assertions.assertEquals(1, events.size());
2050         // use root finder tolerance instead of event finder tolerance.
2051         Assertions.assertEquals(t1.durationFrom(epoch),
2052                 events.get(0).getState().getDate().durationFrom(epoch),
2053                 FastMath.ulp(-15.0));
2054         Assertions.assertEquals(true, events.get(0).isIncreasing());
2055         Assertions.assertSame(detectorA, events.get(0).getDetector());
2056     }
2057 
2058     /** Check that steps are restricted correctly with a continue event. */
2059     @Test
2060     public void testEventStepHandlerReverse() {
2061         // setup
2062         double tolerance = 1e-18;
2063         Propagator propagator = getPropagator(10);
2064         propagator.addEventDetector(new TimeDetector(-5)
2065                 .withHandler(new Handler(Action.CONTINUE))
2066                 .withThreshold(tolerance));
2067         StepHandler stepHandler = new StepHandler();
2068         propagator.setStepHandler(stepHandler);
2069 
2070         // action
2071         SpacecraftState finalState = propagator.propagate(epoch.shiftedBy(-10));
2072 
2073         // verify
2074         Assertions.assertEquals(-10.0, finalState.getDate().durationFrom(epoch), tolerance);
2075         Assertions.assertEquals(0.0,
2076                 stepHandler.initialState.getDate().durationFrom(epoch), tolerance);
2077         Assertions.assertEquals(-10.0, stepHandler.targetDate.durationFrom(epoch), tolerance);
2078         Assertions.assertEquals(-10.0,
2079                 stepHandler.finalState.getDate().durationFrom(epoch), tolerance);
2080         OrekitStepInterpolator interpolator = stepHandler.interpolators.get(0);
2081         Assertions.assertEquals(0.0,
2082                 interpolator.getPreviousState().getDate().durationFrom(epoch), tolerance);
2083         Assertions.assertEquals(-5.0,
2084                 interpolator.getCurrentState().getDate().durationFrom(epoch), tolerance);
2085         interpolator = stepHandler.interpolators.get(1);
2086         Assertions.assertEquals(-5.0,
2087                 interpolator.getPreviousState().getDate().durationFrom(epoch), tolerance);
2088         Assertions.assertEquals(-10.0,
2089                 interpolator.getCurrentState().getDate().durationFrom(epoch), tolerance);
2090         Assertions.assertEquals(2, stepHandler.interpolators.size());
2091     }
2092 
2093     /**
2094      * Test {@link EventHandler#resetState(EventDetector, SpacecraftState)} returns {@code
2095      * null}.
2096      */
2097     @Test
2098     public void testEventCausedByDerivativesResetReverse() {
2099         // setup
2100         TimeDetector detectorA = new TimeDetector(-15.0)
2101                 .withHandler(new Handler(Action.RESET_STATE){
2102                     @Override
2103                     public SpacecraftState resetState(EventDetector d, SpacecraftState s) {
2104                         return null;
2105                     }
2106                 })
2107                 .withMaxCheck(10)
2108                 .withThreshold(1e-6);
2109         Propagator propagator = getPropagator(10);
2110         propagator.addEventDetector(detectorA);
2111 
2112         try {
2113             // action
2114             propagator.propagate(epoch.shiftedBy(-20.0));
2115             Assertions.fail("Expected Exception");
2116         } catch (NullPointerException e) {
2117             // expected
2118         }
2119     }
2120 
2121     @Test
2122     public void testResetChangesSign() {
2123         Propagator propagator = getPropagator(2.5);
2124         AbsoluteDate t0 = propagator.getInitialState().getDate();
2125         final double small = 1.25e-11;
2126         ResetChangesSignGenerator eventsGenerator = new ResetChangesSignGenerator(t0, 0.75, 1.125, -0.5 * small).
2127                                                     withMaxCheck(1).
2128                                                     withThreshold(small).
2129                                                     withMaxIter(1000);
2130         propagator.addEventDetector(eventsGenerator);
2131         final SpacecraftState end = propagator.propagate(propagator.getInitialState().getDate().shiftedBy(12.5));
2132         Assertions.assertEquals(2,                 eventsGenerator.getCount());
2133         Assertions.assertEquals(1.125 + 0.5 * small, end.getDate().durationFrom(t0), 1.0e-12);
2134     }
2135 
2136     /* utility classes and methods */
2137 
2138     /**
2139      * Create a state at a time.
2140      *
2141      * @param t time of state.
2142      * @return new state.
2143      */
2144     private SpacecraftState state(double t) {
2145         return new SpacecraftState(
2146                 new KeplerianOrbit(6378137 + 500e3, 0, 0, 0, 0, 0,
2147                         PositionAngleType.TRUE, eci, epoch.shiftedBy(t),
2148                         mu));
2149     }
2150 
2151     private static List<AbsoluteDate> toDates(double[] eventTs) {
2152         Arrays.sort(eventTs);
2153         final List<AbsoluteDate> ret = new ArrayList<>();
2154         for (double eventT : eventTs) {
2155             ret.add(epoch.shiftedBy(eventT));
2156         }
2157         return ret;
2158     }
2159 
2160     /** Trigger an event at a particular time. */
2161     protected static class TimeDetector extends AbstractDetector<TimeDetector> {
2162 
2163         /** time of the event to trigger. */
2164         private final List<AbsoluteDate> eventTs;
2165 
2166         /**
2167          * Create a detector that finds events at specific times.
2168          *
2169          * @param eventTs event times past epoch.
2170          */
2171         public TimeDetector(double... eventTs) {
2172             this(EventDetectionSettings.getDefaultEventDetectionSettings(), new StopOnEvent(), toDates(eventTs));
2173         }
2174 
2175         /**
2176          * Create a detector that finds events at specific times.
2177          *
2178          * @param eventTs event times past epoch.
2179          */
2180         public TimeDetector(AbsoluteDate... eventTs) {
2181             this(EventDetectionSettings.getDefaultEventDetectionSettings(), new StopOnEvent(), Arrays.asList(eventTs));
2182         }
2183 
2184         private TimeDetector(EventDetectionSettings detectionSettings, EventHandler newHandler,
2185                              List<AbsoluteDate> dates) {
2186             super(detectionSettings, newHandler);
2187             this.eventTs = dates;
2188         }
2189 
2190         @Override
2191         public double g(SpacecraftState s) {
2192             final AbsoluteDate t = s.getDate();
2193             int i = 0;
2194             while (i < eventTs.size() && t.compareTo(eventTs.get(i)) > 0) {
2195                 i++;
2196             }
2197             i--;
2198             if (i < 0) {
2199                 return t.durationFrom(eventTs.get(0));
2200             } else {
2201                 int sign = (i % 2) * 2 - 1;
2202                 return -sign * (t.durationFrom(eventTs.get(i)));
2203             }
2204         }
2205 
2206         @Override
2207         protected TimeDetector create(EventDetectionSettings detectionSettings,
2208                                       EventHandler newHandler) {
2209             return new TimeDetector(detectionSettings, newHandler, eventTs);
2210         }
2211 
2212     }
2213 
2214     /**
2215      * Same as {@link TimeDetector} except that it has a very flat g function which makes
2216      * root finding hard.
2217      */
2218     private static class FlatDetector extends AbstractDetector<FlatDetector> {
2219 
2220         private final EventDetector g;
2221 
2222         public FlatDetector(double... eventTs) {
2223             this(EventDetectionSettings.getDefaultEventDetectionSettings(),
2224                  new StopOnEvent(), new TimeDetector(eventTs));
2225         }
2226 
2227         public FlatDetector(AbsoluteDate... eventTs) {
2228             this(EventDetectionSettings.getDefaultEventDetectionSettings(),
2229                  new StopOnEvent(), new TimeDetector(eventTs));
2230         }
2231 
2232         private FlatDetector(EventDetectionSettings detectionSettings,
2233                              EventHandler newHandler,
2234                              EventDetector g) {
2235             super(detectionSettings, newHandler);
2236             this.g = g;
2237         }
2238 
2239         @Override
2240         public double g(SpacecraftState s) {
2241             return FastMath.signum(g.g(s));
2242         }
2243 
2244         @Override
2245         protected FlatDetector create(EventDetectionSettings detectionSettings,
2246                                       EventHandler newHandler) {
2247             return new FlatDetector(detectionSettings, newHandler, g);
2248         }
2249 
2250     }
2251 
2252     /** quadratic. */
2253     private static class ContinuousDetector extends AbstractDetector<ContinuousDetector> {
2254 
2255         /** time of the event to trigger. */
2256         private final List<AbsoluteDate> eventTs;
2257 
2258         public ContinuousDetector(double... eventTs) {
2259             this(EventDetectionSettings.getDefaultEventDetectionSettings(),
2260                  new StopOnEvent(), toDates(eventTs));
2261         }
2262 
2263         private ContinuousDetector(EventDetectionSettings detectionSettings,
2264                                    EventHandler newHandler,
2265                                    List<AbsoluteDate> eventDates) {
2266             super(detectionSettings, newHandler);
2267             this.eventTs = eventDates;
2268         }
2269 
2270         @Override
2271         public double g(SpacecraftState s) {
2272             final AbsoluteDate t = s.getDate();
2273             int i = 0;
2274             while (i < eventTs.size() && t.compareTo(eventTs.get(i)) > 0) {
2275                 i++;
2276             }
2277             i--;
2278             if (i < 0) {
2279                 return t.durationFrom(eventTs.get(0));
2280             } else if (i < eventTs.size() - 1) {
2281                 int sign = (i % 2) * 2 - 1;
2282                 return -sign * (t.durationFrom(eventTs.get(i)))
2283                         * (eventTs.get(i + 1).durationFrom(t));
2284             } else {
2285                 int sign = (i % 2) * 2 - 1;
2286                 return -sign * (t.durationFrom(eventTs.get(i)));
2287             }
2288         }
2289 
2290         @Override
2291         protected ContinuousDetector create(EventDetectionSettings detectionSettings,
2292                 EventHandler newHandler) {
2293             return new ContinuousDetector(detectionSettings, newHandler, eventTs);
2294         }
2295 
2296     }
2297 
2298     private static class Handler extends RecordAndContinue {
2299 
2300         private final Action action;
2301 
2302         public Handler(Action action) {
2303             this.action = action;
2304         }
2305 
2306         public Handler(List<Event> events, Action action) {
2307             super(events);
2308             this.action = action;
2309         }
2310 
2311         @Override
2312         public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing) {
2313             super.eventOccurred(s, detector, increasing);
2314             return this.action;
2315         }
2316 
2317     }
2318 
2319     private static class ResetHandler extends Handler {
2320 
2321         private final SpacecraftState newState;
2322         private final int times;
2323         private long i = 0;
2324 
2325         public ResetHandler(List<Event> events, SpacecraftState newState) {
2326             this(events, newState, Integer.MAX_VALUE);
2327         }
2328 
2329         public ResetHandler(List<Event> events, SpacecraftState newState, int times) {
2330             super(events, Action.RESET_STATE);
2331             this.newState = newState;
2332             this.times = times;
2333         }
2334 
2335         @Override
2336         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
2337             super.eventOccurred(s, detector, increasing);
2338             if (i++ < times) {
2339                 return Action.RESET_STATE;
2340             }
2341             return Action.CONTINUE;
2342         }
2343 
2344         @Override
2345         public SpacecraftState resetState(EventDetector detector, SpacecraftState oldState) {
2346             Assertions.assertEquals(0, newState.getDate().durationFrom(oldState.getDate()), 0);
2347             return newState;
2348         }
2349     }
2350 
2351     private static class StepHandler implements OrekitStepHandler {
2352 
2353         private SpacecraftState initialState;
2354         private AbsoluteDate targetDate;
2355         private List<OrekitStepInterpolator> interpolators = new ArrayList<>();
2356         private SpacecraftState finalState;
2357 
2358         @Override
2359         public void init(SpacecraftState s0, AbsoluteDate t) {
2360             initialState = s0;
2361             targetDate = t;
2362         }
2363 
2364         @Override
2365         public void handleStep(OrekitStepInterpolator interpolator) {
2366             interpolators.add(interpolator);
2367         }
2368 
2369         @Override
2370         public void finish(SpacecraftState finalState) {
2371             this.finalState = finalState;
2372         }
2373     }
2374 
2375     private class ResetChangesSignGenerator extends AbstractDetector<ResetChangesSignGenerator> {
2376 
2377         final AbsoluteDate t0;
2378         final double y1;
2379         final double y2;
2380         final double change;
2381         double delta;
2382         int count;
2383 
2384         public ResetChangesSignGenerator(final AbsoluteDate t0, final double y1, final double y2, final double change) {
2385             this(EventDetectionSettings.getDefaultEventDetectionSettings(),
2386                  new ContinueOnEvent(), t0, y1, y2, change);
2387         }
2388 
2389         private ResetChangesSignGenerator(final EventDetectionSettings eventDetectionSettings,
2390                                           final EventHandler newHandler,
2391                                           final AbsoluteDate t0, final double y1, final double y2, final double change ) {
2392             super(eventDetectionSettings, newHandler);
2393             this.t0     = t0;
2394             this.y1     = y1;
2395             this.y2     = y2;
2396             this.change = change;
2397             this.delta  = 0;
2398             this.count  = 0;
2399         }
2400 
2401         protected ResetChangesSignGenerator create(final EventDetectionSettings detectionSettings,
2402                                                    final EventHandler newHandler) {
2403             return new ResetChangesSignGenerator(detectionSettings, newHandler,
2404                                                  t0, y1, y2, change);
2405         }
2406 
2407         public double g(SpacecraftState s) {
2408             double dt = s.getDate().durationFrom(t0) + delta;
2409             return (dt - y1) * (dt - y2);
2410         }
2411 
2412         public EventHandler getHandler() {
2413             return new EventHandler() {
2414                 public Action eventOccurred(SpacecraftState s, EventDetector detector, boolean increasing) {
2415                     return ++count < 2 ? Action.RESET_STATE : Action.STOP;
2416                 }
2417 
2418                 public SpacecraftState resetState(EventDetector detector, SpacecraftState s) {
2419                     delta = change;
2420                     return s;
2421                 }
2422             };
2423         }
2424 
2425         public int getCount() {
2426             return count;
2427         }
2428 
2429     }
2430 
2431 }