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