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