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