Home | History | Annotate | Download | only in events
      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 
     18 package org.apache.commons.math.ode.events;
     19 
     20 import org.apache.commons.math.ConvergenceException;
     21 import org.apache.commons.math.FunctionEvaluationException;
     22 import org.apache.commons.math.analysis.UnivariateRealFunction;
     23 import org.apache.commons.math.analysis.solvers.BrentSolver;
     24 import org.apache.commons.math.exception.MathInternalError;
     25 import org.apache.commons.math.ode.DerivativeException;
     26 import org.apache.commons.math.ode.sampling.StepInterpolator;
     27 import org.apache.commons.math.util.FastMath;
     28 
     29 /** This class handles the state for one {@link EventHandler
     30  * event handler} during integration steps.
     31  *
     32  * <p>Each time the integrator proposes a step, the event handler
     33  * switching function should be checked. This class handles the state
     34  * of one handler during one integration step, with references to the
     35  * state at the end of the preceding step. This information is used to
     36  * decide if the handler should trigger an event or not during the
     37  * proposed step (and hence the step should be reduced to ensure the
     38  * event occurs at a bound rather than inside the step).</p>
     39  *
     40  * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 fvr. 2011) $
     41  * @since 1.2
     42  */
     43 public class EventState {
     44 
     45     /** Event handler. */
     46     private final EventHandler handler;
     47 
     48     /** Maximal time interval between events handler checks. */
     49     private final double maxCheckInterval;
     50 
     51     /** Convergence threshold for event localization. */
     52     private final double convergence;
     53 
     54     /** Upper limit in the iteration count for event localization. */
     55     private final int maxIterationCount;
     56 
     57     /** Time at the beginning of the step. */
     58     private double t0;
     59 
     60     /** Value of the events handler at the beginning of the step. */
     61     private double g0;
     62 
     63     /** Simulated sign of g0 (we cheat when crossing events). */
     64     private boolean g0Positive;
     65 
     66     /** Indicator of event expected during the step. */
     67     private boolean pendingEvent;
     68 
     69     /** Occurrence time of the pending event. */
     70     private double pendingEventTime;
     71 
     72     /** Occurrence time of the previous event. */
     73     private double previousEventTime;
     74 
     75     /** Integration direction. */
     76     private boolean forward;
     77 
     78     /** Variation direction around pending event.
     79      *  (this is considered with respect to the integration direction)
     80      */
     81     private boolean increasing;
     82 
     83     /** Next action indicator. */
     84     private int nextAction;
     85 
     86     /** Simple constructor.
     87      * @param handler event handler
     88      * @param maxCheckInterval maximal time interval between switching
     89      * function checks (this interval prevents missing sign changes in
     90      * case the integration steps becomes very large)
     91      * @param convergence convergence threshold in the event time search
     92      * @param maxIterationCount upper limit of the iteration count in
     93      * the event time search
     94      */
     95     public EventState(final EventHandler handler, final double maxCheckInterval,
     96                       final double convergence, final int maxIterationCount) {
     97         this.handler           = handler;
     98         this.maxCheckInterval  = maxCheckInterval;
     99         this.convergence       = FastMath.abs(convergence);
    100         this.maxIterationCount = maxIterationCount;
    101 
    102         // some dummy values ...
    103         t0                = Double.NaN;
    104         g0                = Double.NaN;
    105         g0Positive        = true;
    106         pendingEvent      = false;
    107         pendingEventTime  = Double.NaN;
    108         previousEventTime = Double.NaN;
    109         increasing        = true;
    110         nextAction        = EventHandler.CONTINUE;
    111 
    112     }
    113 
    114     /** Get the underlying event handler.
    115      * @return underlying event handler
    116      */
    117     public EventHandler getEventHandler() {
    118         return handler;
    119     }
    120 
    121     /** Get the maximal time interval between events handler checks.
    122      * @return maximal time interval between events handler checks
    123      */
    124     public double getMaxCheckInterval() {
    125         return maxCheckInterval;
    126     }
    127 
    128     /** Get the convergence threshold for event localization.
    129      * @return convergence threshold for event localization
    130      */
    131     public double getConvergence() {
    132         return convergence;
    133     }
    134 
    135     /** Get the upper limit in the iteration count for event localization.
    136      * @return upper limit in the iteration count for event localization
    137      */
    138     public int getMaxIterationCount() {
    139         return maxIterationCount;
    140     }
    141 
    142     /** Reinitialize the beginning of the step.
    143      * @param interpolator valid for the current step
    144      * @exception EventException if the event handler
    145      * value cannot be evaluated at the beginning of the step
    146      */
    147     public void reinitializeBegin(final StepInterpolator interpolator)
    148         throws EventException {
    149         try {
    150             // excerpt from MATH-421 issue:
    151             // If an ODE solver is setup with an EventHandler that return STOP
    152             // when the even is triggered, the integrator stops (which is exactly
    153             // the expected behavior). If however the user want to restart the
    154             // solver from the final state reached at the event with the same
    155             // configuration (expecting the event to be triggered again at a
    156             // later time), then the integrator may fail to start. It can get stuck
    157             // at the previous event.
    158 
    159             // The use case for the bug MATH-421 is fairly general, so events occurring
    160             // less than epsilon after the solver start in the first step should be ignored,
    161             // where epsilon is the convergence threshold of the event. The sign of the g
    162             // function should be evaluated after this initial ignore zone, not exactly at
    163             // beginning (if there are no event at the very beginning g(t0) and g(t0+epsilon)
    164             // have the same sign, so this does not hurt ; if there is an event at the very
    165             // beginning, g(t0) and g(t0+epsilon) have opposite signs and we want to start
    166             // with the second one. Of course, the sign of epsilon depend on the integration
    167             // direction (forward or backward). This explains what is done below.
    168 
    169             final double ignoreZone = interpolator.isForward() ? getConvergence() : -getConvergence();
    170             t0 = interpolator.getPreviousTime() + ignoreZone;
    171             interpolator.setInterpolatedTime(t0);
    172             g0 = handler.g(t0, interpolator.getInterpolatedState());
    173             if (g0 == 0) {
    174                 // extremely rare case: there is a zero EXACTLY at end of ignore zone
    175                 // we will use the opposite of sign at step beginning to force ignoring this zero
    176                 final double tStart = interpolator.getPreviousTime();
    177                 interpolator.setInterpolatedTime(tStart);
    178                 g0Positive = handler.g(tStart, interpolator.getInterpolatedState()) <= 0;
    179             } else {
    180                 g0Positive = g0 >= 0;
    181             }
    182 
    183         } catch (DerivativeException mue) {
    184             throw new EventException(mue);
    185         }
    186     }
    187 
    188     /** Evaluate the impact of the proposed step on the event handler.
    189      * @param interpolator step interpolator for the proposed step
    190      * @return true if the event handler triggers an event before
    191      * the end of the proposed step
    192      * @exception DerivativeException if the interpolator fails to
    193      * compute the switching function somewhere within the step
    194      * @exception EventException if the switching function
    195      * cannot be evaluated
    196      * @exception ConvergenceException if an event cannot be located
    197      */
    198     public boolean evaluateStep(final StepInterpolator interpolator)
    199         throws DerivativeException, EventException, ConvergenceException {
    200 
    201         try {
    202 
    203             forward = interpolator.isForward();
    204             final double t1 = interpolator.getCurrentTime();
    205             if (FastMath.abs(t1 - t0) < convergence) {
    206                 // we cannot do anything on such a small step, don't trigger any events
    207                 return false;
    208             }
    209             final double start = forward ? (t0 + convergence) : t0 - convergence;
    210             final double dt    = t1 - start;
    211             final int    n     = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt) / maxCheckInterval));
    212             final double h     = dt / n;
    213 
    214             double ta = t0;
    215             double ga = g0;
    216             for (int i = 0; i < n; ++i) {
    217 
    218                 // evaluate handler value at the end of the substep
    219                 final double tb = start + (i + 1) * h;
    220                 interpolator.setInterpolatedTime(tb);
    221                 final double gb = handler.g(tb, interpolator.getInterpolatedState());
    222 
    223                 // check events occurrence
    224                 if (g0Positive ^ (gb >= 0)) {
    225                     // there is a sign change: an event is expected during this step
    226 
    227                     // variation direction, with respect to the integration direction
    228                     increasing = gb >= ga;
    229 
    230                     final UnivariateRealFunction f = new UnivariateRealFunction() {
    231                         public double value(final double t) {
    232                             try {
    233                                 interpolator.setInterpolatedTime(t);
    234                                 return handler.g(t, interpolator.getInterpolatedState());
    235                             } catch (DerivativeException e) {
    236                                 throw new EmbeddedDerivativeException(e);
    237                             } catch (EventException e) {
    238                                 throw new EmbeddedEventException(e);
    239                             }
    240                         }
    241                     };
    242                     final BrentSolver solver = new BrentSolver(convergence);
    243 
    244                     if (ga * gb >= 0) {
    245                         // this is a corner case:
    246                         // - there was an event near ta,
    247                         // - there is another event between ta and tb
    248                         // - when ta was computed, convergence was reached on the "wrong side" of the interval
    249                         // this implies that the real sign of ga is the same as gb, so we need to slightly
    250                         // shift ta to make sure ga and gb get opposite signs and the solver won't complain
    251                         // about bracketing
    252                         final double epsilon = (forward ? 0.25 : -0.25) * convergence;
    253                         for (int k = 0; (k < 4) && (ga * gb > 0); ++k) {
    254                             ta += epsilon;
    255                             try {
    256                                 ga = f.value(ta);
    257                             } catch (FunctionEvaluationException ex) {
    258                                 throw new DerivativeException(ex);
    259                             }
    260                         }
    261                         if (ga * gb > 0) {
    262                             // this should never happen
    263                             throw new MathInternalError();
    264                         }
    265                     }
    266 
    267                     final double root;
    268                     try {
    269                         root = (ta <= tb) ?
    270                                 solver.solve(maxIterationCount, f, ta, tb) :
    271                                     solver.solve(maxIterationCount, f, tb, ta);
    272                     } catch (FunctionEvaluationException ex) {
    273                         throw new DerivativeException(ex);
    274                     }
    275 
    276                     if ((!Double.isNaN(previousEventTime)) &&
    277                         (FastMath.abs(root - ta) <= convergence) &&
    278                         (FastMath.abs(root - previousEventTime) <= convergence)) {
    279                         // we have either found nothing or found (again ?) a past event, we simply ignore it
    280                         ta = tb;
    281                         ga = gb;
    282                     } else if (Double.isNaN(previousEventTime) ||
    283                                (FastMath.abs(previousEventTime - root) > convergence)) {
    284                         pendingEventTime = root;
    285                         pendingEvent = true;
    286                         return true;
    287                     } else {
    288                         // no sign change: there is no event for now
    289                         ta = tb;
    290                         ga = gb;
    291                     }
    292 
    293                 } else {
    294                     // no sign change: there is no event for now
    295                     ta = tb;
    296                     ga = gb;
    297                 }
    298 
    299             }
    300 
    301             // no event during the whole step
    302             pendingEvent     = false;
    303             pendingEventTime = Double.NaN;
    304             return false;
    305 
    306         } catch (EmbeddedDerivativeException ede) {
    307             throw ede.getDerivativeException();
    308         } catch (EmbeddedEventException eee) {
    309             throw eee.getEventException();
    310         }
    311 
    312     }
    313 
    314     /** Get the occurrence time of the event triggered in the current step.
    315      * @return occurrence time of the event triggered in the current
    316      * step or positive infinity if no events are triggered
    317      */
    318     public double getEventTime() {
    319         return pendingEvent ? pendingEventTime : Double.POSITIVE_INFINITY;
    320     }
    321 
    322     /** Acknowledge the fact the step has been accepted by the integrator.
    323      * @param t value of the independent <i>time</i> variable at the
    324      * end of the step
    325      * @param y array containing the current value of the state vector
    326      * at the end of the step
    327      * @exception EventException if the value of the event
    328      * handler cannot be evaluated
    329      */
    330     public void stepAccepted(final double t, final double[] y)
    331         throws EventException {
    332 
    333         t0 = t;
    334         g0 = handler.g(t, y);
    335 
    336         if (pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence)) {
    337             // force the sign to its value "just after the event"
    338             previousEventTime = t;
    339             g0Positive        = increasing;
    340             nextAction        = handler.eventOccurred(t, y, !(increasing ^ forward));
    341         } else {
    342             g0Positive = g0 >= 0;
    343             nextAction = EventHandler.CONTINUE;
    344         }
    345     }
    346 
    347     /** Check if the integration should be stopped at the end of the
    348      * current step.
    349      * @return true if the integration should be stopped
    350      */
    351     public boolean stop() {
    352         return nextAction == EventHandler.STOP;
    353     }
    354 
    355     /** Let the event handler reset the state if it wants.
    356      * @param t value of the independent <i>time</i> variable at the
    357      * beginning of the next step
    358      * @param y array were to put the desired state vector at the beginning
    359      * of the next step
    360      * @return true if the integrator should reset the derivatives too
    361      * @exception EventException if the state cannot be reseted by the event
    362      * handler
    363      */
    364     public boolean reset(final double t, final double[] y)
    365         throws EventException {
    366 
    367         if (!(pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence))) {
    368             return false;
    369         }
    370 
    371         if (nextAction == EventHandler.RESET_STATE) {
    372             handler.resetState(t, y);
    373         }
    374         pendingEvent      = false;
    375         pendingEventTime  = Double.NaN;
    376 
    377         return (nextAction == EventHandler.RESET_STATE) ||
    378                (nextAction == EventHandler.RESET_DERIVATIVES);
    379 
    380     }
    381 
    382     /** Local exception for embedding DerivativeException. */
    383     private static class EmbeddedDerivativeException extends RuntimeException {
    384 
    385         /** Serializable UID. */
    386         private static final long serialVersionUID = 3574188382434584610L;
    387 
    388         /** Embedded exception. */
    389         private final DerivativeException derivativeException;
    390 
    391         /** Simple constructor.
    392          * @param derivativeException embedded exception
    393          */
    394         public EmbeddedDerivativeException(final DerivativeException derivativeException) {
    395             this.derivativeException = derivativeException;
    396         }
    397 
    398         /** Get the embedded exception.
    399          * @return embedded exception
    400          */
    401         public DerivativeException getDerivativeException() {
    402             return derivativeException;
    403         }
    404 
    405     }
    406 
    407     /** Local exception for embedding EventException. */
    408     private static class EmbeddedEventException extends RuntimeException {
    409 
    410         /** Serializable UID. */
    411         private static final long serialVersionUID = -1337749250090455474L;
    412 
    413         /** Embedded exception. */
    414         private final EventException eventException;
    415 
    416         /** Simple constructor.
    417          * @param eventException embedded exception
    418          */
    419         public EmbeddedEventException(final EventException eventException) {
    420             this.eventException = eventException;
    421         }
    422 
    423         /** Get the embedded exception.
    424          * @return embedded exception
    425          */
    426         public EventException getEventException() {
    427             return eventException;
    428         }
    429 
    430     }
    431 }
    432