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