EventState.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.apache.commons.math4.legacy.ode.events;

  18. import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
  19. import org.apache.commons.math4.legacy.analysis.solvers.AllowedSolution;
  20. import org.apache.commons.math4.legacy.analysis.solvers.BracketedUnivariateSolver;
  21. import org.apache.commons.math4.legacy.analysis.solvers.PegasusSolver;
  22. import org.apache.commons.math4.legacy.analysis.solvers.UnivariateSolver;
  23. import org.apache.commons.math4.legacy.analysis.solvers.UnivariateSolverUtils;
  24. import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
  25. import org.apache.commons.math4.legacy.exception.NoBracketingException;
  26. import org.apache.commons.math4.legacy.ode.EquationsMapper;
  27. import org.apache.commons.math4.legacy.ode.ExpandableStatefulODE;
  28. import org.apache.commons.math4.legacy.ode.sampling.StepInterpolator;
  29. import org.apache.commons.math4.core.jdkmath.JdkMath;

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

  43.     /** Event handler. */
  44.     private final EventHandler handler;

  45.     /** Maximal time interval between events handler checks. */
  46.     private final double maxCheckInterval;

  47.     /** Convergence threshold for event localization. */
  48.     private final double convergence;

  49.     /** Upper limit in the iteration count for event localization. */
  50.     private final int maxIterationCount;

  51.     /** Equation being integrated. */
  52.     private ExpandableStatefulODE expandable;

  53.     /** Time at the beginning of the step. */
  54.     private double t0;

  55.     /** Value of the events handler at the beginning of the step. */
  56.     private double g0;

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

  59.     /** Indicator of event expected during the step. */
  60.     private boolean pendingEvent;

  61.     /** Occurrence time of the pending event. */
  62.     private double pendingEventTime;

  63.     /** Occurrence time of the previous event. */
  64.     private double previousEventTime;

  65.     /** Integration direction. */
  66.     private boolean forward;

  67.     /** Variation direction around pending event.
  68.      *  (this is considered with respect to the integration direction)
  69.      */
  70.     private boolean increasing;

  71.     /** Next action indicator. */
  72.     private EventHandler.Action nextAction;

  73.     /** Root-finding algorithm to use to detect state events. */
  74.     private final UnivariateSolver solver;

  75.     /** Simple constructor.
  76.      * @param handler event handler
  77.      * @param maxCheckInterval maximal time interval between switching
  78.      * function checks (this interval prevents missing sign changes in
  79.      * case the integration steps becomes very large)
  80.      * @param convergence convergence threshold in the event time search
  81.      * @param maxIterationCount upper limit of the iteration count in
  82.      * the event time search
  83.      * @param solver Root-finding algorithm to use to detect state events
  84.      */
  85.     public EventState(final EventHandler handler, final double maxCheckInterval,
  86.                       final double convergence, final int maxIterationCount,
  87.                       final UnivariateSolver solver) {
  88.         this.handler           = handler;
  89.         this.maxCheckInterval  = maxCheckInterval;
  90.         this.convergence       = JdkMath.abs(convergence);
  91.         this.maxIterationCount = maxIterationCount;
  92.         this.solver            = solver;

  93.         // some dummy values ...
  94.         expandable        = null;
  95.         t0                = Double.NaN;
  96.         g0                = Double.NaN;
  97.         g0Positive        = true;
  98.         pendingEvent      = false;
  99.         pendingEventTime  = Double.NaN;
  100.         previousEventTime = Double.NaN;
  101.         increasing        = true;
  102.         nextAction        = EventHandler.Action.CONTINUE;
  103.     }

  104.     /** Get the underlying event handler.
  105.      * @return underlying event handler
  106.      */
  107.     public EventHandler getEventHandler() {
  108.         return handler;
  109.     }

  110.     /** Set the equation.
  111.      * @param expandable equation being integrated
  112.      */
  113.     public void setExpandable(final ExpandableStatefulODE expandable) {
  114.         this.expandable = expandable;
  115.     }

  116.     /** Get the maximal time interval between events handler checks.
  117.      * @return maximal time interval between events handler checks
  118.      */
  119.     public double getMaxCheckInterval() {
  120.         return maxCheckInterval;
  121.     }

  122.     /** Get the convergence threshold for event localization.
  123.      * @return convergence threshold for event localization
  124.      */
  125.     public double getConvergence() {
  126.         return convergence;
  127.     }

  128.     /** Get the upper limit in the iteration count for event localization.
  129.      * @return upper limit in the iteration count for event localization
  130.      */
  131.     public int getMaxIterationCount() {
  132.         return maxIterationCount;
  133.     }

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

  141.         t0 = interpolator.getPreviousTime();
  142.         interpolator.setInterpolatedTime(t0);
  143.         g0 = handler.g(t0, getCompleteState(interpolator));
  144.         if (g0 == 0) {
  145.             // excerpt from MATH-421 issue:
  146.             // If an ODE solver is setup with an EventHandler 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.

  155.             // extremely rare case: there is a zero EXACTLY at interval start
  156.             // we will use the sign slightly after step beginning to force ignoring this zero
  157.             final double epsilon = JdkMath.max(solver.getAbsoluteAccuracy(),
  158.                                                 JdkMath.abs(solver.getRelativeAccuracy() * t0));
  159.             final double tStart = t0 + 0.5 * epsilon;
  160.             interpolator.setInterpolatedTime(tStart);
  161.             g0 = handler.g(tStart, getCompleteState(interpolator));
  162.         }
  163.         g0Positive = g0 >= 0;
  164.     }

  165.     /** Get the complete state (primary and secondary).
  166.      * @param interpolator interpolator to use
  167.      * @return complete state
  168.      */
  169.     private double[] getCompleteState(final StepInterpolator interpolator) {

  170.         final double[] complete = new double[expandable.getTotalDimension()];

  171.         expandable.getPrimaryMapper().insertEquationData(interpolator.getInterpolatedState(),
  172.                                                          complete);
  173.         int index = 0;
  174.         for (EquationsMapper secondary : expandable.getSecondaryMappers()) {
  175.             secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index++),
  176.                                          complete);
  177.         }

  178.         return complete;
  179.     }

  180.     /** Evaluate the impact of the proposed step on the event handler.
  181.      * @param interpolator step interpolator for the proposed step
  182.      * @return true if the event handler triggers an event before
  183.      * the end of the proposed step
  184.      * @exception MaxCountExceededException if the interpolator throws one because
  185.      * the number of functions evaluations is exceeded
  186.      * @exception NoBracketingException if the event cannot be bracketed
  187.      */
  188.     public boolean evaluateStep(final StepInterpolator interpolator)
  189.         throws MaxCountExceededException, NoBracketingException {

  190.         try {
  191.             forward = interpolator.isForward();
  192.             final double t1 = interpolator.getCurrentTime();
  193.             final double dt = t1 - t0;
  194.             if (JdkMath.abs(dt) < convergence) {
  195.                 // we cannot do anything on such a small step, don't trigger any events
  196.                 return false;
  197.             }
  198.             final int    n = JdkMath.max(1, (int) JdkMath.ceil(JdkMath.abs(dt) / maxCheckInterval));
  199.             final double h = dt / n;

  200.             final UnivariateFunction f = new UnivariateFunction() {
  201.                 /** {@inheritDoc} */
  202.                 @Override
  203.                 public double value(final double t) throws LocalMaxCountExceededException {
  204.                     try {
  205.                         interpolator.setInterpolatedTime(t);
  206.                         return handler.g(t, getCompleteState(interpolator));
  207.                     } catch (MaxCountExceededException mcee) {
  208.                         throw new LocalMaxCountExceededException(mcee);
  209.                     }
  210.                 }
  211.             };

  212.             double ta = t0;
  213.             double ga = g0;
  214.             for (int i = 0; i < n; ++i) {

  215.                 // evaluate handler value at the end of the substep
  216.                 final double tb = (i == n - 1) ? t1 : t0 + (i + 1) * h;
  217.                 interpolator.setInterpolatedTime(tb);
  218.                 final double gb = handler.g(tb, getCompleteState(interpolator));

  219.                 // check events occurrence
  220.                 if (g0Positive ^ (gb >= 0)) {
  221.                     // there is a sign change: an event is expected during this step

  222.                     // variation direction, with respect to the integration direction
  223.                     increasing = gb >= ga;

  224.                     // find the event time making sure we select a solution just at or past the exact root
  225.                     final double root;
  226.                     if (solver instanceof BracketedUnivariateSolver<?>) {
  227.                         @SuppressWarnings("unchecked")
  228.                         BracketedUnivariateSolver<UnivariateFunction> bracketing =
  229.                                 (BracketedUnivariateSolver<UnivariateFunction>) solver;
  230.                         root = forward ?
  231.                                bracketing.solve(maxIterationCount, f, ta, tb, AllowedSolution.RIGHT_SIDE) :
  232.                                bracketing.solve(maxIterationCount, f, tb, ta, AllowedSolution.LEFT_SIDE);
  233.                     } else {
  234.                         final double baseRoot = forward ?
  235.                                                 solver.solve(maxIterationCount, f, ta, tb) :
  236.                                                 solver.solve(maxIterationCount, f, tb, ta);
  237.                         final int remainingEval = maxIterationCount - solver.getEvaluations();
  238.                         BracketedUnivariateSolver<UnivariateFunction> bracketing =
  239.                                 new PegasusSolver(solver.getRelativeAccuracy(), solver.getAbsoluteAccuracy());
  240.                         root = forward ?
  241.                                UnivariateSolverUtils.forceSide(remainingEval, f, bracketing,
  242.                                                                    baseRoot, ta, tb, AllowedSolution.RIGHT_SIDE) :
  243.                                UnivariateSolverUtils.forceSide(remainingEval, f, bracketing,
  244.                                                                    baseRoot, tb, ta, AllowedSolution.LEFT_SIDE);
  245.                     }

  246.                     if (!Double.isNaN(previousEventTime) &&
  247.                         JdkMath.abs(root - ta) <= convergence &&
  248.                         JdkMath.abs(root - previousEventTime) <= convergence) {
  249.                         // we have either found nothing or found (again ?) a past event,
  250.                         // retry the substep excluding this value, and taking care to have the
  251.                         // required sign in case the g function is noisy around its zero and
  252.                         // crosses the axis several times
  253.                         do {
  254.                             ta = forward ? ta + convergence : ta - convergence;
  255.                             ga = f.value(ta);
  256.                         } while ((g0Positive ^ (ga >= 0)) && (forward ^ (ta >= tb)));

  257.                         if (forward ^ (ta >= tb)) {
  258.                             // we were able to skip this spurious root
  259.                             --i;
  260.                         } else {
  261.                             // we can't avoid this root before the end of the step,
  262.                             // we have to handle it despite it is close to the former one
  263.                             // maybe we have two very close roots
  264.                             pendingEventTime = root;
  265.                             pendingEvent = true;
  266.                             return true;
  267.                         }
  268.                     } else if (Double.isNaN(previousEventTime) ||
  269.                                JdkMath.abs(previousEventTime - root) > convergence) {
  270.                         pendingEventTime = root;
  271.                         pendingEvent = true;
  272.                         return true;
  273.                     } else {
  274.                         // no sign change: there is no event for now
  275.                         ta = tb;
  276.                         ga = gb;
  277.                     }
  278.                 } else {
  279.                     // no sign change: there is no event for now
  280.                     ta = tb;
  281.                     ga = gb;
  282.                 }
  283.             }

  284.             // no event during the whole step
  285.             pendingEvent     = false;
  286.             pendingEventTime = Double.NaN;
  287.             return false;
  288.         } catch (LocalMaxCountExceededException lmcee) {
  289.             throw lmcee.getException();
  290.         }
  291.     }

  292.     /** Get the occurrence time of the event triggered in the current step.
  293.      * @return occurrence time of the event triggered in the current
  294.      * step or infinity if no events are triggered
  295.      */
  296.     public double getEventTime() {
  297.         return pendingEvent ?
  298.                pendingEventTime :
  299.                (forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY);
  300.     }

  301.     /** Acknowledge the fact the step has been accepted by the integrator.
  302.      * @param t value of the independent <i>time</i> variable at the
  303.      * end of the step
  304.      * @param y array containing the current value of the state vector
  305.      * at the end of the step
  306.      */
  307.     public void stepAccepted(final double t, final double[] y) {

  308.         t0 = t;
  309.         g0 = handler.g(t, y);

  310.         if (pendingEvent && JdkMath.abs(pendingEventTime - t) <= convergence) {
  311.             // force the sign to its value "just after the event"
  312.             previousEventTime = t;
  313.             g0Positive        = increasing;
  314.             nextAction        = handler.eventOccurred(t, y, increasing == forward);
  315.         } else {
  316.             g0Positive = g0 >= 0;
  317.             nextAction = EventHandler.Action.CONTINUE;
  318.         }
  319.     }

  320.     /** Check if the integration should be stopped at the end of the
  321.      * current step.
  322.      * @return true if the integration should be stopped
  323.      */
  324.     public boolean stop() {
  325.         return nextAction == EventHandler.Action.STOP;
  326.     }

  327.     /** Let the event handler reset the state if it wants.
  328.      * @param t value of the independent <i>time</i> variable at the
  329.      * beginning of the next step
  330.      * @param y array were to put the desired state vector at the beginning
  331.      * of the next step
  332.      * @return true if the integrator should reset the derivatives too
  333.      */
  334.     public boolean reset(final double t, final double[] y) {

  335.         if (!(pendingEvent && JdkMath.abs(pendingEventTime - t) <= convergence)) {
  336.             return false;
  337.         }

  338.         if (nextAction == EventHandler.Action.RESET_STATE) {
  339.             handler.resetState(t, y);
  340.         }
  341.         pendingEvent      = false;
  342.         pendingEventTime  = Double.NaN;

  343.         return nextAction == EventHandler.Action.RESET_STATE ||
  344.                nextAction == EventHandler.Action.RESET_DERIVATIVES;
  345.     }

  346.     /** Local wrapper to propagate exceptions. */
  347.     private static final class LocalMaxCountExceededException extends RuntimeException {

  348.         /** Serializable UID. */
  349.         private static final long serialVersionUID = 20120901L;

  350.         /** Wrapped exception. */
  351.         private final MaxCountExceededException wrapped;

  352.         /** Simple constructor.
  353.          * @param exception exception to wrap
  354.          */
  355.         LocalMaxCountExceededException(final MaxCountExceededException exception) {
  356.             wrapped = exception;
  357.         }

  358.         /** Get the wrapped exception.
  359.          * @return wrapped exception
  360.          */
  361.         public MaxCountExceededException getException() {
  362.             return wrapped;
  363.         }
  364.     }
  365. }