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