1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.propagation.events;
18
19 import java.util.function.DoubleFunction;
20
21 import org.hipparchus.CalculusFieldElement;
22 import org.hipparchus.Field;
23 import org.hipparchus.analysis.UnivariateFunction;
24 import org.hipparchus.analysis.solvers.BracketedUnivariateSolver;
25 import org.hipparchus.analysis.solvers.BracketedUnivariateSolver.Interval;
26 import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
27 import org.hipparchus.exception.MathRuntimeException;
28 import org.hipparchus.ode.events.Action;
29 import org.hipparchus.util.FastMath;
30 import org.hipparchus.util.Precision;
31 import org.orekit.errors.OrekitException;
32 import org.orekit.errors.OrekitInternalError;
33 import org.orekit.errors.OrekitMessages;
34 import org.orekit.propagation.FieldSpacecraftState;
35 import org.orekit.propagation.events.handlers.FieldEventHandler;
36 import org.orekit.propagation.sampling.FieldOrekitStepInterpolator;
37 import org.orekit.time.FieldAbsoluteDate;
38
39 /** This class handles the state for one {@link FieldEventDetector
40 * event detector} during integration steps.
41 *
42 * <p>This class is heavily based on the class with the same name from the
43 * Hipparchus library. The changes performed consist in replacing
44 * raw types (double and double arrays) with space dynamics types
45 * ({@link FieldAbsoluteDate}, {@link FieldSpacecraftState}).</p>
46 * <p>Each time the propagator proposes a step, the event detector
47 * should be checked. This class handles the state of one detector
48 * during one propagation step, with references to the state at the
49 * end of the preceding step. This information is used to determine if
50 * the detector should trigger an event or not during the proposed
51 * step (and hence the step should be reduced to ensure the event
52 * occurs at a bound rather than inside the step).</p>
53 * @author Luc Maisonobe
54 * @param <D> class type for the generic version
55 * @param <T> type of the field elements
56 */
57 public class FieldEventState<D extends FieldEventDetector<T>, T extends CalculusFieldElement<T>> {
58
59 /** Event detector. */
60 private D detector;
61
62 /** Event handler. */
63 private FieldEventHandler<T> handler;
64
65 /** Time of the previous call to g. */
66 private FieldAbsoluteDate<T> lastT;
67
68 /** Value from the previous call to g. */
69 private T lastG;
70
71 /** Time at the beginning of the step. */
72 private FieldAbsoluteDate<T> t0;
73
74 /** Value of the event detector at the beginning of the step. */
75 private T g0;
76
77 /** Simulated sign of g0 (we cheat when crossing events). */
78 private boolean g0Positive;
79
80 /** Indicator of event expected during the step. */
81 private boolean pendingEvent;
82
83 /** Occurrence time of the pending event. */
84 private FieldAbsoluteDate<T> pendingEventTime;
85
86 /**
87 * Time to stop propagation if the event is a stop event. Used to enable stopping at
88 * an event and then restarting after that event.
89 */
90 private FieldAbsoluteDate<T> stopTime;
91
92 /** Time after the current event. */
93 private FieldAbsoluteDate<T> afterEvent;
94
95 /** Value of the g function after the current event. */
96 private T afterG;
97
98 /** The earliest time considered for events. */
99 private FieldAbsoluteDate<T> earliestTimeConsidered;
100
101 /** Integration direction. */
102 private boolean forward;
103
104 /** Variation direction around pending event.
105 * (this is considered with respect to the integration direction)
106 */
107 private boolean increasing;
108
109 /** Simple constructor.
110 * @param detector monitored event detector
111 */
112 public FieldEventState(final D detector) {
113
114 this.detector = detector;
115 this.handler = detector.getHandler();
116
117 // some dummy values ...
118 final Field<T> field = detector.getThreshold().getField();
119 final T nan = field.getZero().add(Double.NaN);
120 lastT = FieldAbsoluteDate.getPastInfinity(field);
121 lastG = nan;
122 t0 = null;
123 g0 = nan;
124 g0Positive = true;
125 pendingEvent = false;
126 pendingEventTime = null;
127 stopTime = null;
128 increasing = true;
129 earliestTimeConsidered = null;
130 afterEvent = null;
131 afterG = nan;
132
133 }
134
135 /** Get the underlying event detector.
136 * @return underlying event detector
137 */
138 public D getEventDetector() {
139 return detector;
140 }
141
142 /** Initialize event handler at the start of a propagation.
143 * <p>
144 * This method is called once at the start of the propagation. It
145 * may be used by the event handler to initialize some internal data
146 * if needed.
147 * </p>
148 * @param s0 initial state
149 * @param t target time for the integration
150 *
151 */
152 public void init(final FieldSpacecraftState<T> s0,
153 final FieldAbsoluteDate<T> t) {
154 detector.init(s0, t);
155 final Field<T> field = detector.getThreshold().getField();
156 lastT = FieldAbsoluteDate.getPastInfinity(field);
157 lastG = field.getZero().add(Double.NaN);
158 }
159
160 /** Compute the value of the switching function.
161 * This function must be continuous (at least in its roots neighborhood),
162 * as the integrator will need to find its roots to locate the events.
163 * @param s the current state information: date, kinematics, attitude
164 * @return value of the switching function
165 */
166 private T g(final FieldSpacecraftState<T> s) {
167 if (!s.getDate().equals(lastT)) {
168 lastT = s.getDate();
169 lastG = detector.g(s);
170 }
171 return lastG;
172 }
173
174 /** Reinitialize the beginning of the step.
175 * @param interpolator interpolator valid for the current step
176 */
177 public void reinitializeBegin(final FieldOrekitStepInterpolator<T> interpolator) {
178 forward = interpolator.isForward();
179 final FieldSpacecraftState<T> s0 = interpolator.getPreviousState();
180 this.t0 = s0.getDate();
181 g0 = g(s0);
182 while (g0.getReal() == 0) {
183 // extremely rare case: there is a zero EXACTLY at interval start
184 // we will use the sign slightly after step beginning to force ignoring this zero
185 // try moving forward by half a convergence interval
186 final T dt = detector.getThreshold().multiply(forward ? 0.5 : -0.5);
187 FieldAbsoluteDate<T> startDate = t0.shiftedBy(dt);
188 // if convergence is too small move an ulp
189 if (t0.equals(startDate)) {
190 startDate = nextAfter(startDate);
191 }
192 t0 = startDate;
193 g0 = g(interpolator.getInterpolatedState(t0));
194 }
195 g0Positive = g0.getReal() > 0;
196 // "last" event was increasing
197 increasing = g0Positive;
198 }
199
200 /** Evaluate the impact of the proposed step on the event detector.
201 * @param interpolator step interpolator for the proposed step
202 * @return true if the event detector triggers an event before
203 * the end of the proposed step (this implies the step should be
204 * rejected)
205 * @exception MathRuntimeException if an event cannot be located
206 */
207 public boolean evaluateStep(final FieldOrekitStepInterpolator<T> interpolator)
208 throws MathRuntimeException {
209 forward = interpolator.isForward();
210 final FieldSpacecraftState<T> s0 = interpolator.getPreviousState();
211 final FieldSpacecraftState<T> s1 = interpolator.getCurrentState();
212 final FieldAbsoluteDate<T> t1 = s1.getDate();
213 final T dt = t1.durationFrom(t0);
214 if (FastMath.abs(dt.getReal()) < detector.getThreshold().getReal()) {
215 // we cannot do anything on such a small step, don't trigger any events
216 pendingEvent = false;
217 pendingEventTime = null;
218 return false;
219 }
220
221 FieldAbsoluteDate<T> ta = t0;
222 T ga = g0;
223 for (FieldSpacecraftState<T> sb = nextCheck(s0, s1, interpolator);
224 sb != null;
225 sb = nextCheck(sb, s1, interpolator)) {
226
227 // evaluate handler value at the end of the substep
228 final FieldAbsoluteDate<T> tb = sb.getDate();
229 final T gb = g(sb);
230
231 // check events occurrence
232 if (gb.getReal() == 0.0 || (g0Positive ^ gb.getReal() > 0)) {
233 // there is a sign change: an event is expected during this step
234 if (findRoot(interpolator, ta, ga, tb, gb)) {
235 return true;
236 }
237 } else {
238 // no sign change: there is no event for now
239 ta = tb;
240 ga = gb;
241 }
242 }
243
244 // no event during the whole step
245 pendingEvent = false;
246 pendingEventTime = null;
247 return false;
248
249 }
250
251 /** Estimate next state to check.
252 * @param done state already checked
253 * @param target target state towards which we are checking
254 * @param interpolator step interpolator for the proposed step
255 * @return intermediate state to check, or exactly {@code null}
256 * if we already have {@code done == target}
257 * @since 12.0
258 */
259 private FieldSpacecraftState<T> nextCheck(final FieldSpacecraftState<T> done, final FieldSpacecraftState<T> target,
260 final FieldOrekitStepInterpolator<T> interpolator) {
261 if (done == target) {
262 // we have already reached target
263 return null;
264 } else {
265 // we have to select some intermediate state
266 // attempting to split the remaining time in an integer number of checks
267 final T dt = target.getDate().durationFrom(done.getDate());
268 final double maxCheck = detector.getMaxCheckInterval().currentInterval(done, dt.getReal() >= 0.);
269 final int n = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt).divide(maxCheck).getReal()));
270 return n == 1 ? target : interpolator.getInterpolatedState(done.getDate().shiftedBy(dt.divide(n)));
271 }
272 }
273
274 /**
275 * Find a root in a bracketing interval.
276 *
277 * <p> When calling this method one of the following must be true. Either ga == 0, gb
278 * == 0, (ga < 0 and gb > 0), or (ga > 0 and gb < 0).
279 *
280 * @param interpolator that covers the interval.
281 * @param ta earliest possible time for root.
282 * @param ga g(ta).
283 * @param tb latest possible time for root.
284 * @param gb g(tb).
285 * @return if a zero crossing was found.
286 */
287 private boolean findRoot(final FieldOrekitStepInterpolator<T> interpolator,
288 final FieldAbsoluteDate<T> ta, final T ga,
289 final FieldAbsoluteDate<T> tb, final T gb) {
290
291 final T zero = ga.getField().getZero();
292
293 // check there appears to be a root in [ta, tb]
294 check(ga.getReal() == 0.0 || gb.getReal() == 0.0 || ga.getReal() > 0.0 && gb.getReal() < 0.0 || ga.getReal() < 0.0 && gb.getReal() > 0.0);
295 final T convergence = detector.getThreshold();
296 final int maxIterationCount = detector.getMaxIterationCount();
297 final BracketedUnivariateSolver<UnivariateFunction> solver =
298 new BracketingNthOrderBrentSolver(0, convergence.getReal(), 0, 5);
299
300 // prepare loop below
301 FieldAbsoluteDate<T> loopT = ta;
302 T loopG = ga;
303
304 // event time, just at or before the actual root.
305 FieldAbsoluteDate<T> beforeRootT = null;
306 T beforeRootG = zero.add(Double.NaN);
307 // time on the other side of the root.
308 // Initialized the the loop below executes once.
309 FieldAbsoluteDate<T> afterRootT = ta;
310 T afterRootG = zero;
311
312 // check for some conditions that the root finders don't like
313 // these conditions cannot not happen in the loop below
314 // the ga == 0.0 case is handled by the loop below
315 if (ta.equals(tb)) {
316 // both non-zero but times are the same. Probably due to reset state
317 beforeRootT = ta;
318 beforeRootG = ga;
319 afterRootT = shiftedBy(beforeRootT, convergence);
320 afterRootG = g(interpolator.getInterpolatedState(afterRootT));
321 } else if (ga.getReal() != 0.0 && gb.getReal() == 0.0) {
322 // hard: ga != 0.0 and gb == 0.0
323 // look past gb by up to convergence to find next sign
324 // throw an exception if g(t) = 0.0 in [tb, tb + convergence]
325 beforeRootT = tb;
326 beforeRootG = gb;
327 afterRootT = shiftedBy(beforeRootT, convergence);
328 afterRootG = g(interpolator.getInterpolatedState(afterRootT));
329 } else if (ga.getReal() != 0.0) {
330 final T newGa = g(interpolator.getInterpolatedState(ta));
331 if (ga.getReal() > 0 != newGa.getReal() > 0) {
332 // both non-zero, step sign change at ta, possibly due to reset state
333 final FieldAbsoluteDate<T> nextT = minTime(shiftedBy(ta, convergence), tb);
334 final T nextG = g(interpolator.getInterpolatedState(nextT));
335 if (nextG.getReal() > 0.0 == g0Positive) {
336 // the sign change between ga and newGa just moved the root less than one convergence
337 // threshold later, we are still in a regular search for another root before tb,
338 // we just need to fix the bracketing interval
339 // (see issue https://github.com/Hipparchus-Math/hipparchus/issues/184)
340 loopT = nextT;
341 loopG = nextG;
342 } else {
343 beforeRootT = ta;
344 beforeRootG = newGa;
345 afterRootT = nextT;
346 afterRootG = nextG;
347 }
348 }
349 }
350
351 // loop to skip through "fake" roots, i.e. where g(t) = g'(t) = 0.0
352 // executed once if we didn't hit a special case above
353 while ((afterRootG.getReal() == 0.0 || afterRootG.getReal() > 0.0 == g0Positive) &&
354 strictlyAfter(afterRootT, tb)) {
355 if (loopG.getReal() == 0.0) {
356 // ga == 0.0 and gb may or may not be 0.0
357 // handle the root at ta first
358 beforeRootT = loopT;
359 beforeRootG = loopG;
360 afterRootT = minTime(shiftedBy(beforeRootT, convergence), tb);
361 afterRootG = g(interpolator.getInterpolatedState(afterRootT));
362 } else {
363 // both non-zero, the usual case, use a root finder.
364 // time zero for evaluating the function f. Needs to be final
365 final FieldAbsoluteDate<T> fT0 = loopT;
366 final double tbDouble = tb.durationFrom(fT0).getReal();
367 final double middle = 0.5 * tbDouble;
368 final DoubleFunction<FieldAbsoluteDate<T>> date = dt -> {
369 // use either fT0 or tb as the base time for shifts
370 // in order to ensure we reproduce exactly those times
371 // using only one reference time like fT0 would imply
372 // to use ft0.shiftedBy(tbDouble), which may be different
373 // from tb due to numerical noise (see issue 921)
374 if (forward == dt <= middle) {
375 // use start of interval as reference
376 return fT0.shiftedBy(dt);
377 } else {
378 // use end of interval as reference
379 return tb.shiftedBy(dt - tbDouble);
380 }
381 };
382 final UnivariateFunction f = dt -> g(interpolator.getInterpolatedState(date.apply(dt))).getReal();
383 if (forward) {
384 try {
385 final Interval interval =
386 solver.solveInterval(maxIterationCount, f, 0, tbDouble);
387 beforeRootT = date.apply(interval.getLeftAbscissa());
388 beforeRootG = zero.add(interval.getLeftValue());
389 afterRootT = date.apply(interval.getRightAbscissa());
390 afterRootG = zero.add(interval.getRightValue());
391 // CHECKSTYLE: stop IllegalCatch check
392 } catch (RuntimeException e) {
393 // CHECKSTYLE: resume IllegalCatch check
394 throw new OrekitException(e, OrekitMessages.FIND_ROOT,
395 detector, loopT, loopG, tb, gb, lastT, lastG);
396 }
397 } else {
398 try {
399 final Interval interval =
400 solver.solveInterval(maxIterationCount, f, tbDouble, 0);
401 beforeRootT = date.apply(interval.getRightAbscissa());
402 beforeRootG = zero.newInstance(interval.getRightValue());
403 afterRootT = date.apply(interval.getLeftAbscissa());
404 afterRootG = zero.newInstance(interval.getLeftValue());
405 // CHECKSTYLE: stop IllegalCatch check
406 } catch (RuntimeException e) {
407 // CHECKSTYLE: resume IllegalCatch check
408 throw new OrekitException(e, OrekitMessages.FIND_ROOT,
409 detector, tb, gb, loopT, loopG, lastT, lastG);
410 }
411 }
412 }
413 // tolerance is set to less than 1 ulp
414 // assume tolerance is 1 ulp
415 if (beforeRootT.equals(afterRootT)) {
416 afterRootT = nextAfter(afterRootT);
417 afterRootG = g(interpolator.getInterpolatedState(afterRootT));
418 }
419 // check loop is making some progress
420 check(forward && afterRootT.compareTo(beforeRootT) > 0 ||
421 !forward && afterRootT.compareTo(beforeRootT) < 0);
422 // setup next iteration
423 loopT = afterRootT;
424 loopG = afterRootG;
425 }
426
427 // figure out the result of root finding, and return accordingly
428 if (afterRootG.getReal() == 0.0 || afterRootG.getReal() > 0.0 == g0Positive) {
429 // loop gave up and didn't find any crossing within this step
430 return false;
431 } else {
432 // real crossing
433 check(beforeRootT != null && !Double.isNaN(beforeRootG.getReal()));
434 // variation direction, with respect to the integration direction
435 increasing = !g0Positive;
436 pendingEventTime = beforeRootT;
437 stopTime = beforeRootG.getReal() == 0.0 ? beforeRootT : afterRootT;
438 pendingEvent = true;
439 afterEvent = afterRootT;
440 afterG = afterRootG;
441
442 // check increasing set correctly
443 check(afterG.getReal() > 0 == increasing);
444 check(increasing == gb.getReal() >= ga.getReal());
445
446 return true;
447 }
448
449 }
450
451 /**
452 * Get the next number after the given number in the current propagation direction.
453 *
454 * @param t input time
455 * @return t +/- 1 ulp depending on the direction.
456 */
457 private FieldAbsoluteDate<T> nextAfter(final FieldAbsoluteDate<T> t) {
458 return t.shiftedBy(forward ? +Precision.EPSILON : -Precision.EPSILON);
459 }
460
461
462 /** Get the occurrence time of the event triggered in the current
463 * step.
464 * @return occurrence time of the event triggered in the current
465 * step.
466 */
467 public FieldAbsoluteDate<T> getEventDate() {
468 return pendingEventTime;
469 }
470
471 /**
472 * Try to accept the current history up to the given time.
473 *
474 * <p> It is not necessary to call this method before calling {@link
475 * #doEvent(FieldSpacecraftState)} with the same state. It is necessary to call this
476 * method before you call {@link #doEvent(FieldSpacecraftState)} on some other event
477 * detector.
478 *
479 * @param state to try to accept.
480 * @param interpolator to use to find the new root, if any.
481 * @return if the event detector has an event it has not detected before that is on or
482 * before the same time as {@code state}. In other words {@code false} means continue
483 * on while {@code true} means stop and handle my event first.
484 */
485 public boolean tryAdvance(final FieldSpacecraftState<T> state,
486 final FieldOrekitStepInterpolator<T> interpolator) {
487 final FieldAbsoluteDate<T> t = state.getDate();
488 // check this is only called before a pending event.
489 check(!pendingEvent || !strictlyAfter(pendingEventTime, t));
490
491 final boolean meFirst;
492
493 if (strictlyAfter(t, earliestTimeConsidered)) {
494 // just found an event and we know the next time we want to search again
495 meFirst = false;
496 } else {
497 // check g function to see if there is a new event
498 final T g = g(state);
499 final boolean positive = g.getReal() > 0;
500
501 if (positive == g0Positive) {
502 // g function has expected sign
503 g0 = g; // g0Positive is the same
504 meFirst = false;
505 } else {
506 // found a root we didn't expect -> find precise location
507 final FieldAbsoluteDate<T> oldPendingEventTime = pendingEventTime;
508 final boolean foundRoot = findRoot(interpolator, t0, g0, t, g);
509 // make sure the new root is not the same as the old root, if one exists
510 meFirst = foundRoot && !pendingEventTime.equals(oldPendingEventTime);
511 }
512 }
513
514 if (!meFirst) {
515 // advance t0 to the current time so we can't find events that occur before t
516 t0 = t;
517 }
518
519 return meFirst;
520 }
521
522 /**
523 * Notify the user's listener of the event. The event occurs wholly within this method
524 * call including a call to {@link FieldEventHandler#resetState(FieldEventDetector,
525 * FieldSpacecraftState)} if necessary.
526 *
527 * @param state the state at the time of the event. This must be at the same time as
528 * the current value of {@link #getEventDate()}.
529 * @return the user's requested action and the new state if the action is {@link
530 * Action#RESET_STATE}. Otherwise
531 * the new state is {@code state}. The stop time indicates what time propagation should
532 * stop if the action is {@link Action#STOP}.
533 * This guarantees the integration will stop on or after the root, so that integration
534 * may be restarted safely.
535 */
536 public EventOccurrence<T> doEvent(final FieldSpacecraftState<T> state) {
537 // check event is pending and is at the same time
538 check(pendingEvent);
539 check(state.getDate().equals(this.pendingEventTime));
540
541 final Action action = handler.eventOccurred(state, detector, increasing == forward);
542 final FieldSpacecraftState<T> newState;
543 if (action == Action.RESET_STATE) {
544 newState = handler.resetState(detector, state);
545 } else {
546 newState = state;
547 }
548 // clear pending event
549 pendingEvent = false;
550 pendingEventTime = null;
551 // setup for next search
552 earliestTimeConsidered = afterEvent;
553 t0 = afterEvent;
554 g0 = afterG;
555 g0Positive = increasing;
556 // check g0Positive set correctly
557 check(g0.getReal() == 0.0 || g0Positive == g0.getReal() > 0);
558 return new EventOccurrence<>(action, newState, stopTime);
559 }
560
561 /**
562 * Shift a time value along the current integration direction: {@link #forward}.
563 *
564 * @param t the time to shift.
565 * @param delta the amount to shift.
566 * @return t + delta if forward, else t - delta. If the result has to be rounded it
567 * will be rounded to be before the true value of t + delta.
568 */
569 private FieldAbsoluteDate<T> shiftedBy(final FieldAbsoluteDate<T> t, final T delta) {
570 if (forward) {
571 final FieldAbsoluteDate<T> ret = t.shiftedBy(delta);
572 if (ret.durationFrom(t).getReal() > delta.getReal()) {
573 return ret.shiftedBy(-Precision.EPSILON);
574 } else {
575 return ret;
576 }
577 } else {
578 final FieldAbsoluteDate<T> ret = t.shiftedBy(delta.negate());
579 if (t.durationFrom(ret).getReal() > delta.getReal()) {
580 return ret.shiftedBy(+Precision.EPSILON);
581 } else {
582 return ret;
583 }
584 }
585 }
586
587 /**
588 * Get the time that happens first along the current propagation direction: {@link
589 * #forward}.
590 *
591 * @param a first time
592 * @param b second time
593 * @return min(a, b) if forward, else max (a, b)
594 */
595 private FieldAbsoluteDate<T> minTime(final FieldAbsoluteDate<T> a, final FieldAbsoluteDate<T> b) {
596 return (forward ^ a.compareTo(b) > 0) ? a : b;
597 }
598
599 /**
600 * Check the ordering of two times.
601 *
602 * @param t1 the first time.
603 * @param t2 the second time.
604 * @return true if {@code t2} is strictly after {@code t1} in the propagation
605 * direction.
606 */
607 private boolean strictlyAfter(final FieldAbsoluteDate<T> t1, final FieldAbsoluteDate<T> t2) {
608 if (t1 == null || t2 == null) {
609 return false;
610 } else {
611 return forward ? t1.compareTo(t2) < 0 : t2.compareTo(t1) < 0;
612 }
613 }
614
615 /**
616 * Same as keyword assert, but throw a {@link MathRuntimeException}.
617 *
618 * @param condition to check
619 * @throws MathRuntimeException if {@code condition} is false.
620 */
621 private void check(final boolean condition) throws MathRuntimeException {
622 if (!condition) {
623 throw new OrekitInternalError(null);
624 }
625 }
626
627 /**
628 * This method finalizes the event detector's job.
629 * @param state state at propagation end
630 * @since 12.2
631 */
632 public void finish(final FieldSpacecraftState<T> state) {
633 detector.finish(state);
634 }
635
636 /**
637 * Class to hold the data related to an event occurrence that is needed to decide how
638 * to modify integration.
639 * @param <T> type of the field elements
640 */
641 public static class EventOccurrence<T extends CalculusFieldElement<T>> {
642
643 /** User requested action. */
644 private final Action action;
645 /** New state for a reset action. */
646 private final FieldSpacecraftState<T> newState;
647 /** The time to stop propagation if the action is a stop event. */
648 private final FieldAbsoluteDate<T> stopDate;
649
650 /**
651 * Create a new occurrence of an event.
652 *
653 * @param action the user requested action.
654 * @param newState for a reset event. Should be the current state unless the
655 * action is {@link Action#RESET_STATE}.
656 * @param stopDate to stop propagation if the action is {@link Action#STOP}. Used
657 * to move the stop time to just after the root.
658 */
659 EventOccurrence(final Action action,
660 final FieldSpacecraftState<T> newState,
661 final FieldAbsoluteDate<T> stopDate) {
662 this.action = action;
663 this.newState = newState;
664 this.stopDate = stopDate;
665 }
666
667 /**
668 * Get the user requested action.
669 *
670 * @return the action.
671 */
672 public Action getAction() {
673 return action;
674 }
675
676 /**
677 * Get the new state for a reset action.
678 *
679 * @return the new state.
680 */
681 public FieldSpacecraftState<T> getNewState() {
682 return newState;
683 }
684
685 /**
686 * Get the new time for a stop action.
687 *
688 * @return when to stop propagation.
689 */
690 public FieldAbsoluteDate<T> getStopDate() {
691 return stopDate;
692 }
693
694 }
695
696 /**Get PendingEvent.
697 * @return if there is a pending event or not
698 * */
699
700 public boolean getPendingEvent() {
701 return pendingEvent;
702 }
703
704 }