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
18 package org.apache.commons.math.ode;
19
20 /**
21 * This abstract class holds the common part of all adaptive
22 * stepsize integrators for Ordinary Differential Equations.
23 *
24 * <p>These algorithms perform integration with stepsize control, which
25 * means the user does not specify the integration step but rather a
26 * tolerance on error. The error threshold is computed as
27 * <pre>
28 * threshold_i = absTol_i + relTol_i * max (abs (ym), abs (ym+1))
29 * </pre>
30 * where absTol_i is the absolute tolerance for component i of the
31 * state vector and relTol_i is the relative tolerance for the same
32 * component. The user can also use only two scalar values absTol and
33 * relTol which will be used for all components.</p>
34 *
35 * <p>If the estimated error for ym+1 is such that
36 * <pre>
37 * sqrt((sum (errEst_i / threshold_i)^2 ) / n) < 1
38 * </pre>
39 *
40 * (where n is the state vector dimension) then the step is accepted,
41 * otherwise the step is rejected and a new attempt is made with a new
42 * stepsize.</p>
43 *
44 * @version $Revision: 620312 $ $Date: 2008-02-10 12:28:59 -0700 (Sun, 10 Feb 2008) $
45 * @since 1.2
46 *
47 */
48
49 public abstract class AdaptiveStepsizeIntegrator
50 implements FirstOrderIntegrator {
51
52 /** Build an integrator with the given stepsize bounds.
53 * The default step handler does nothing.
54 * @param minStep minimal step (must be positive even for backward
55 * integration), the last step can be smaller than this
56 * @param maxStep maximal step (must be positive even for backward
57 * integration)
58 * @param scalAbsoluteTolerance allowed absolute error
59 * @param scalRelativeTolerance allowed relative error
60 */
61 public AdaptiveStepsizeIntegrator(double minStep, double maxStep,
62 double scalAbsoluteTolerance,
63 double scalRelativeTolerance) {
64
65 this.minStep = minStep;
66 this.maxStep = maxStep;
67 this.initialStep = -1.0;
68
69 this.scalAbsoluteTolerance = scalAbsoluteTolerance;
70 this.scalRelativeTolerance = scalRelativeTolerance;
71 this.vecAbsoluteTolerance = null;
72 this.vecRelativeTolerance = null;
73
74 // set the default step handler
75 handler = DummyStepHandler.getInstance();
76
77 switchesHandler = new SwitchingFunctionsHandler();
78
79 resetInternalState();
80
81 }
82
83 /** Build an integrator with the given stepsize bounds.
84 * The default step handler does nothing.
85 * @param minStep minimal step (must be positive even for backward
86 * integration), the last step can be smaller than this
87 * @param maxStep maximal step (must be positive even for backward
88 * integration)
89 * @param vecAbsoluteTolerance allowed absolute error
90 * @param vecRelativeTolerance allowed relative error
91 */
92 public AdaptiveStepsizeIntegrator(double minStep, double maxStep,
93 double[] vecAbsoluteTolerance,
94 double[] vecRelativeTolerance) {
95
96 this.minStep = minStep;
97 this.maxStep = maxStep;
98 this.initialStep = -1.0;
99
100 this.scalAbsoluteTolerance = 0;
101 this.scalRelativeTolerance = 0;
102 this.vecAbsoluteTolerance = vecAbsoluteTolerance;
103 this.vecRelativeTolerance = vecRelativeTolerance;
104
105 // set the default step handler
106 handler = DummyStepHandler.getInstance();
107
108 switchesHandler = new SwitchingFunctionsHandler();
109
110 resetInternalState();
111
112 }
113
114 /** Set the initial step size.
115 * <p>This method allows the user to specify an initial positive
116 * step size instead of letting the integrator guess it by
117 * itself. If this method is not called before integration is
118 * started, the initial step size will be estimated by the
119 * integrator.</p>
120 * @param initialStepSize initial step size to use (must be positive even
121 * for backward integration ; providing a negative value or a value
122 * outside of the min/max step interval will lead the integrator to
123 * ignore the value and compute the initial step size by itself)
124 */
125 public void setInitialStepSize(double initialStepSize) {
126 if ((initialStepSize < minStep) || (initialStepSize > maxStep)) {
127 initialStep = -1.0;
128 } else {
129 initialStep = initialStepSize;
130 }
131 }
132
133 /** Set the step handler for this integrator.
134 * The handler will be called by the integrator for each accepted
135 * step.
136 * @param handler handler for the accepted steps
137 */
138 public void setStepHandler (StepHandler handler) {
139 this.handler = handler;
140 }
141
142 /** Get the step handler for this integrator.
143 * @return the step handler for this integrator
144 */
145 public StepHandler getStepHandler() {
146 return handler;
147 }
148
149 /** Add a switching function to the integrator.
150 * @param function switching function
151 * @param maxCheckInterval maximal time interval between switching
152 * function checks (this interval prevents missing sign changes in
153 * case the integration steps becomes very large)
154 * @param convergence convergence threshold in the event time search
155 * @param maxIterationCount upper limit of the iteration count in
156 * the event time search
157 */
158 public void addSwitchingFunction(SwitchingFunction function,
159 double maxCheckInterval,
160 double convergence,
161 int maxIterationCount) {
162 switchesHandler.add(function, maxCheckInterval, convergence, maxIterationCount);
163 }
164
165 /** Perform some sanity checks on the integration parameters.
166 * @param equations differential equations set
167 * @param t0 start time
168 * @param y0 state vector at t0
169 * @param t target time for the integration
170 * @param y placeholder where to put the state vector
171 * @exception IntegratorException if some inconsistency is detected
172 */
173 protected void sanityChecks(FirstOrderDifferentialEquations equations,
174 double t0, double[] y0, double t, double[] y)
175 throws IntegratorException {
176 if (equations.getDimension() != y0.length) {
177 throw new IntegratorException("dimensions mismatch: ODE problem has dimension {0}," +
178 " initial state vector has dimension {1}",
179 new Object[] {
180 new Integer(equations.getDimension()),
181 new Integer(y0.length)
182 });
183 }
184 if (equations.getDimension() != y.length) {
185 throw new IntegratorException("dimensions mismatch: ODE problem has dimension {0}," +
186 " final state vector has dimension {1}",
187 new Object[] {
188 new Integer(equations.getDimension()),
189 new Integer(y.length)
190 });
191 }
192 if ((vecAbsoluteTolerance != null) && (vecAbsoluteTolerance.length != y0.length)) {
193 throw new IntegratorException("dimensions mismatch: state vector has dimension {0}," +
194 " absolute tolerance vector has dimension {1}",
195 new Object[] {
196 new Integer(y0.length),
197 new Integer(vecAbsoluteTolerance.length)
198 });
199 }
200 if ((vecRelativeTolerance != null) && (vecRelativeTolerance.length != y0.length)) {
201 throw new IntegratorException("dimensions mismatch: state vector has dimension {0}," +
202 " relative tolerance vector has dimension {1}",
203 new Object[] {
204 new Integer(y0.length),
205 new Integer(vecRelativeTolerance.length)
206 });
207 }
208 if (Math.abs(t - t0) <= 1.0e-12 * Math.max(Math.abs(t0), Math.abs(t))) {
209 throw new IntegratorException("too small integration interval: length = {0}",
210 new Object[] { new Double(Math.abs(t - t0)) });
211 }
212
213 }
214
215 /** Initialize the integration step.
216 * @param equations differential equations set
217 * @param forward forward integration indicator
218 * @param order order of the method
219 * @param scale scaling vector for the state vector
220 * @param t0 start time
221 * @param y0 state vector at t0
222 * @param yDot0 first time derivative of y0
223 * @param y1 work array for a state vector
224 * @param yDot1 work array for the first time derivative of y1
225 * @return first integration step
226 * @exception DerivativeException this exception is propagated to
227 * the caller if the underlying user function triggers one
228 */
229 public double initializeStep(FirstOrderDifferentialEquations equations,
230 boolean forward, int order, double[] scale,
231 double t0, double[] y0, double[] yDot0,
232 double[] y1, double[] yDot1)
233 throws DerivativeException {
234
235 if (initialStep > 0) {
236 // use the user provided value
237 return forward ? initialStep : -initialStep;
238 }
239
240 // very rough first guess : h = 0.01 * ||y/scale|| / ||y'/scale||
241 // this guess will be used to perform an Euler step
242 double ratio;
243 double yOnScale2 = 0;
244 double yDotOnScale2 = 0;
245 for (int j = 0; j < y0.length; ++j) {
246 ratio = y0[j] / scale[j];
247 yOnScale2 += ratio * ratio;
248 ratio = yDot0[j] / scale[j];
249 yDotOnScale2 += ratio * ratio;
250 }
251
252 double h = ((yOnScale2 < 1.0e-10) || (yDotOnScale2 < 1.0e-10)) ?
253 1.0e-6 : (0.01 * Math.sqrt(yOnScale2 / yDotOnScale2));
254 if (! forward) {
255 h = -h;
256 }
257
258 // perform an Euler step using the preceding rough guess
259 for (int j = 0; j < y0.length; ++j) {
260 y1[j] = y0[j] + h * yDot0[j];
261 }
262 equations.computeDerivatives(t0 + h, y1, yDot1);
263
264 // estimate the second derivative of the solution
265 double yDDotOnScale = 0;
266 for (int j = 0; j < y0.length; ++j) {
267 ratio = (yDot1[j] - yDot0[j]) / scale[j];
268 yDDotOnScale += ratio * ratio;
269 }
270 yDDotOnScale = Math.sqrt(yDDotOnScale) / h;
271
272 // step size is computed such that
273 // h^order * max (||y'/tol||, ||y''/tol||) = 0.01
274 double maxInv2 = Math.max(Math.sqrt(yDotOnScale2), yDDotOnScale);
275 double h1 = (maxInv2 < 1.0e-15) ?
276 Math.max(1.0e-6, 0.001 * Math.abs(h)) :
277 Math.pow(0.01 / maxInv2, 1.0 / order);
278 h = Math.min(100.0 * Math.abs(h), h1);
279 h = Math.max(h, 1.0e-12 * Math.abs(t0)); // avoids cancellation when computing t1 - t0
280 if (h < getMinStep()) {
281 h = getMinStep();
282 }
283 if (h > getMaxStep()) {
284 h = getMaxStep();
285 }
286 if (! forward) {
287 h = -h;
288 }
289
290 return h;
291
292 }
293
294 /** Filter the integration step.
295 * @param h signed step
296 * @param acceptSmall if true, steps smaller than the minimal value
297 * are silently increased up to this value, if false such small
298 * steps generate an exception
299 * @return a bounded integration step (h if no bound is reach, or a bounded value)
300 * @exception IntegratorException if the step is too small and acceptSmall is false
301 */
302 protected double filterStep(double h, boolean acceptSmall)
303 throws IntegratorException {
304
305 if (Math.abs(h) < minStep) {
306 if (acceptSmall) {
307 h = (h < 0) ? -minStep : minStep;
308 } else {
309 throw new IntegratorException("minimal step size ({0}) reached," +
310 " integration needs {1}",
311 new Object[] {
312 new Double(minStep),
313 new Double(Math.abs(h))
314 });
315 }
316 }
317
318 if (h > maxStep) {
319 h = maxStep;
320 } else if (h < -maxStep) {
321 h = -maxStep;
322 }
323
324 return h;
325
326 }
327
328 /** Integrate the differential equations up to the given time.
329 * <p>This method solves an Initial Value Problem (IVP).</p>
330 * <p>Since this method stores some internal state variables made
331 * available in its public interface during integration ({@link
332 * #getCurrentSignedStepsize()}), it is <em>not</em> thread-safe.</p>
333 * @param equations differential equations to integrate
334 * @param t0 initial time
335 * @param y0 initial value of the state vector at t0
336 * @param t target time for the integration
337 * (can be set to a value smaller than <code>t0</code> for backward integration)
338 * @param y placeholder where to put the state vector at each successful
339 * step (and hence at the end of integration), can be the same object as y0
340 * @throws IntegratorException if the integrator cannot perform integration
341 * @throws DerivativeException this exception is propagated to the caller if
342 * the underlying user function triggers one
343 */
344 public abstract void integrate (FirstOrderDifferentialEquations equations,
345 double t0, double[] y0,
346 double t, double[] y)
347 throws DerivativeException, IntegratorException;
348
349 /** Get the current value of the step start time t<sub>i</sub>.
350 * <p>This method can be called during integration (typically by
351 * the object implementing the {@link FirstOrderDifferentialEquations
352 * differential equations} problem) if the value of the current step that
353 * is attempted is needed.</p>
354 * <p>The result is undefined if the method is called outside of
355 * calls to {@link #integrate}</p>
356 * @return current value of the step start time t<sub>i</sub>
357 */
358 public double getCurrentStepStart() {
359 return stepStart;
360 }
361
362 /** Get the current signed value of the integration stepsize.
363 * <p>This method can be called during integration (typically by
364 * the object implementing the {@link FirstOrderDifferentialEquations
365 * differential equations} problem) if the signed value of the current stepsize
366 * that is tried is needed.</p>
367 * <p>The result is undefined if the method is called outside of
368 * calls to {@link #integrate}</p>
369 * @return current signed value of the stepsize
370 */
371 public double getCurrentSignedStepsize() {
372 return stepSize;
373 }
374
375 /** Reset internal state to dummy values. */
376 protected void resetInternalState() {
377 stepStart = Double.NaN;
378 stepSize = Math.sqrt(minStep * maxStep);
379 }
380
381 /** Get the minimal step.
382 * @return minimal step
383 */
384 public double getMinStep() {
385 return minStep;
386 }
387
388 /** Get the maximal step.
389 * @return maximal step
390 */
391 public double getMaxStep() {
392 return maxStep;
393 }
394
395 /** Minimal step. */
396 private double minStep;
397
398 /** Maximal step. */
399 private double maxStep;
400
401 /** User supplied initial step. */
402 private double initialStep;
403
404 /** Allowed absolute scalar error. */
405 protected double scalAbsoluteTolerance;
406
407 /** Allowed relative scalar error. */
408 protected double scalRelativeTolerance;
409
410 /** Allowed absolute vectorial error. */
411 protected double[] vecAbsoluteTolerance;
412
413 /** Allowed relative vectorial error. */
414 protected double[] vecRelativeTolerance;
415
416 /** Step handler. */
417 protected StepHandler handler;
418
419 /** Switching functions handler. */
420 protected SwitchingFunctionsHandler switchesHandler;
421
422 /** Current step start time. */
423 protected double stepStart;
424
425 /** Current stepsize. */
426 protected double stepSize;
427
428 }