DetectorBasedEventState.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.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */

  21. package org.hipparchus.ode.events;

  22. import org.hipparchus.analysis.UnivariateFunction;
  23. import org.hipparchus.analysis.solvers.BracketedUnivariateSolver;
  24. import org.hipparchus.analysis.solvers.BracketedUnivariateSolver.Interval;
  25. import org.hipparchus.exception.MathIllegalArgumentException;
  26. import org.hipparchus.exception.MathIllegalStateException;
  27. import org.hipparchus.exception.MathRuntimeException;
  28. import org.hipparchus.ode.LocalizedODEFormats;
  29. import org.hipparchus.ode.ODEState;
  30. import org.hipparchus.ode.ODEStateAndDerivative;
  31. import org.hipparchus.ode.sampling.ODEStateInterpolator;
  32. import org.hipparchus.util.FastMath;

  33. /** This class handles the state for one {@link ODEEventHandler
  34.  * event handler} during integration steps.
  35.  *
  36.  * <p>Each time the integrator proposes a step, the event handler
  37.  * switching function should be checked. This class handles the state
  38.  * of one handler during one integration step, with references to the
  39.  * state at the end of the preceding step. This information is used to
  40.  * decide if the handler should trigger an event or not during the
  41.  * proposed step.</p>
  42.  *
  43.  */
  44. public class DetectorBasedEventState implements EventState {

  45.     /** Event detector.
  46.      * @since 3.0
  47.      */
  48.     private final ODEEventDetector detector;

  49.     /** Event solver.
  50.      * @since 3.0
  51.      */
  52.     private final BracketedUnivariateSolver<UnivariateFunction> solver;

  53.     /** Event handler. */
  54.     private final ODEEventHandler handler;

  55.     /** Time of the previous call to g. */
  56.     private double lastT;

  57.     /** Value from the previous call to g. */
  58.     private double lastG;

  59.     /** Time at the beginning of the step. */
  60.     private double t0;

  61.     /** Value of the events handler at the beginning of the step. */
  62.     private double g0;

  63.     /** Sign of g0. */
  64.     private boolean g0Positive;

  65.     /** Indicator of event expected during the step. */
  66.     private boolean pendingEvent;

  67.     /** Occurrence time of the pending event. */
  68.     private double pendingEventTime;

  69.     /**
  70.      * Time to stop propagation if the event is a stop event. Used to enable stopping at
  71.      * an event and then restarting after that event.
  72.      */
  73.     private double stopTime;

  74.     /** Time after the current event. */
  75.     private double afterEvent;

  76.     /** Value of the g function after the current event. */
  77.     private double afterG;

  78.     /** The earliest time considered for events. */
  79.     private double earliestTimeConsidered;

  80.     /** Integration direction. */
  81.     private boolean forward;

  82.     /**
  83.      * Direction of g(t) in the propagation direction for the pending event, or if there
  84.      * is no pending event the direction of the previous event.
  85.      */
  86.     private boolean increasing;

  87.     /** Simple constructor.
  88.      * @param detector event detector
  89.      * @since 3.0
  90.      */
  91.     public DetectorBasedEventState(final ODEEventDetector detector) {

  92.         this.detector     = detector;
  93.         this.solver       = detector.getSolver();
  94.         this.handler      = detector.getHandler();

  95.         // some dummy values ...
  96.         t0                = Double.NaN;
  97.         g0                = Double.NaN;
  98.         g0Positive        = true;
  99.         pendingEvent      = false;
  100.         pendingEventTime  = Double.NaN;
  101.         increasing        = true;
  102.         earliestTimeConsidered = Double.NaN;
  103.         afterEvent = Double.NaN;
  104.         afterG = Double.NaN;
  105.     }

  106.     /** Get the underlying event detector.
  107.      * @return underlying event detector
  108.      * @since 3.0
  109.      */
  110.     public ODEEventDetector getEventDetector() {
  111.         return detector;
  112.     }

  113.     /** {@inheritDoc} */
  114.     @Override
  115.     public void init(final ODEStateAndDerivative s0, final double t) {
  116.         detector.init(s0, t);
  117.         lastT = Double.NEGATIVE_INFINITY;
  118.         lastG = Double.NaN;
  119.     }

  120.     /** Compute the value of the switching function.
  121.      * This function must be continuous (at least in its roots neighborhood),
  122.      * as the integrator will need to find its roots to locate the events.
  123.      * @param s the current state information: date, kinematics, attitude
  124.      * @return value of the switching function
  125.      */
  126.     private double g(final ODEStateAndDerivative s) {
  127.         if (s.getTime() != lastT) {
  128.             lastG = detector.g(s);
  129.             lastT = s.getTime();
  130.         }
  131.         return lastG;
  132.     }

  133.     /** Reinitialize the beginning of the step.
  134.      * @param interpolator valid for the current step
  135.      * @exception MathIllegalStateException if the interpolator throws one because
  136.      * the number of functions evaluations is exceeded
  137.      */
  138.     public void reinitializeBegin(final ODEStateInterpolator interpolator)
  139.         throws MathIllegalStateException {

  140.         forward = interpolator.isForward();
  141.         final ODEStateAndDerivative s0 = interpolator.getPreviousState();
  142.         t0 = s0.getTime();
  143.         g0 = g(s0);
  144.         while (g0 == 0) {
  145.             // excerpt from MATH-421 issue:
  146.             // If an ODE solver is setup with an ODEEventHandler that return STOP
  147.             // when the even is triggered, the integrator stops (which is exactly
  148.             // the expected behavior). If however the user wants to restart the
  149.             // solver from the final state reached at the event with the same
  150.             // configuration (expecting the event to be triggered again at a
  151.             // later time), then the integrator may fail to start. It can get stuck
  152.             // at the previous event. The use case for the bug MATH-421 is fairly
  153.             // general, so events occurring exactly at start in the first step should
  154.             // be ignored. Some g functions may be zero for multiple adjacent values of t
  155.             // so keep skipping roots while g(t) is zero.

  156.             // extremely rare case: there is a zero EXACTLY at interval start
  157.             // we will use the sign slightly after step beginning to force ignoring this zero
  158.             double tStart = t0 + (forward ? 0.5 : -0.5) * solver.getAbsoluteAccuracy();
  159.             // check for case where tolerance is too small to make a difference
  160.             if (tStart == t0) {
  161.                 tStart = nextAfter(t0);
  162.             }
  163.             t0 = tStart;
  164.             g0 = g(interpolator.getInterpolatedState(tStart));
  165.         }
  166.         g0Positive = g0 > 0;
  167.         // "last" event was increasing
  168.         increasing = g0Positive;

  169.     }

  170.     /** {@inheritDoc} */
  171.     @Override
  172.     public boolean evaluateStep(final ODEStateInterpolator interpolator)
  173.             throws MathIllegalArgumentException, MathIllegalStateException {

  174.         forward = interpolator.isForward();
  175.         final ODEStateAndDerivative s0 = interpolator.getPreviousState();
  176.         final ODEStateAndDerivative s1 = interpolator.getCurrentState();
  177.         final double t1 = s1.getTime();
  178.         final double dt = t1 - t0;
  179.         if (FastMath.abs(dt) < solver.getAbsoluteAccuracy()) {
  180.             // we cannot do anything on such a small step, don't trigger any events
  181.             pendingEvent     = false;
  182.             pendingEventTime = Double.NaN;
  183.             return false;
  184.         }

  185.         double ta = t0;
  186.         double ga = g0;
  187.         for (ODEStateAndDerivative sb = nextCheck(s0, s1, interpolator);
  188.              sb != null;
  189.              sb = nextCheck(sb, s1, interpolator)) {

  190.             // evaluate handler value at the end of the substep
  191.             final double tb = sb.getTime();
  192.             final double gb = g(sb);

  193.             // check events occurrence
  194.             if (gb == 0.0 || (g0Positive ^ (gb > 0))) {
  195.                 // there is a sign change: an event is expected during this step
  196.                 if (findRoot(interpolator, ta, ga, tb, gb)) {
  197.                     return true;
  198.                 }
  199.             } else {
  200.                 // no sign change: there is no event for now
  201.                 ta = tb;
  202.                 ga = gb;
  203.             }

  204.         }

  205.         // no event during the whole step
  206.         pendingEvent     = false;
  207.         pendingEventTime = Double.NaN;
  208.         return false;

  209.     }

  210.     /** Estimate next state to check.
  211.      * @param done state already checked
  212.      * @param target target state towards which we are checking
  213.      * @param interpolator step interpolator for the proposed step
  214.      * @return intermediate state to check, or exactly {@code null}
  215.      * if we already have {@code done == target}
  216.      * @since 3.0
  217.      */
  218.     private ODEStateAndDerivative nextCheck(final ODEStateAndDerivative done, final ODEStateAndDerivative target,
  219.                                             final ODEStateInterpolator interpolator) {
  220.         if (done == target) {
  221.             // we have already reached target
  222.             return null;
  223.         } else {
  224.             // we have to select some intermediate state
  225.             // attempting to split the remaining time in an integer number of checks
  226.             final double dt       = target.getTime() - done.getTime();
  227.             final double maxCheck = detector.getMaxCheckInterval().currentInterval(done, dt >= 0.);
  228.             final int    n        = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt) / maxCheck));
  229.             return n == 1 ? target : interpolator.getInterpolatedState(done.getTime() + dt / n);
  230.         }
  231.     }

  232.     /**
  233.      * Find a root in a bracketing interval.
  234.      *
  235.      * <p> When calling this method one of the following must be true. Either ga == 0, gb
  236.      * == 0, (ga < 0  and gb > 0), or (ga > 0 and gb < 0).
  237.      *
  238.      * @param interpolator that covers the interval.
  239.      * @param ta           earliest possible time for root.
  240.      * @param ga           g(ta).
  241.      * @param tb           latest possible time for root.
  242.      * @param gb           g(tb).
  243.      * @return if a zero crossing was found.
  244.      */
  245.     private boolean findRoot(final ODEStateInterpolator interpolator,
  246.                              final double ta,
  247.                              final double ga,
  248.                              final double tb,
  249.                              final double gb) {
  250.         // check there appears to be a root in [ta, tb]
  251.         check(ga == 0.0 || gb == 0.0 || (ga > 0.0 && gb < 0.0) || (ga < 0.0 && gb > 0.0));

  252.         final int maxIterationCount = detector.getMaxIterationCount();
  253.         final UnivariateFunction f = t -> g(interpolator.getInterpolatedState(t));

  254.         // prepare loop below
  255.         double loopT = ta;
  256.         double loopG = ga;

  257.         // event time, just at or before the actual root.
  258.         double beforeRootT = Double.NaN;
  259.         double beforeRootG = Double.NaN;
  260.         // time on the other side of the root.
  261.         // Initialized the the loop below executes once.
  262.         double afterRootT = ta;
  263.         double afterRootG = 0.0;

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

  300.             }
  301.         }

  302.         // loop to skip through "fake" roots, i.e. where g(t) = g'(t) = 0.0
  303.         // executed once if we didn't hit a special case above
  304.         while ((afterRootG == 0.0 || afterRootG > 0.0 == g0Positive) &&
  305.                strictlyAfter(afterRootT, tb)) {
  306.             if (loopG == 0.0) {
  307.                 // ga == 0.0 and gb may or may not be 0.0
  308.                 // handle the root at ta first
  309.                 beforeRootT = loopT;
  310.                 beforeRootG = loopG;
  311.                 afterRootT = minTime(shiftedBy(beforeRootT, solver.getAbsoluteAccuracy()), tb);
  312.                 afterRootG = f.value(afterRootT);
  313.             } else {
  314.                 // both non-zero, the usual case, use a root finder.
  315.                 if (forward) {
  316.                     try {
  317.                         final Interval interval =
  318.                                         solver.solveInterval(maxIterationCount, f, loopT, tb);
  319.                         beforeRootT = interval.getLeftAbscissa();
  320.                         beforeRootG = interval.getLeftValue();
  321.                         afterRootT = interval.getRightAbscissa();
  322.                         afterRootG = interval.getRightValue();
  323.                         // CHECKSTYLE: stop IllegalCatch check
  324.                     } catch (RuntimeException e) { // NOPMD
  325.                         // CHECKSTYLE: resume IllegalCatch check
  326.                         throw new MathIllegalStateException(e, LocalizedODEFormats.FIND_ROOT,
  327.                                                             detector, loopT, loopG, tb, gb, lastT, lastG);
  328.                     }
  329.                 } else {
  330.                     try {
  331.                         final Interval interval =
  332.                                         solver.solveInterval(maxIterationCount, f, tb, loopT);
  333.                         beforeRootT = interval.getRightAbscissa();
  334.                         beforeRootG = interval.getRightValue();
  335.                         afterRootT = interval.getLeftAbscissa();
  336.                         afterRootG = interval.getLeftValue();
  337.                         // CHECKSTYLE: stop IllegalCatch check
  338.                     } catch (RuntimeException e) { // NOPMD
  339.                         // CHECKSTYLE: resume IllegalCatch check
  340.                         throw new MathIllegalStateException(e, LocalizedODEFormats.FIND_ROOT,
  341.                                                             detector, tb, gb, loopT, loopG, lastT, lastG);
  342.                     }
  343.                 }
  344.             }
  345.             // tolerance is set to less than 1 ulp
  346.             // assume tolerance is 1 ulp
  347.             if (beforeRootT == afterRootT) {
  348.                 afterRootT = nextAfter(afterRootT);
  349.                 afterRootG = f.value(afterRootT);
  350.             }
  351.             // check loop is making some progress
  352.             check((forward && afterRootT > beforeRootT) || (!forward && afterRootT < beforeRootT));
  353.             // setup next iteration
  354.             loopT = afterRootT;
  355.             loopG = afterRootG;
  356.         }

  357.         // figure out the result of root finding, and return accordingly
  358.         if (afterRootG == 0.0 || afterRootG > 0.0 == g0Positive) {
  359.             // loop gave up and didn't find any crossing within this step
  360.             return false;
  361.         } else {
  362.             // real crossing
  363.             check(!Double.isNaN(beforeRootT) && !Double.isNaN(beforeRootG));
  364.             // variation direction, with respect to the integration direction
  365.             increasing = !g0Positive;
  366.             pendingEventTime = beforeRootT;
  367.             stopTime = beforeRootG == 0.0 ? beforeRootT : afterRootT;
  368.             pendingEvent = true;
  369.             afterEvent = afterRootT;
  370.             afterG = afterRootG;

  371.             // check increasing set correctly
  372.             check(afterG > 0 == increasing);
  373.             check(increasing == gb >= ga);

  374.             return true;
  375.         }

  376.     }

  377.     /**
  378.      * Get the next number after the given number in the current propagation direction.
  379.      *
  380.      * @param t input time
  381.      * @return t +/- 1 ulp depending on the direction.
  382.      */
  383.     private double nextAfter(final double t) {
  384.         // direction
  385.         final double dir = forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
  386.         return FastMath.nextAfter(t, dir);
  387.     }

  388.     /** {@inheritDoc} */
  389.     @Override
  390.     public double getEventTime() {
  391.         return pendingEvent ?
  392.                pendingEventTime :
  393.                (forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY);
  394.     }

  395.     /**
  396.      * Try to accept the current history up to the given time.
  397.      *
  398.      * <p> It is not necessary to call this method before calling {@link
  399.      * #doEvent(ODEStateAndDerivative)} with the same state. It is necessary to call this
  400.      * method before you call {@link #doEvent(ODEStateAndDerivative)} on some other event
  401.      * detector.
  402.      *
  403.      * @param state        to try to accept.
  404.      * @param interpolator to use to find the new root, if any.
  405.      * @return if the event detector has an event it has not detected before that is on or
  406.      * before the same time as {@code state}. In other words {@code false} means continue
  407.      * on while {@code true} means stop and handle my event first.
  408.      */
  409.     public boolean tryAdvance(final ODEStateAndDerivative state,
  410.                               final ODEStateInterpolator interpolator) {
  411.         final double t = state.getTime();
  412.         // check this is only called before a pending event.
  413.         check(!pendingEvent || !strictlyAfter(pendingEventTime, t));

  414.         final boolean meFirst;

  415.         if (strictlyAfter(t, earliestTimeConsidered)) {
  416.             // just found an event and we know the next time we want to search again
  417.             meFirst = false;
  418.         } else {
  419.             // check g function to see if there is a new event
  420.             final double g = g(state);
  421.             final boolean positive = g > 0;

  422.             if (positive == g0Positive) {
  423.                 // g function has expected sign
  424.                 g0 = g; // g0Positive is the same
  425.                 meFirst = false;
  426.             } else {
  427.                 // found a root we didn't expect -> find precise location
  428.                 final double oldPendingEventTime = pendingEventTime;
  429.                 final boolean foundRoot = findRoot(interpolator, t0, g0, t, g);
  430.                 // make sure the new root is not the same as the old root, if one exists
  431.                 meFirst = foundRoot &&
  432.                           (Double.isNaN(oldPendingEventTime) || oldPendingEventTime != pendingEventTime);
  433.             }
  434.         }

  435.         if (!meFirst) {
  436.             // advance t0 to the current time so we can't find events that occur before t
  437.             t0 = t;
  438.         }

  439.         return meFirst;
  440.     }

  441.     /** {@inheritDoc} */
  442.     @Override
  443.     public EventOccurrence doEvent(final ODEStateAndDerivative state) {
  444.         // check event is pending and is at the same time
  445.         check(pendingEvent);
  446.         check(state.getTime() == this.pendingEventTime);

  447.         final Action action = handler.eventOccurred(state, detector, increasing == forward);
  448.         final ODEState newState;
  449.         if (action == Action.RESET_STATE) {
  450.             newState = handler.resetState(detector, state);
  451.         } else {
  452.             newState = state;
  453.         }
  454.         // clear pending event
  455.         pendingEvent = false;
  456.         pendingEventTime = Double.NaN;
  457.         // setup for next search
  458.         earliestTimeConsidered = afterEvent;
  459.         t0 = afterEvent;
  460.         g0 = afterG;
  461.         g0Positive = increasing;
  462.         // check g0Positive set correctly
  463.         check(g0 == 0.0 || g0Positive == (g0 > 0));
  464.         return new EventOccurrence(action, newState, stopTime);
  465.     }

  466.     /**
  467.      * Shift a time value along the current integration direction: {@link #forward}.
  468.      *
  469.      * @param t     the time to shift.
  470.      * @param delta the amount to shift.
  471.      * @return t + delta if forward, else t - delta. If the result has to be rounded it
  472.      * will be rounded to be before the true value of t + delta.
  473.      */
  474.     private double shiftedBy(final double t, final double delta) {
  475.         if (forward) {
  476.             final double ret = t + delta;
  477.             if (ret - t > delta) {
  478.                 return FastMath.nextDown(ret);
  479.             } else {
  480.                 return ret;
  481.             }
  482.         } else {
  483.             final double ret = t - delta;
  484.             if (t - ret > delta) {
  485.                 return FastMath.nextUp(ret);
  486.             } else {
  487.                 return ret;
  488.             }
  489.         }
  490.     }

  491.     /**
  492.      * Get the time that happens first along the current propagation direction: {@link
  493.      * #forward}.
  494.      *
  495.      * @param a first time
  496.      * @param b second time
  497.      * @return min(a, b) if forward, else max (a, b)
  498.      */
  499.     private double minTime(final double a, final double b) {
  500.         return forward ? FastMath.min(a, b) : FastMath.max(a, b);
  501.     }

  502.     /**
  503.      * Check the ordering of two times.
  504.      *
  505.      * @param t1 the first time.
  506.      * @param t2 the second time.
  507.      * @return true if {@code t2} is strictly after {@code t1} in the propagation
  508.      * direction.
  509.      */
  510.     private boolean strictlyAfter(final double t1, final double t2) {
  511.         return forward ? t1 < t2 : t2 < t1;
  512.     }

  513.     /**
  514.      * Same as keyword assert, but throw a {@link MathRuntimeException}.
  515.      *
  516.      * @param condition to check
  517.      * @throws MathRuntimeException if {@code condition} is false.
  518.      */
  519.     private void check(final boolean condition) throws MathRuntimeException {
  520.         if (!condition) {
  521.             throw MathRuntimeException.createInternalError();
  522.         }
  523.     }

  524. }