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
18package org.apache.commons.math.ode.events;
19
20import org.apache.commons.math.ConvergenceException;
21import org.apache.commons.math.FunctionEvaluationException;
22import org.apache.commons.math.analysis.UnivariateRealFunction;
23import org.apache.commons.math.analysis.solvers.BrentSolver;
24import org.apache.commons.math.exception.MathInternalError;
25import org.apache.commons.math.ode.DerivativeException;
26import org.apache.commons.math.ode.sampling.StepInterpolator;
27import 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 févr. 2011) $
41 * @since 1.2
42 */
43public 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