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