001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.math3.ode.events;
019    
020    import org.apache.commons.math3.analysis.UnivariateFunction;
021    import org.apache.commons.math3.analysis.solvers.AllowedSolution;
022    import org.apache.commons.math3.analysis.solvers.BracketedUnivariateSolver;
023    import org.apache.commons.math3.analysis.solvers.PegasusSolver;
024    import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
025    import org.apache.commons.math3.analysis.solvers.UnivariateSolverUtils;
026    import org.apache.commons.math3.exception.MaxCountExceededException;
027    import org.apache.commons.math3.exception.NoBracketingException;
028    import org.apache.commons.math3.ode.sampling.StepInterpolator;
029    import org.apache.commons.math3.util.FastMath;
030    
031    /** This class handles the state for one {@link EventHandler
032     * event handler} during integration steps.
033     *
034     * <p>Each time the integrator proposes a step, the event handler
035     * switching function should be checked. This class handles the state
036     * of one handler during one integration step, with references to the
037     * state at the end of the preceding step. This information is used to
038     * decide if the handler should trigger an event or not during the
039     * proposed step.</p>
040     *
041     * @version $Id: EventState.java 1416643 2012-12-03 19:37:14Z tn $
042     * @since 1.2
043     */
044    public class EventState {
045    
046        /** Event handler. */
047        private final EventHandler handler;
048    
049        /** Maximal time interval between events handler checks. */
050        private final double maxCheckInterval;
051    
052        /** Convergence threshold for event localization. */
053        private final double convergence;
054    
055        /** Upper limit in the iteration count for event localization. */
056        private final int maxIterationCount;
057    
058        /** Time at the beginning of the step. */
059        private double t0;
060    
061        /** Value of the events handler at the beginning of the step. */
062        private double g0;
063    
064        /** Simulated sign of g0 (we cheat when crossing events). */
065        private boolean g0Positive;
066    
067        /** Indicator of event expected during the step. */
068        private boolean pendingEvent;
069    
070        /** Occurrence time of the pending event. */
071        private double pendingEventTime;
072    
073        /** Occurrence time of the previous event. */
074        private double previousEventTime;
075    
076        /** Integration direction. */
077        private boolean forward;
078    
079        /** Variation direction around pending event.
080         *  (this is considered with respect to the integration direction)
081         */
082        private boolean increasing;
083    
084        /** Next action indicator. */
085        private EventHandler.Action nextAction;
086    
087        /** Root-finding algorithm to use to detect state events. */
088        private final UnivariateSolver solver;
089    
090        /** Simple constructor.
091         * @param handler event handler
092         * @param maxCheckInterval maximal time interval between switching
093         * function checks (this interval prevents missing sign changes in
094         * case the integration steps becomes very large)
095         * @param convergence convergence threshold in the event time search
096         * @param maxIterationCount upper limit of the iteration count in
097         * the event time search
098         * @param solver Root-finding algorithm to use to detect state events
099         */
100        public EventState(final EventHandler handler, final double maxCheckInterval,
101                          final double convergence, final int maxIterationCount,
102                          final UnivariateSolver solver) {
103            this.handler           = handler;
104            this.maxCheckInterval  = maxCheckInterval;
105            this.convergence       = FastMath.abs(convergence);
106            this.maxIterationCount = maxIterationCount;
107            this.solver            = solver;
108    
109            // some dummy values ...
110            t0                = Double.NaN;
111            g0                = Double.NaN;
112            g0Positive        = true;
113            pendingEvent      = false;
114            pendingEventTime  = Double.NaN;
115            previousEventTime = Double.NaN;
116            increasing        = true;
117            nextAction        = EventHandler.Action.CONTINUE;
118    
119        }
120    
121        /** Get the underlying event handler.
122         * @return underlying event handler
123         */
124        public EventHandler getEventHandler() {
125            return handler;
126        }
127    
128        /** Get the maximal time interval between events handler checks.
129         * @return maximal time interval between events handler checks
130         */
131        public double getMaxCheckInterval() {
132            return maxCheckInterval;
133        }
134    
135        /** Get the convergence threshold for event localization.
136         * @return convergence threshold for event localization
137         */
138        public double getConvergence() {
139            return convergence;
140        }
141    
142        /** Get the upper limit in the iteration count for event localization.
143         * @return upper limit in the iteration count for event localization
144         */
145        public int getMaxIterationCount() {
146            return maxIterationCount;
147        }
148    
149        /** Reinitialize the beginning of the step.
150         * @param interpolator valid for the current step
151         * @exception MaxCountExceededException if the interpolator throws one because
152         * the number of functions evaluations is exceeded
153         */
154        public void reinitializeBegin(final StepInterpolator interpolator)
155            throws MaxCountExceededException {
156    
157            t0 = interpolator.getPreviousTime();
158            interpolator.setInterpolatedTime(t0);
159            g0 = handler.g(t0, interpolator.getInterpolatedState());
160            if (g0 == 0) {
161                // excerpt from MATH-421 issue:
162                // If an ODE solver is setup with an EventHandler that return STOP
163                // when the even is triggered, the integrator stops (which is exactly
164                // the expected behavior). If however the user wants to restart the
165                // solver from the final state reached at the event with the same
166                // configuration (expecting the event to be triggered again at a
167                // later time), then the integrator may fail to start. It can get stuck
168                // at the previous event. The use case for the bug MATH-421 is fairly
169                // general, so events occurring exactly at start in the first step should
170                // be ignored.
171    
172                // extremely rare case: there is a zero EXACTLY at interval start
173                // we will use the sign slightly after step beginning to force ignoring this zero
174                final double epsilon = FastMath.max(solver.getAbsoluteAccuracy(),
175                                                    FastMath.abs(solver.getRelativeAccuracy() * t0));
176                final double tStart = t0 + 0.5 * epsilon;
177                interpolator.setInterpolatedTime(tStart);
178                g0 = handler.g(tStart, interpolator.getInterpolatedState());
179            }
180            g0Positive = g0 >= 0;
181    
182        }
183    
184        /** Evaluate the impact of the proposed step on the event handler.
185         * @param interpolator step interpolator for the proposed step
186         * @return true if the event handler triggers an event before
187         * the end of the proposed step
188         * @exception MaxCountExceededException if the interpolator throws one because
189         * the number of functions evaluations is exceeded
190         * @exception NoBracketingException if the event cannot be bracketed
191         */
192        public boolean evaluateStep(final StepInterpolator interpolator)
193            throws MaxCountExceededException, NoBracketingException {
194    
195            try {
196                forward = interpolator.isForward();
197                final double t1 = interpolator.getCurrentTime();
198                final double dt = t1 - t0;
199                if (FastMath.abs(dt) < convergence) {
200                    // we cannot do anything on such a small step, don't trigger any events
201                    return false;
202                }
203                final int    n = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt) / maxCheckInterval));
204                final double h = dt / n;
205    
206                final UnivariateFunction f = new UnivariateFunction() {
207                    public double value(final double t) throws LocalMaxCountExceededException {
208                        try {
209                            interpolator.setInterpolatedTime(t);
210                            return handler.g(t, interpolator.getInterpolatedState());
211                        } catch (MaxCountExceededException mcee) {
212                            throw new LocalMaxCountExceededException(mcee);
213                        }
214                    }
215                };
216    
217                double ta = t0;
218                double ga = g0;
219                for (int i = 0; i < n; ++i) {
220    
221                    // evaluate handler value at the end of the substep
222                    final double tb = t0 + (i + 1) * h;
223                    interpolator.setInterpolatedTime(tb);
224                    final double gb = handler.g(tb, interpolator.getInterpolatedState());
225    
226                    // check events occurrence
227                    if (g0Positive ^ (gb >= 0)) {
228                        // there is a sign change: an event is expected during this step
229    
230                        // variation direction, with respect to the integration direction
231                        increasing = gb >= ga;
232    
233                        // find the event time making sure we select a solution just at or past the exact root
234                        final double root;
235                        if (solver instanceof BracketedUnivariateSolver<?>) {
236                            @SuppressWarnings("unchecked")
237                            BracketedUnivariateSolver<UnivariateFunction> bracketing =
238                                    (BracketedUnivariateSolver<UnivariateFunction>) solver;
239                            root = forward ?
240                                   bracketing.solve(maxIterationCount, f, ta, tb, AllowedSolution.RIGHT_SIDE) :
241                                   bracketing.solve(maxIterationCount, f, tb, ta, AllowedSolution.LEFT_SIDE);
242                        } else {
243                            final double baseRoot = forward ?
244                                                    solver.solve(maxIterationCount, f, ta, tb) :
245                                                    solver.solve(maxIterationCount, f, tb, ta);
246                            final int remainingEval = maxIterationCount - solver.getEvaluations();
247                            BracketedUnivariateSolver<UnivariateFunction> bracketing =
248                                    new PegasusSolver(solver.getRelativeAccuracy(), solver.getAbsoluteAccuracy());
249                            root = forward ?
250                                   UnivariateSolverUtils.forceSide(remainingEval, f, bracketing,
251                                                                       baseRoot, ta, tb, AllowedSolution.RIGHT_SIDE) :
252                                   UnivariateSolverUtils.forceSide(remainingEval, f, bracketing,
253                                                                       baseRoot, tb, ta, AllowedSolution.LEFT_SIDE);
254                        }
255    
256                        if ((!Double.isNaN(previousEventTime)) &&
257                            (FastMath.abs(root - ta) <= convergence) &&
258                            (FastMath.abs(root - previousEventTime) <= convergence)) {
259                            // we have either found nothing or found (again ?) a past event,
260                            // retry the substep excluding this value
261                            ta = forward ? ta + convergence : ta - convergence;
262                            ga = f.value(ta);
263                            --i;
264                        } else if (Double.isNaN(previousEventTime) ||
265                                   (FastMath.abs(previousEventTime - root) > convergence)) {
266                            pendingEventTime = root;
267                            pendingEvent = true;
268                            return true;
269                        } else {
270                            // no sign change: there is no event for now
271                            ta = tb;
272                            ga = gb;
273                        }
274    
275                    } else {
276                        // no sign change: there is no event for now
277                        ta = tb;
278                        ga = gb;
279                    }
280    
281                }
282    
283                // no event during the whole step
284                pendingEvent     = false;
285                pendingEventTime = Double.NaN;
286                return false;
287    
288            } catch (LocalMaxCountExceededException lmcee) {
289                throw lmcee.getException();
290            }
291    
292        }
293    
294        /** Get the occurrence time of the event triggered in the current step.
295         * @return occurrence time of the event triggered in the current
296         * step or infinity if no events are triggered
297         */
298        public double getEventTime() {
299            return pendingEvent ?
300                   pendingEventTime :
301                   (forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY);
302        }
303    
304        /** Acknowledge the fact the step has been accepted by the integrator.
305         * @param t value of the independent <i>time</i> variable at the
306         * end of the step
307         * @param y array containing the current value of the state vector
308         * at the end of the step
309         */
310        public void stepAccepted(final double t, final double[] y) {
311    
312            t0 = t;
313            g0 = handler.g(t, y);
314    
315            if (pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence)) {
316                // force the sign to its value "just after the event"
317                previousEventTime = t;
318                g0Positive        = increasing;
319                nextAction        = handler.eventOccurred(t, y, !(increasing ^ forward));
320            } else {
321                g0Positive = g0 >= 0;
322                nextAction = EventHandler.Action.CONTINUE;
323            }
324        }
325    
326        /** Check if the integration should be stopped at the end of the
327         * current step.
328         * @return true if the integration should be stopped
329         */
330        public boolean stop() {
331            return nextAction == EventHandler.Action.STOP;
332        }
333    
334        /** Let the event handler reset the state if it wants.
335         * @param t value of the independent <i>time</i> variable at the
336         * beginning of the next step
337         * @param y array were to put the desired state vector at the beginning
338         * of the next step
339         * @return true if the integrator should reset the derivatives too
340         */
341        public boolean reset(final double t, final double[] y) {
342    
343            if (!(pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence))) {
344                return false;
345            }
346    
347            if (nextAction == EventHandler.Action.RESET_STATE) {
348                handler.resetState(t, y);
349            }
350            pendingEvent      = false;
351            pendingEventTime  = Double.NaN;
352    
353            return (nextAction == EventHandler.Action.RESET_STATE) ||
354                   (nextAction == EventHandler.Action.RESET_DERIVATIVES);
355    
356        }
357    
358        /** Local wrapper to propagate exceptions. */
359        private static class LocalMaxCountExceededException extends RuntimeException {
360    
361            /** Serializable UID. */
362            private static final long serialVersionUID = 20120901L;
363    
364            /** Wrapped exception. */
365            private final MaxCountExceededException wrapped;
366    
367            /** Simple constructor.
368             * @param exception exception to wrap
369             */
370            public LocalMaxCountExceededException(final MaxCountExceededException exception) {
371                wrapped = exception;
372            }
373    
374            /** Get the wrapped exception.
375             * @return wrapped exception
376             */
377            public MaxCountExceededException getException() {
378                return wrapped;
379            }
380    
381        }
382    
383    }