blob: e91bacc3665f7d1658d2f1450c4effdc639de782 [file] [log] [blame]
/*
* 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.math4.legacy.ode.events;
import org.apache.commons.math4.legacy.core.RealFieldElement;
import org.apache.commons.math4.legacy.analysis.RealFieldUnivariateFunction;
import org.apache.commons.math4.legacy.analysis.solvers.AllowedSolution;
import org.apache.commons.math4.legacy.analysis.solvers.BracketedRealFieldUnivariateSolver;
import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
import org.apache.commons.math4.legacy.exception.NoBracketingException;
import org.apache.commons.math4.legacy.ode.FieldODEState;
import org.apache.commons.math4.legacy.ode.FieldODEStateAndDerivative;
import org.apache.commons.math4.legacy.ode.sampling.FieldStepInterpolator;
import org.apache.commons.math4.legacy.core.jdkmath.AccurateMath;
/** 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>
*
* @param <T> the type of the field elements
* @since 3.6
*/
public class FieldEventState<T extends RealFieldElement<T>> {
/** Event handler. */
private final FieldEventHandler<T> handler;
/** Maximal time interval between events handler checks. */
private final double maxCheckInterval;
/** Convergence threshold for event localization. */
private final T convergence;
/** Upper limit in the iteration count for event localization. */
private final int maxIterationCount;
/** Time at the beginning of the step. */
private T t0;
/** Value of the events handler at the beginning of the step. */
private T 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 T pendingEventTime;
/** Occurrence time of the previous event. */
private T 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 Action nextAction;
/** Root-finding algorithm to use to detect state events. */
private final BracketedRealFieldUnivariateSolver<T> 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 FieldEventState(final FieldEventHandler<T> handler, final double maxCheckInterval,
final T convergence, final int maxIterationCount,
final BracketedRealFieldUnivariateSolver<T> solver) {
this.handler = handler;
this.maxCheckInterval = maxCheckInterval;
this.convergence = convergence.abs();
this.maxIterationCount = maxIterationCount;
this.solver = solver;
// some dummy values ...
t0 = null;
g0 = null;
g0Positive = true;
pendingEvent = false;
pendingEventTime = null;
previousEventTime = null;
increasing = true;
nextAction = Action.CONTINUE;
}
/** Get the underlying event handler.
* @return underlying event handler
*/
public FieldEventHandler<T> getEventHandler() {
return handler;
}
/** 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 T 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 FieldStepInterpolator<T> interpolator)
throws MaxCountExceededException {
final FieldODEStateAndDerivative<T> s0 = interpolator.getPreviousState();
t0 = s0.getTime();
g0 = handler.g(s0);
if (g0.getReal() == 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 = AccurateMath.max(solver.getAbsoluteAccuracy().getReal(),
AccurateMath.abs(solver.getRelativeAccuracy().multiply(t0).getReal()));
final T tStart = t0.add(0.5 * epsilon);
g0 = handler.g(interpolator.getInterpolatedState(tStart));
}
g0Positive = g0.getReal() >= 0;
}
/** 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 FieldStepInterpolator<T> interpolator)
throws MaxCountExceededException, NoBracketingException {
forward = interpolator.isForward();
final FieldODEStateAndDerivative<T> s1 = interpolator.getCurrentState();
final T t1 = s1.getTime();
final T dt = t1.subtract(t0);
if (dt.abs().subtract(convergence).getReal() < 0) {
// we cannot do anything on such a small step, don't trigger any events
return false;
}
final int n = AccurateMath.max(1, (int) AccurateMath.ceil(AccurateMath.abs(dt.getReal()) / maxCheckInterval));
final T h = dt.divide(n);
final RealFieldUnivariateFunction<T> f = new RealFieldUnivariateFunction<T>() {
/** {@inheritDoc} */
@Override
public T value(final T t) {
return handler.g(interpolator.getInterpolatedState(t));
}
};
T ta = t0;
T ga = g0;
for (int i = 0; i < n; ++i) {
// evaluate handler value at the end of the substep
final T tb = (i == n - 1) ? t1 : t0.add(h.multiply(i + 1));
final T gb = handler.g(interpolator.getInterpolatedState(tb));
// check events occurrence
if (g0Positive ^ (gb.getReal() >= 0)) {
// there is a sign change: an event is expected during this step
// variation direction, with respect to the integration direction
increasing = gb.subtract(ga).getReal() >= 0;
// find the event time making sure we select a solution just at or past the exact root
final T root = forward ?
solver.solve(maxIterationCount, f, ta, tb, AllowedSolution.RIGHT_SIDE) :
solver.solve(maxIterationCount, f, tb, ta, AllowedSolution.LEFT_SIDE);
if (previousEventTime != null &&
root.subtract(ta).abs().subtract(convergence).getReal() <= 0 &&
root.subtract(previousEventTime).abs().subtract(convergence).getReal() <= 0) {
// 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.add(convergence) : ta.subtract(convergence);
ga = f.value(ta);
} while ((g0Positive ^ (ga.getReal() >= 0)) && (forward ^ (ta.subtract(tb).getReal() >= 0)));
if (forward ^ (ta.subtract(tb).getReal() >= 0)) {
// 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 (previousEventTime == null ||
previousEventTime.subtract(root).abs().subtract(convergence).getReal() > 0) {
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 = null;
return false;
}
/** 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 T getEventTime() {
return pendingEvent ?
pendingEventTime :
t0.getField().getZero().add(forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY);
}
/** Acknowledge the fact the step has been accepted by the integrator.
* @param state state at the end of the step
*/
public void stepAccepted(final FieldODEStateAndDerivative<T> state) {
t0 = state.getTime();
g0 = handler.g(state);
if (pendingEvent && pendingEventTime.subtract(state.getTime()).abs().subtract(convergence).getReal() <= 0) {
// force the sign to its value "just after the event"
previousEventTime = state.getTime();
g0Positive = increasing;
nextAction = handler.eventOccurred(state, increasing == forward);
} else {
g0Positive = g0.getReal() >= 0;
nextAction = 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 == Action.STOP;
}
/** Let the event handler reset the state if it wants.
* @param state state at the beginning of the next step
* @return reset state (may by the same as initial state if only
* derivatives should be reset), or null if nothing is reset
*/
public FieldODEState<T> reset(final FieldODEStateAndDerivative<T> state) {
if (!(pendingEvent && pendingEventTime.subtract(state.getTime()).abs().subtract(convergence).getReal() <= 0)) {
return null;
}
final FieldODEState<T> newState;
if (nextAction == Action.RESET_STATE) {
newState = handler.resetState(state);
} else if (nextAction == Action.RESET_DERIVATIVES) {
newState = state;
} else {
newState = null;
}
pendingEvent = false;
pendingEventTime = null;
return newState;
}
}