FieldEventState.java

  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. import java.util.function.DoubleFunction;

  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.Field;
  21. import org.hipparchus.analysis.UnivariateFunction;
  22. import org.hipparchus.analysis.solvers.BracketedUnivariateSolver;
  23. import org.hipparchus.analysis.solvers.BracketedUnivariateSolver.Interval;
  24. import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
  25. import org.hipparchus.exception.MathRuntimeException;
  26. import org.hipparchus.ode.events.Action;
  27. import org.hipparchus.util.FastMath;
  28. import org.hipparchus.util.Precision;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitInternalError;
  31. import org.orekit.errors.OrekitMessages;
  32. import org.orekit.propagation.FieldSpacecraftState;
  33. import org.orekit.propagation.events.handlers.FieldEventHandler;
  34. import org.orekit.propagation.sampling.FieldOrekitStepInterpolator;
  35. import org.orekit.time.FieldAbsoluteDate;

  36. /** This class handles the state for one {@link FieldEventDetector
  37.  * event detector} during integration steps.
  38.  *
  39.  * <p>This class is heavily based on the class with the same name from the
  40.  * Hipparchus library. The changes performed consist in replacing
  41.  * raw types (double and double arrays) with space dynamics types
  42.  * ({@link FieldAbsoluteDate}, {@link FieldSpacecraftState}).</p>
  43.  * <p>Each time the propagator proposes a step, the event detector
  44.  * should be checked. This class handles the state of one detector
  45.  * during one propagation step, with references to the state at the
  46.  * end of the preceding step. This information is used to determine if
  47.  * the detector should trigger an event or not during the proposed
  48.  * step (and hence the step should be reduced to ensure the event
  49.  * occurs at a bound rather than inside the step).</p>
  50.  * @author Luc Maisonobe
  51.  * @param <D> class type for the generic version
  52.  * @param <T> type of the field elements
  53.  */
  54. public class FieldEventState<D extends FieldEventDetector<T>, T extends CalculusFieldElement<T>> {

  55.     /** Event detector. */
  56.     private D detector;

  57.     /** Event handler. */
  58.     private FieldEventHandler<T> handler;

  59.     /** Time of the previous call to g. */
  60.     private FieldAbsoluteDate<T> lastT;

  61.     /** Value from the previous call to g. */
  62.     private T lastG;

  63.     /** Time at the beginning of the step. */
  64.     private FieldAbsoluteDate<T> t0;

  65.     /** Value of the event detector at the beginning of the step. */
  66.     private T g0;

  67.     /** Simulated sign of g0 (we cheat when crossing events). */
  68.     private boolean g0Positive;

  69.     /** Indicator of event expected during the step. */
  70.     private boolean pendingEvent;

  71.     /** Occurrence time of the pending event. */
  72.     private FieldAbsoluteDate<T> pendingEventTime;

  73.     /**
  74.      * Time to stop propagation if the event is a stop event. Used to enable stopping at
  75.      * an event and then restarting after that event.
  76.      */
  77.     private FieldAbsoluteDate<T> stopTime;

  78.     /** Time after the current event. */
  79.     private FieldAbsoluteDate<T> afterEvent;

  80.     /** Value of the g function after the current event. */
  81.     private T afterG;

  82.     /** The earliest time considered for events. */
  83.     private FieldAbsoluteDate<T> earliestTimeConsidered;

  84.     /** Integration direction. */
  85.     private boolean forward;

  86.     /** Variation direction around pending event.
  87.      *  (this is considered with respect to the integration direction)
  88.      */
  89.     private boolean increasing;

  90.     /** Simple constructor.
  91.      * @param detector monitored event detector
  92.      */
  93.     public FieldEventState(final D detector) {

  94.         this.detector = detector;
  95.         this.handler  = detector.getHandler();

  96.         // some dummy values ...
  97.         final Field<T> field   = detector.getThreshold().getField();
  98.         final T nan            = field.getZero().add(Double.NaN);
  99.         lastT                  = FieldAbsoluteDate.getPastInfinity(field);
  100.         lastG                  = nan;
  101.         t0                     = null;
  102.         g0                     = nan;
  103.         g0Positive             = true;
  104.         pendingEvent           = false;
  105.         pendingEventTime       = null;
  106.         stopTime               = null;
  107.         increasing             = true;
  108.         earliestTimeConsidered = null;
  109.         afterEvent             = null;
  110.         afterG                 = nan;

  111.     }

  112.     /** Get the underlying event detector.
  113.      * @return underlying event detector
  114.      */
  115.     public D getEventDetector() {
  116.         return detector;
  117.     }

  118.     /** Initialize event handler at the start of a propagation.
  119.      * <p>
  120.      * This method is called once at the start of the propagation. It
  121.      * may be used by the event handler to initialize some internal data
  122.      * if needed.
  123.      * </p>
  124.      * @param s0 initial state
  125.      * @param t target time for the integration
  126.      *
  127.      */
  128.     public void init(final FieldSpacecraftState<T> s0,
  129.                      final FieldAbsoluteDate<T> t) {
  130.         detector.init(s0, t);
  131.         final Field<T> field = detector.getThreshold().getField();
  132.         lastT = FieldAbsoluteDate.getPastInfinity(field);
  133.         lastG = field.getZero().add(Double.NaN);
  134.     }

  135.     /** Compute the value of the switching function.
  136.      * This function must be continuous (at least in its roots neighborhood),
  137.      * as the integrator will need to find its roots to locate the events.
  138.      * @param s the current state information: date, kinematics, attitude
  139.      * @return value of the switching function
  140.      */
  141.     private T g(final FieldSpacecraftState<T> s) {
  142.         if (!s.getDate().equals(lastT)) {
  143.             lastT = s.getDate();
  144.             lastG = detector.g(s);
  145.         }
  146.         return lastG;
  147.     }

  148.     /** Reinitialize the beginning of the step.
  149.      * @param interpolator interpolator valid for the current step
  150.      */
  151.     public void reinitializeBegin(final FieldOrekitStepInterpolator<T> interpolator) {
  152.         forward = interpolator.isForward();
  153.         final FieldSpacecraftState<T> s0 = interpolator.getPreviousState();
  154.         this.t0 = s0.getDate();
  155.         g0 = g(s0);
  156.         while (g0.getReal() == 0) {
  157.             // extremely rare case: there is a zero EXACTLY at interval start
  158.             // we will use the sign slightly after step beginning to force ignoring this zero
  159.             // try moving forward by half a convergence interval
  160.             final T dt = detector.getThreshold().multiply(forward ? 0.5 : -0.5);
  161.             FieldAbsoluteDate<T> startDate = t0.shiftedBy(dt);
  162.             // if convergence is too small move an ulp
  163.             if (t0.equals(startDate)) {
  164.                 startDate = nextAfter(startDate);
  165.             }
  166.             t0 = startDate;
  167.             g0 = g(interpolator.getInterpolatedState(t0));
  168.         }
  169.         g0Positive = g0.getReal() > 0;
  170.         // "last" event was increasing
  171.         increasing = g0Positive;
  172.     }

  173.     /** Evaluate the impact of the proposed step on the event detector.
  174.      * @param interpolator step interpolator for the proposed step
  175.      * @return true if the event detector triggers an event before
  176.      * the end of the proposed step (this implies the step should be
  177.      * rejected)
  178.      * @exception MathRuntimeException if an event cannot be located
  179.      */
  180.     public boolean evaluateStep(final FieldOrekitStepInterpolator<T> interpolator)
  181.         throws MathRuntimeException {
  182.         forward = interpolator.isForward();
  183.         final FieldSpacecraftState<T> s0 = interpolator.getPreviousState();
  184.         final FieldSpacecraftState<T> s1 = interpolator.getCurrentState();
  185.         final FieldAbsoluteDate<T> t1 = s1.getDate();
  186.         final T dt = t1.durationFrom(t0);
  187.         if (FastMath.abs(dt.getReal()) < detector.getThreshold().getReal()) {
  188.             // we cannot do anything on such a small step, don't trigger any events
  189.             pendingEvent     = false;
  190.             pendingEventTime = null;
  191.             return false;
  192.         }

  193.         FieldAbsoluteDate<T> ta = t0;
  194.         T ga = g0;
  195.         for (FieldSpacecraftState<T> sb = nextCheck(s0, s1, interpolator);
  196.              sb != null;
  197.              sb = nextCheck(sb, s1, interpolator)) {

  198.             // evaluate handler value at the end of the substep
  199.             final FieldAbsoluteDate<T> tb = sb.getDate();
  200.             final T gb = g(sb);

  201.             // check events occurrence
  202.             if (gb.getReal() == 0.0 || (g0Positive ^ gb.getReal() > 0)) {
  203.                 // there is a sign change: an event is expected during this step
  204.                 if (findRoot(interpolator, ta, ga, tb, gb)) {
  205.                     return true;
  206.                 }
  207.             } else {
  208.                 // no sign change: there is no event for now
  209.                 ta = tb;
  210.                 ga = gb;
  211.             }
  212.         }

  213.         // no event during the whole step
  214.         pendingEvent     = false;
  215.         pendingEventTime = null;
  216.         return false;

  217.     }

  218.     /** Estimate next state to check.
  219.      * @param done state already checked
  220.      * @param target target state towards which we are checking
  221.      * @param interpolator step interpolator for the proposed step
  222.      * @return intermediate state to check, or exactly {@code null}
  223.      * if we already have {@code done == target}
  224.      * @since 12.0
  225.      */
  226.     private FieldSpacecraftState<T> nextCheck(final FieldSpacecraftState<T> done, final FieldSpacecraftState<T> target,
  227.                                               final FieldOrekitStepInterpolator<T> interpolator) {
  228.         if (done == target) {
  229.             // we have already reached target
  230.             return null;
  231.         } else {
  232.             // we have to select some intermediate state
  233.             // attempting to split the remaining time in an integer number of checks
  234.             final T dt            = target.getDate().durationFrom(done.getDate());
  235.             final double maxCheck = detector.getMaxCheckInterval().currentInterval(done, dt.getReal() >= 0.);
  236.             final int    n        = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt).divide(maxCheck).getReal()));
  237.             return n == 1 ? target : interpolator.getInterpolatedState(done.getDate().shiftedBy(dt.divide(n)));
  238.         }
  239.     }

  240.     /**
  241.      * Find a root in a bracketing interval.
  242.      *
  243.      * <p> When calling this method one of the following must be true. Either ga == 0, gb
  244.      * == 0, (ga < 0  and gb > 0), or (ga > 0 and gb < 0).
  245.      *
  246.      * @param interpolator that covers the interval.
  247.      * @param ta           earliest possible time for root.
  248.      * @param ga           g(ta).
  249.      * @param tb           latest possible time for root.
  250.      * @param gb           g(tb).
  251.      * @return if a zero crossing was found.
  252.      */
  253.     private boolean findRoot(final FieldOrekitStepInterpolator<T> interpolator,
  254.                              final FieldAbsoluteDate<T> ta, final T ga,
  255.                              final FieldAbsoluteDate<T> tb, final T gb) {

  256.         final T zero = ga.getField().getZero();

  257.         // check there appears to be a root in [ta, tb]
  258.         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);
  259.         final T convergence = detector.getThreshold();
  260.         final int maxIterationCount = detector.getMaxIterationCount();
  261.         final BracketedUnivariateSolver<UnivariateFunction> solver =
  262.                 new BracketingNthOrderBrentSolver(0, convergence.getReal(), 0, 5);

  263.         // prepare loop below
  264.         FieldAbsoluteDate<T> loopT = ta;
  265.         T loopG = ga;

  266.         // event time, just at or before the actual root.
  267.         FieldAbsoluteDate<T> beforeRootT = null;
  268.         T beforeRootG = zero.add(Double.NaN);
  269.         // time on the other side of the root.
  270.         // Initialized the the loop below executes once.
  271.         FieldAbsoluteDate<T> afterRootT = ta;
  272.         T afterRootG = zero;

  273.         // check for some conditions that the root finders don't like
  274.         // these conditions cannot not happen in the loop below
  275.         // the ga == 0.0 case is handled by the loop below
  276.         if (ta.equals(tb)) {
  277.             // both non-zero but times are the same. Probably due to reset state
  278.             beforeRootT = ta;
  279.             beforeRootG = ga;
  280.             afterRootT = shiftedBy(beforeRootT, convergence);
  281.             afterRootG = g(interpolator.getInterpolatedState(afterRootT));
  282.         } else if (ga.getReal() != 0.0 && gb.getReal() == 0.0) {
  283.             // hard: ga != 0.0 and gb == 0.0
  284.             // look past gb by up to convergence to find next sign
  285.             // throw an exception if g(t) = 0.0 in [tb, tb + convergence]
  286.             beforeRootT = tb;
  287.             beforeRootG = gb;
  288.             afterRootT = shiftedBy(beforeRootT, convergence);
  289.             afterRootG = g(interpolator.getInterpolatedState(afterRootT));
  290.         } else if (ga.getReal() != 0.0) {
  291.             final T newGa = g(interpolator.getInterpolatedState(ta));
  292.             if (ga.getReal() > 0 != newGa.getReal() > 0) {
  293.                 // both non-zero, step sign change at ta, possibly due to reset state
  294.                 final FieldAbsoluteDate<T> nextT = minTime(shiftedBy(ta, convergence), tb);
  295.                 final T                    nextG = g(interpolator.getInterpolatedState(nextT));
  296.                 if (nextG.getReal() > 0.0 == g0Positive) {
  297.                     // the sign change between ga and newGa just moved the root less than one convergence
  298.                     // threshold later, we are still in a regular search for another root before tb,
  299.                     // we just need to fix the bracketing interval
  300.                     // (see issue https://github.com/Hipparchus-Math/hipparchus/issues/184)
  301.                     loopT = nextT;
  302.                     loopG = nextG;
  303.                 } else {
  304.                     beforeRootT = ta;
  305.                     beforeRootG = newGa;
  306.                     afterRootT  = nextT;
  307.                     afterRootG  = nextG;
  308.                 }
  309.             }
  310.         }

  311.         // loop to skip through "fake" roots, i.e. where g(t) = g'(t) = 0.0
  312.         // executed once if we didn't hit a special case above
  313.         while ((afterRootG.getReal() == 0.0 || afterRootG.getReal() > 0.0 == g0Positive) &&
  314.                 strictlyAfter(afterRootT, tb)) {
  315.             if (loopG.getReal() == 0.0) {
  316.                 // ga == 0.0 and gb may or may not be 0.0
  317.                 // handle the root at ta first
  318.                 beforeRootT = loopT;
  319.                 beforeRootG = loopG;
  320.                 afterRootT = minTime(shiftedBy(beforeRootT, convergence), tb);
  321.                 afterRootG = g(interpolator.getInterpolatedState(afterRootT));
  322.             } else {
  323.                 // both non-zero, the usual case, use a root finder.
  324.                 // time zero for evaluating the function f. Needs to be final
  325.                 final FieldAbsoluteDate<T> fT0 = loopT;
  326.                 final double tbDouble = tb.durationFrom(fT0).getReal();
  327.                 final double middle   = 0.5 * tbDouble;
  328.                 final DoubleFunction<FieldAbsoluteDate<T>> date = dt -> {
  329.                     // use either fT0 or tb as the base time for shifts
  330.                     // in order to ensure we reproduce exactly those times
  331.                     // using only one reference time like fT0 would imply
  332.                     // to use ft0.shiftedBy(tbDouble), which may be different
  333.                     // from tb due to numerical noise (see issue 921)
  334.                     if (forward == dt <= middle) {
  335.                         // use start of interval as reference
  336.                         return fT0.shiftedBy(dt);
  337.                     } else {
  338.                         // use end of interval as reference
  339.                         return tb.shiftedBy(dt - tbDouble);
  340.                     }
  341.                 };
  342.                 final UnivariateFunction f = dt -> g(interpolator.getInterpolatedState(date.apply(dt))).getReal();
  343.                 if (forward) {
  344.                     try {
  345.                         final Interval interval =
  346.                                 solver.solveInterval(maxIterationCount, f, 0, tbDouble);
  347.                         beforeRootT = date.apply(interval.getLeftAbscissa());
  348.                         beforeRootG = zero.add(interval.getLeftValue());
  349.                         afterRootT  = date.apply(interval.getRightAbscissa());
  350.                         afterRootG  = zero.add(interval.getRightValue());
  351.                         // CHECKSTYLE: stop IllegalCatch check
  352.                     } catch (RuntimeException e) {
  353.                         // CHECKSTYLE: resume IllegalCatch check
  354.                         throw new OrekitException(e, OrekitMessages.FIND_ROOT,
  355.                                 detector, loopT, loopG, tb, gb, lastT, lastG);
  356.                     }
  357.                 } else {
  358.                     try {
  359.                         final Interval interval =
  360.                                 solver.solveInterval(maxIterationCount, f, tbDouble, 0);
  361.                         beforeRootT = date.apply(interval.getRightAbscissa());
  362.                         beforeRootG = zero.newInstance(interval.getRightValue());
  363.                         afterRootT  = date.apply(interval.getLeftAbscissa());
  364.                         afterRootG  = zero.newInstance(interval.getLeftValue());
  365.                         // CHECKSTYLE: stop IllegalCatch check
  366.                     } catch (RuntimeException e) {
  367.                         // CHECKSTYLE: resume IllegalCatch check
  368.                         throw new OrekitException(e, OrekitMessages.FIND_ROOT,
  369.                                 detector, tb, gb, loopT, loopG, lastT, lastG);
  370.                     }
  371.                 }
  372.             }
  373.             // tolerance is set to less than 1 ulp
  374.             // assume tolerance is 1 ulp
  375.             if (beforeRootT.equals(afterRootT)) {
  376.                 afterRootT = nextAfter(afterRootT);
  377.                 afterRootG = g(interpolator.getInterpolatedState(afterRootT));
  378.             }
  379.             // check loop is making some progress
  380.             check(forward && afterRootT.compareTo(beforeRootT) > 0 ||
  381.                   !forward && afterRootT.compareTo(beforeRootT) < 0);
  382.             // setup next iteration
  383.             loopT = afterRootT;
  384.             loopG = afterRootG;
  385.         }

  386.         // figure out the result of root finding, and return accordingly
  387.         if (afterRootG.getReal() == 0.0 || afterRootG.getReal() > 0.0 == g0Positive) {
  388.             // loop gave up and didn't find any crossing within this step
  389.             return false;
  390.         } else {
  391.             // real crossing
  392.             check(beforeRootT != null && !Double.isNaN(beforeRootG.getReal()));
  393.             // variation direction, with respect to the integration direction
  394.             increasing = !g0Positive;
  395.             pendingEventTime = beforeRootT;
  396.             stopTime = beforeRootG.getReal() == 0.0 ? beforeRootT : afterRootT;
  397.             pendingEvent = true;
  398.             afterEvent = afterRootT;
  399.             afterG = afterRootG;

  400.             // check increasing set correctly
  401.             check(afterG.getReal() > 0 == increasing);
  402.             check(increasing == gb.getReal() >= ga.getReal());

  403.             return true;
  404.         }

  405.     }

  406.     /**
  407.      * Get the next number after the given number in the current propagation direction.
  408.      *
  409.      * @param t input time
  410.      * @return t +/- 1 ulp depending on the direction.
  411.      */
  412.     private FieldAbsoluteDate<T> nextAfter(final FieldAbsoluteDate<T> t) {
  413.         return t.shiftedBy(forward ? +Precision.EPSILON : -Precision.EPSILON);
  414.     }


  415.     /** Get the occurrence time of the event triggered in the current
  416.      * step.
  417.      * @return occurrence time of the event triggered in the current
  418.      * step.
  419.      */
  420.     public FieldAbsoluteDate<T> getEventDate() {
  421.         return pendingEventTime;
  422.     }

  423.     /**
  424.      * Try to accept the current history up to the given time.
  425.      *
  426.      * <p> It is not necessary to call this method before calling {@link
  427.      * #doEvent(FieldSpacecraftState)} with the same state. It is necessary to call this
  428.      * method before you call {@link #doEvent(FieldSpacecraftState)} on some other event
  429.      * detector.
  430.      *
  431.      * @param state        to try to accept.
  432.      * @param interpolator to use to find the new root, if any.
  433.      * @return if the event detector has an event it has not detected before that is on or
  434.      * before the same time as {@code state}. In other words {@code false} means continue
  435.      * on while {@code true} means stop and handle my event first.
  436.      */
  437.     public boolean tryAdvance(final FieldSpacecraftState<T> state,
  438.                               final FieldOrekitStepInterpolator<T> interpolator) {
  439.         final FieldAbsoluteDate<T> t = state.getDate();
  440.         // check this is only called before a pending event.
  441.         check(!pendingEvent || !strictlyAfter(pendingEventTime, t));

  442.         final boolean meFirst;

  443.         if (strictlyAfter(t, earliestTimeConsidered)) {
  444.             // just found an event and we know the next time we want to search again
  445.             meFirst = false;
  446.         } else {
  447.             // check g function to see if there is a new event
  448.             final T g = g(state);
  449.             final boolean positive = g.getReal() > 0;

  450.             if (positive == g0Positive) {
  451.                 // g function has expected sign
  452.                 g0 = g; // g0Positive is the same
  453.                 meFirst = false;
  454.             } else {
  455.                 // found a root we didn't expect -> find precise location
  456.                 final FieldAbsoluteDate<T> oldPendingEventTime = pendingEventTime;
  457.                 final boolean foundRoot = findRoot(interpolator, t0, g0, t, g);
  458.                 // make sure the new root is not the same as the old root, if one exists
  459.                 meFirst = foundRoot && !pendingEventTime.equals(oldPendingEventTime);
  460.             }
  461.         }

  462.         if (!meFirst) {
  463.             // advance t0 to the current time so we can't find events that occur before t
  464.             t0 = t;
  465.         }

  466.         return meFirst;
  467.     }

  468.     /**
  469.      * Notify the user's listener of the event. The event occurs wholly within this method
  470.      * call including a call to {@link FieldEventHandler#resetState(FieldEventDetector,
  471.      * FieldSpacecraftState)} if necessary.
  472.      *
  473.      * @param state the state at the time of the event. This must be at the same time as
  474.      *              the current value of {@link #getEventDate()}.
  475.      * @return the user's requested action and the new state if the action is {@link
  476.      * Action#RESET_STATE}. Otherwise
  477.      * the new state is {@code state}. The stop time indicates what time propagation should
  478.      * stop if the action is {@link Action#STOP}.
  479.      * This guarantees the integration will stop on or after the root, so that integration
  480.      * may be restarted safely.
  481.      */
  482.     public EventOccurrence<T> doEvent(final FieldSpacecraftState<T> state) {
  483.         // check event is pending and is at the same time
  484.         check(pendingEvent);
  485.         check(state.getDate().equals(this.pendingEventTime));

  486.         final Action action = handler.eventOccurred(state, detector, increasing == forward);
  487.         final FieldSpacecraftState<T> newState;
  488.         if (action == Action.RESET_STATE) {
  489.             newState = handler.resetState(detector, state);
  490.         } else {
  491.             newState = state;
  492.         }
  493.         // clear pending event
  494.         pendingEvent     = false;
  495.         pendingEventTime = null;
  496.         // setup for next search
  497.         earliestTimeConsidered = afterEvent;
  498.         t0 = afterEvent;
  499.         g0 = afterG;
  500.         g0Positive = increasing;
  501.         // check g0Positive set correctly
  502.         check(g0.getReal() == 0.0 || g0Positive == g0.getReal() > 0);
  503.         return new EventOccurrence<>(action, newState, stopTime);
  504.     }

  505.     /**
  506.      * Shift a time value along the current integration direction: {@link #forward}.
  507.      *
  508.      * @param t     the time to shift.
  509.      * @param delta the amount to shift.
  510.      * @return t + delta if forward, else t - delta. If the result has to be rounded it
  511.      * will be rounded to be before the true value of t + delta.
  512.      */
  513.     private FieldAbsoluteDate<T> shiftedBy(final FieldAbsoluteDate<T> t, final T delta) {
  514.         if (forward) {
  515.             final FieldAbsoluteDate<T> ret = t.shiftedBy(delta);
  516.             if (ret.durationFrom(t).getReal() > delta.getReal()) {
  517.                 return ret.shiftedBy(-Precision.EPSILON);
  518.             } else {
  519.                 return ret;
  520.             }
  521.         } else {
  522.             final FieldAbsoluteDate<T> ret = t.shiftedBy(delta.negate());
  523.             if (t.durationFrom(ret).getReal() > delta.getReal()) {
  524.                 return ret.shiftedBy(+Precision.EPSILON);
  525.             } else {
  526.                 return ret;
  527.             }
  528.         }
  529.     }

  530.     /**
  531.      * Get the time that happens first along the current propagation direction: {@link
  532.      * #forward}.
  533.      *
  534.      * @param a first time
  535.      * @param b second time
  536.      * @return min(a, b) if forward, else max (a, b)
  537.      */
  538.     private FieldAbsoluteDate<T> minTime(final FieldAbsoluteDate<T> a, final FieldAbsoluteDate<T> b) {
  539.         return (forward ^ a.compareTo(b) > 0) ? a : b;
  540.     }

  541.     /**
  542.      * Check the ordering of two times.
  543.      *
  544.      * @param t1 the first time.
  545.      * @param t2 the second time.
  546.      * @return true if {@code t2} is strictly after {@code t1} in the propagation
  547.      * direction.
  548.      */
  549.     private boolean strictlyAfter(final FieldAbsoluteDate<T> t1, final FieldAbsoluteDate<T> t2) {
  550.         if (t1 == null || t2 == null) {
  551.             return false;
  552.         } else {
  553.             return forward ? t1.compareTo(t2) < 0 : t2.compareTo(t1) < 0;
  554.         }
  555.     }

  556.     /**
  557.      * Same as keyword assert, but throw a {@link MathRuntimeException}.
  558.      *
  559.      * @param condition to check
  560.      * @throws MathRuntimeException if {@code condition} is false.
  561.      */
  562.     private void check(final boolean condition) throws MathRuntimeException {
  563.         if (!condition) {
  564.             throw new OrekitInternalError(null);
  565.         }
  566.     }

  567.     /**
  568.      * This method finalizes the event detector's job.
  569.      * @param state state at propagation end
  570.      * @since 12.2
  571.      */
  572.     public void finish(final FieldSpacecraftState<T> state) {
  573.         detector.finish(state);
  574.     }

  575.     /**
  576.      * Class to hold the data related to an event occurrence that is needed to decide how
  577.      * to modify integration.
  578.      * @param <T> type of the field elements
  579.      */
  580.     public static class EventOccurrence<T extends CalculusFieldElement<T>> {

  581.         /** User requested action. */
  582.         private final Action action;
  583.         /** New state for a reset action. */
  584.         private final FieldSpacecraftState<T> newState;
  585.         /** The time to stop propagation if the action is a stop event. */
  586.         private final FieldAbsoluteDate<T> stopDate;

  587.         /**
  588.          * Create a new occurrence of an event.
  589.          *
  590.          * @param action   the user requested action.
  591.          * @param newState for a reset event. Should be the current state unless the
  592.          *                 action is {@link Action#RESET_STATE}.
  593.          * @param stopDate to stop propagation if the action is {@link Action#STOP}. Used
  594.          *                 to move the stop time to just after the root.
  595.          */
  596.         EventOccurrence(final Action action,
  597.                         final FieldSpacecraftState<T> newState,
  598.                         final FieldAbsoluteDate<T> stopDate) {
  599.             this.action = action;
  600.             this.newState = newState;
  601.             this.stopDate = stopDate;
  602.         }

  603.         /**
  604.          * Get the user requested action.
  605.          *
  606.          * @return the action.
  607.          */
  608.         public Action getAction() {
  609.             return action;
  610.         }

  611.         /**
  612.          * Get the new state for a reset action.
  613.          *
  614.          * @return the new state.
  615.          */
  616.         public FieldSpacecraftState<T> getNewState() {
  617.             return newState;
  618.         }

  619.         /**
  620.          * Get the new time for a stop action.
  621.          *
  622.          * @return when to stop propagation.
  623.          */
  624.         public FieldAbsoluteDate<T> getStopDate() {
  625.             return stopDate;
  626.         }

  627.     }

  628.     /**Get PendingEvent.
  629.      * @return if there is a pending event or not
  630.      * */

  631.     public boolean getPendingEvent() {
  632.         return pendingEvent;
  633.     }

  634. }