summaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/math3/ode/events/EventState.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/apache/commons/math3/ode/events/EventState.java')
-rw-r--r--src/main/java/org/apache/commons/math3/ode/events/EventState.java431
1 files changed, 431 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math3/ode/events/EventState.java b/src/main/java/org/apache/commons/math3/ode/events/EventState.java
new file mode 100644
index 0000000..9e9da8d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/EventState.java
@@ -0,0 +1,431 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math3.ode.events;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.solvers.AllowedSolution;
+import org.apache.commons.math3.analysis.solvers.BracketedUnivariateSolver;
+import org.apache.commons.math3.analysis.solvers.PegasusSolver;
+import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
+import org.apache.commons.math3.analysis.solvers.UnivariateSolverUtils;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.ode.EquationsMapper;
+import org.apache.commons.math3.ode.ExpandableStatefulODE;
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+/** This class handles the state for one {@link EventHandler
+ * event handler} during integration steps.
+ *
+ * <p>Each time the integrator proposes a step, the event handler
+ * switching function should be checked. This class handles the state
+ * of one handler during one integration step, with references to the
+ * state at the end of the preceding step. This information is used to
+ * decide if the handler should trigger an event or not during the
+ * proposed step.</p>
+ *
+ * @since 1.2
+ */
+public class EventState {
+
+ /** Event handler. */
+ private final EventHandler handler;
+
+ /** Maximal time interval between events handler checks. */
+ private final double maxCheckInterval;
+
+ /** Convergence threshold for event localization. */
+ private final double convergence;
+
+ /** Upper limit in the iteration count for event localization. */
+ private final int maxIterationCount;
+
+ /** Equation being integrated. */
+ private ExpandableStatefulODE expandable;
+
+ /** Time at the beginning of the step. */
+ private double t0;
+
+ /** Value of the events handler at the beginning of the step. */
+ private double g0;
+
+ /** Simulated sign of g0 (we cheat when crossing events). */
+ private boolean g0Positive;
+
+ /** Indicator of event expected during the step. */
+ private boolean pendingEvent;
+
+ /** Occurrence time of the pending event. */
+ private double pendingEventTime;
+
+ /** Occurrence time of the previous event. */
+ private double previousEventTime;
+
+ /** Integration direction. */
+ private boolean forward;
+
+ /** Variation direction around pending event.
+ * (this is considered with respect to the integration direction)
+ */
+ private boolean increasing;
+
+ /** Next action indicator. */
+ private EventHandler.Action nextAction;
+
+ /** Root-finding algorithm to use to detect state events. */
+ private final UnivariateSolver solver;
+
+ /** Simple constructor.
+ * @param handler event handler
+ * @param maxCheckInterval maximal time interval between switching
+ * function checks (this interval prevents missing sign changes in
+ * case the integration steps becomes very large)
+ * @param convergence convergence threshold in the event time search
+ * @param maxIterationCount upper limit of the iteration count in
+ * the event time search
+ * @param solver Root-finding algorithm to use to detect state events
+ */
+ public EventState(final EventHandler handler, final double maxCheckInterval,
+ final double convergence, final int maxIterationCount,
+ final UnivariateSolver solver) {
+ this.handler = handler;
+ this.maxCheckInterval = maxCheckInterval;
+ this.convergence = FastMath.abs(convergence);
+ this.maxIterationCount = maxIterationCount;
+ this.solver = solver;
+
+ // some dummy values ...
+ expandable = null;
+ t0 = Double.NaN;
+ g0 = Double.NaN;
+ g0Positive = true;
+ pendingEvent = false;
+ pendingEventTime = Double.NaN;
+ previousEventTime = Double.NaN;
+ increasing = true;
+ nextAction = EventHandler.Action.CONTINUE;
+
+ }
+
+ /** Get the underlying event handler.
+ * @return underlying event handler
+ */
+ public EventHandler getEventHandler() {
+ return handler;
+ }
+
+ /** Set the equation.
+ * @param expandable equation being integrated
+ */
+ public void setExpandable(final ExpandableStatefulODE expandable) {
+ this.expandable = expandable;
+ }
+
+ /** Get the maximal time interval between events handler checks.
+ * @return maximal time interval between events handler checks
+ */
+ public double getMaxCheckInterval() {
+ return maxCheckInterval;
+ }
+
+ /** Get the convergence threshold for event localization.
+ * @return convergence threshold for event localization
+ */
+ public double getConvergence() {
+ return convergence;
+ }
+
+ /** Get the upper limit in the iteration count for event localization.
+ * @return upper limit in the iteration count for event localization
+ */
+ public int getMaxIterationCount() {
+ return maxIterationCount;
+ }
+
+ /** Reinitialize the beginning of the step.
+ * @param interpolator valid for the current step
+ * @exception MaxCountExceededException if the interpolator throws one because
+ * the number of functions evaluations is exceeded
+ */
+ public void reinitializeBegin(final StepInterpolator interpolator)
+ throws MaxCountExceededException {
+
+ t0 = interpolator.getPreviousTime();
+ interpolator.setInterpolatedTime(t0);
+ g0 = handler.g(t0, getCompleteState(interpolator));
+ if (g0 == 0) {
+ // excerpt from MATH-421 issue:
+ // If an ODE solver is setup with an EventHandler that return STOP
+ // when the even is triggered, the integrator stops (which is exactly
+ // the expected behavior). If however the user wants to restart the
+ // solver from the final state reached at the event with the same
+ // configuration (expecting the event to be triggered again at a
+ // later time), then the integrator may fail to start. It can get stuck
+ // at the previous event. The use case for the bug MATH-421 is fairly
+ // general, so events occurring exactly at start in the first step should
+ // be ignored.
+
+ // extremely rare case: there is a zero EXACTLY at interval start
+ // we will use the sign slightly after step beginning to force ignoring this zero
+ final double epsilon = FastMath.max(solver.getAbsoluteAccuracy(),
+ FastMath.abs(solver.getRelativeAccuracy() * t0));
+ final double tStart = t0 + 0.5 * epsilon;
+ interpolator.setInterpolatedTime(tStart);
+ g0 = handler.g(tStart, getCompleteState(interpolator));
+ }
+ g0Positive = g0 >= 0;
+
+ }
+
+ /** Get the complete state (primary and secondary).
+ * @param interpolator interpolator to use
+ * @return complete state
+ */
+ private double[] getCompleteState(final StepInterpolator interpolator) {
+
+ final double[] complete = new double[expandable.getTotalDimension()];
+
+ expandable.getPrimaryMapper().insertEquationData(interpolator.getInterpolatedState(),
+ complete);
+ int index = 0;
+ for (EquationsMapper secondary : expandable.getSecondaryMappers()) {
+ secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index++),
+ complete);
+ }
+
+ return complete;
+
+ }
+
+ /** Evaluate the impact of the proposed step on the event handler.
+ * @param interpolator step interpolator for the proposed step
+ * @return true if the event handler triggers an event before
+ * the end of the proposed step
+ * @exception MaxCountExceededException if the interpolator throws one because
+ * the number of functions evaluations is exceeded
+ * @exception NoBracketingException if the event cannot be bracketed
+ */
+ public boolean evaluateStep(final StepInterpolator interpolator)
+ throws MaxCountExceededException, NoBracketingException {
+
+ try {
+ forward = interpolator.isForward();
+ final double t1 = interpolator.getCurrentTime();
+ final double dt = t1 - t0;
+ if (FastMath.abs(dt) < convergence) {
+ // we cannot do anything on such a small step, don't trigger any events
+ return false;
+ }
+ final int n = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt) / maxCheckInterval));
+ final double h = dt / n;
+
+ final UnivariateFunction f = new UnivariateFunction() {
+ /** {@inheritDoc} */
+ public double value(final double t) throws LocalMaxCountExceededException {
+ try {
+ interpolator.setInterpolatedTime(t);
+ return handler.g(t, getCompleteState(interpolator));
+ } catch (MaxCountExceededException mcee) {
+ throw new LocalMaxCountExceededException(mcee);
+ }
+ }
+ };
+
+ double ta = t0;
+ double ga = g0;
+ for (int i = 0; i < n; ++i) {
+
+ // evaluate handler value at the end of the substep
+ final double tb = (i == n - 1) ? t1 : t0 + (i + 1) * h;
+ interpolator.setInterpolatedTime(tb);
+ final double gb = handler.g(tb, getCompleteState(interpolator));
+
+ // check events occurrence
+ if (g0Positive ^ (gb >= 0)) {
+ // there is a sign change: an event is expected during this step
+
+ // variation direction, with respect to the integration direction
+ increasing = gb >= ga;
+
+ // find the event time making sure we select a solution just at or past the exact root
+ final double root;
+ if (solver instanceof BracketedUnivariateSolver<?>) {
+ @SuppressWarnings("unchecked")
+ BracketedUnivariateSolver<UnivariateFunction> bracketing =
+ (BracketedUnivariateSolver<UnivariateFunction>) solver;
+ root = forward ?
+ bracketing.solve(maxIterationCount, f, ta, tb, AllowedSolution.RIGHT_SIDE) :
+ bracketing.solve(maxIterationCount, f, tb, ta, AllowedSolution.LEFT_SIDE);
+ } else {
+ final double baseRoot = forward ?
+ solver.solve(maxIterationCount, f, ta, tb) :
+ solver.solve(maxIterationCount, f, tb, ta);
+ final int remainingEval = maxIterationCount - solver.getEvaluations();
+ BracketedUnivariateSolver<UnivariateFunction> bracketing =
+ new PegasusSolver(solver.getRelativeAccuracy(), solver.getAbsoluteAccuracy());
+ root = forward ?
+ UnivariateSolverUtils.forceSide(remainingEval, f, bracketing,
+ baseRoot, ta, tb, AllowedSolution.RIGHT_SIDE) :
+ UnivariateSolverUtils.forceSide(remainingEval, f, bracketing,
+ baseRoot, tb, ta, AllowedSolution.LEFT_SIDE);
+ }
+
+ if ((!Double.isNaN(previousEventTime)) &&
+ (FastMath.abs(root - ta) <= convergence) &&
+ (FastMath.abs(root - previousEventTime) <= convergence)) {
+ // we have either found nothing or found (again ?) a past event,
+ // retry the substep excluding this value, and taking care to have the
+ // required sign in case the g function is noisy around its zero and
+ // crosses the axis several times
+ do {
+ ta = forward ? ta + convergence : ta - convergence;
+ ga = f.value(ta);
+ } while ((g0Positive ^ (ga >= 0)) && (forward ^ (ta >= tb)));
+
+ if (forward ^ (ta >= tb)) {
+ // we were able to skip this spurious root
+ --i;
+ } else {
+ // we can't avoid this root before the end of the step,
+ // we have to handle it despite it is close to the former one
+ // maybe we have two very close roots
+ pendingEventTime = root;
+ pendingEvent = true;
+ return true;
+ }
+ } else if (Double.isNaN(previousEventTime) ||
+ (FastMath.abs(previousEventTime - root) > convergence)) {
+ pendingEventTime = root;
+ pendingEvent = true;
+ return true;
+ } else {
+ // no sign change: there is no event for now
+ ta = tb;
+ ga = gb;
+ }
+
+ } else {
+ // no sign change: there is no event for now
+ ta = tb;
+ ga = gb;
+ }
+
+ }
+
+ // no event during the whole step
+ pendingEvent = false;
+ pendingEventTime = Double.NaN;
+ return false;
+
+ } catch (LocalMaxCountExceededException lmcee) {
+ throw lmcee.getException();
+ }
+
+ }
+
+ /** Get the occurrence time of the event triggered in the current step.
+ * @return occurrence time of the event triggered in the current
+ * step or infinity if no events are triggered
+ */
+ public double getEventTime() {
+ return pendingEvent ?
+ pendingEventTime :
+ (forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY);
+ }
+
+ /** Acknowledge the fact the step has been accepted by the integrator.
+ * @param t value of the independent <i>time</i> variable at the
+ * end of the step
+ * @param y array containing the current value of the state vector
+ * at the end of the step
+ */
+ public void stepAccepted(final double t, final double[] y) {
+
+ t0 = t;
+ g0 = handler.g(t, y);
+
+ if (pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence)) {
+ // force the sign to its value "just after the event"
+ previousEventTime = t;
+ g0Positive = increasing;
+ nextAction = handler.eventOccurred(t, y, !(increasing ^ forward));
+ } else {
+ g0Positive = g0 >= 0;
+ nextAction = EventHandler.Action.CONTINUE;
+ }
+ }
+
+ /** Check if the integration should be stopped at the end of the
+ * current step.
+ * @return true if the integration should be stopped
+ */
+ public boolean stop() {
+ return nextAction == EventHandler.Action.STOP;
+ }
+
+ /** Let the event handler reset the state if it wants.
+ * @param t value of the independent <i>time</i> variable at the
+ * beginning of the next step
+ * @param y array were to put the desired state vector at the beginning
+ * of the next step
+ * @return true if the integrator should reset the derivatives too
+ */
+ public boolean reset(final double t, final double[] y) {
+
+ if (!(pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence))) {
+ return false;
+ }
+
+ if (nextAction == EventHandler.Action.RESET_STATE) {
+ handler.resetState(t, y);
+ }
+ pendingEvent = false;
+ pendingEventTime = Double.NaN;
+
+ return (nextAction == EventHandler.Action.RESET_STATE) ||
+ (nextAction == EventHandler.Action.RESET_DERIVATIVES);
+
+ }
+
+ /** Local wrapper to propagate exceptions. */
+ private static class LocalMaxCountExceededException extends RuntimeException {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20120901L;
+
+ /** Wrapped exception. */
+ private final MaxCountExceededException wrapped;
+
+ /** Simple constructor.
+ * @param exception exception to wrap
+ */
+ LocalMaxCountExceededException(final MaxCountExceededException exception) {
+ wrapped = exception;
+ }
+
+ /** Get the wrapped exception.
+ * @return wrapped exception
+ */
+ public MaxCountExceededException getException() {
+ return wrapped;
+ }
+
+ }
+
+}