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