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 class implements the common part of all fixed step Runge-Kutta
22 * integrators for Ordinary Differential Equations.
23 *
24 * <p>These methods are explicit Runge-Kutta methods, their Butcher
25 * arrays are as follows :
26 * <pre>
27 * 0 |
28 * c2 | a21
29 * c3 | a31 a32
30 * ... | ...
31 * cs | as1 as2 ... ass-1
32 * |--------------------------
33 * | b1 b2 ... bs-1 bs
34 * </pre>
35 * </p>
36 *
37 * @see EulerIntegrator
38 * @see ClassicalRungeKuttaIntegrator
39 * @see GillIntegrator
40 * @see MidpointIntegrator
41 * @version $Revision: 620312 $ $Date: 2008-02-10 12:28:59 -0700 (Sun, 10 Feb 2008) $
42 * @since 1.2
43 */
44
45 public abstract class RungeKuttaIntegrator
46 implements FirstOrderIntegrator {
47
48 /** Simple constructor.
49 * Build a Runge-Kutta integrator with the given
50 * step. The default step handler does nothing.
51 * @param c time steps from Butcher array (without the first zero)
52 * @param a internal weights from Butcher array (without the first empty row)
53 * @param b propagation weights for the high order method from Butcher array
54 * @param prototype prototype of the step interpolator to use
55 * @param step integration step
56 */
57 protected RungeKuttaIntegrator(double[] c, double[][] a, double[] b,
58 RungeKuttaStepInterpolator prototype,
59 double step) {
60 this.c = c;
61 this.a = a;
62 this.b = b;
63 this.prototype = prototype;
64 this.step = step;
65 handler = DummyStepHandler.getInstance();
66 switchesHandler = new SwitchingFunctionsHandler();
67 resetInternalState();
68 }
69
70 /** Get the name of the method.
71 * @return name of the method
72 */
73 public abstract String getName();
74
75 /** Set the step handler for this integrator.
76 * The handler will be called by the integrator for each accepted
77 * step.
78 * @param handler handler for the accepted steps
79 */
80 public void setStepHandler (StepHandler handler) {
81 this.handler = handler;
82 }
83
84 /** Get the step handler for this integrator.
85 * @return the step handler for this integrator
86 */
87 public StepHandler getStepHandler() {
88 return handler;
89 }
90
91 /** Add a switching function to the integrator.
92 * @param function switching function
93 * @param maxCheckInterval maximal time interval between switching
94 * function checks (this interval prevents missing sign changes in
95 * case the integration steps becomes very large)
96 * @param convergence convergence threshold in the event time search
97 * @param maxIterationCount upper limit of the iteration count in
98 * the event time search
99 */
100 public void addSwitchingFunction(SwitchingFunction function,
101 double maxCheckInterval,
102 double convergence,
103 int maxIterationCount) {
104 switchesHandler.add(function, maxCheckInterval, convergence, maxIterationCount);
105 }
106
107 /** Perform some sanity checks on the integration parameters.
108 * @param equations differential equations set
109 * @param t0 start time
110 * @param y0 state vector at t0
111 * @param t target time for the integration
112 * @param y placeholder where to put the state vector
113 * @exception IntegratorException if some inconsistency is detected
114 */
115 private void sanityChecks(FirstOrderDifferentialEquations equations,
116 double t0, double[] y0, double t, double[] y)
117 throws IntegratorException {
118 if (equations.getDimension() != y0.length) {
119 throw new IntegratorException("dimensions mismatch: ODE problem has dimension {0}," +
120 " initial state vector has dimension {1}",
121 new Object[] {
122 new Integer(equations.getDimension()),
123 new Integer(y0.length)
124 });
125 }
126 if (equations.getDimension() != y.length) {
127 throw new IntegratorException("dimensions mismatch: ODE problem has dimension {0}," +
128 " final state vector has dimension {1}",
129 new Object[] {
130 new Integer(equations.getDimension()),
131 new Integer(y.length)
132 });
133 }
134 if (Math.abs(t - t0) <= 1.0e-12 * Math.max(Math.abs(t0), Math.abs(t))) {
135 throw new IntegratorException("too small integration interval: length = {0}",
136 new Object[] { new Double(Math.abs(t - t0)) });
137 }
138 }
139
140 /** Integrate the differential equations up to the given time.
141 * <p>This method solves an Initial Value Problem (IVP).</p>
142 * <p>Since this method stores some internal state variables made
143 * available in its public interface during integration ({@link
144 * #getCurrentSignedStepsize()}), it is <em>not</em> thread-safe.</p>
145 * @param equations differential equations to integrate
146 * @param t0 initial time
147 * @param y0 initial value of the state vector at t0
148 * @param t target time for the integration
149 * (can be set to a value smaller than <code>t0</code> for backward integration)
150 * @param y placeholder where to put the state vector at each successful
151 * step (and hence at the end of integration), can be the same object as y0
152 * @throws IntegratorException if the integrator cannot perform integration
153 * @throws DerivativeException this exception is propagated to the caller if
154 * the underlying user function triggers one
155 */
156 public void integrate(FirstOrderDifferentialEquations equations,
157 double t0, double[] y0,
158 double t, double[] y)
159 throws DerivativeException, IntegratorException {
160
161 sanityChecks(equations, t0, y0, t, y);
162 boolean forward = (t > t0);
163
164 // create some internal working arrays
165 int stages = c.length + 1;
166 if (y != y0) {
167 System.arraycopy(y0, 0, y, 0, y0.length);
168 }
169 double[][] yDotK = new double[stages][];
170 for (int i = 0; i < stages; ++i) {
171 yDotK [i] = new double[y0.length];
172 }
173 double[] yTmp = new double[y0.length];
174
175 // set up an interpolator sharing the integrator arrays
176 AbstractStepInterpolator interpolator;
177 if (handler.requiresDenseOutput() || (! switchesHandler.isEmpty())) {
178 RungeKuttaStepInterpolator rki = (RungeKuttaStepInterpolator) prototype.copy();
179 rki.reinitialize(equations, yTmp, yDotK, forward);
180 interpolator = rki;
181 } else {
182 interpolator = new DummyStepInterpolator(yTmp, forward);
183 }
184 interpolator.storeTime(t0);
185
186 // recompute the step
187 long nbStep = Math.max(1l, Math.abs(Math.round((t - t0) / step)));
188 boolean lastStep = false;
189 stepStart = t0;
190 stepSize = (t - t0) / nbStep;
191 handler.reset();
192 for (long i = 0; ! lastStep; ++i) {
193
194 interpolator.shift();
195
196 boolean needUpdate = false;
197 for (boolean loop = true; loop;) {
198
199 // first stage
200 equations.computeDerivatives(stepStart, y, yDotK[0]);
201
202 // next stages
203 for (int k = 1; k < stages; ++k) {
204
205 for (int j = 0; j < y0.length; ++j) {
206 double sum = a[k-1][0] * yDotK[0][j];
207 for (int l = 1; l < k; ++l) {
208 sum += a[k-1][l] * yDotK[l][j];
209 }
210 yTmp[j] = y[j] + stepSize * sum;
211 }
212
213 equations.computeDerivatives(stepStart + c[k-1] * stepSize, yTmp, yDotK[k]);
214
215 }
216
217 // estimate the state at the end of the step
218 for (int j = 0; j < y0.length; ++j) {
219 double sum = b[0] * yDotK[0][j];
220 for (int l = 1; l < stages; ++l) {
221 sum += b[l] * yDotK[l][j];
222 }
223 yTmp[j] = y[j] + stepSize * sum;
224 }
225
226 // Switching functions handling
227 interpolator.storeTime(stepStart + stepSize);
228 if (switchesHandler.evaluateStep(interpolator)) {
229 needUpdate = true;
230 stepSize = switchesHandler.getEventTime() - stepStart;
231 } else {
232 loop = false;
233 }
234
235 }
236
237 // the step has been accepted
238 double nextStep = stepStart + stepSize;
239 System.arraycopy(yTmp, 0, y, 0, y0.length);
240 switchesHandler.stepAccepted(nextStep, y);
241 if (switchesHandler.stop()) {
242 lastStep = true;
243 } else {
244 lastStep = (i == (nbStep - 1));
245 }
246
247 // provide the step data to the step handler
248 interpolator.storeTime(nextStep);
249 handler.handleStep(interpolator, lastStep);
250 stepStart = nextStep;
251
252 if (switchesHandler.reset(stepStart, y) && ! lastStep) {
253 // some switching function has triggered changes that
254 // invalidate the derivatives, we need to recompute them
255 equations.computeDerivatives(stepStart, y, yDotK[0]);
256 }
257
258 if (needUpdate) {
259 // a switching function has changed the step
260 // we need to recompute stepsize
261 nbStep = Math.max(1l, Math.abs(Math.round((t - stepStart) / step)));
262 stepSize = (t - stepStart) / nbStep;
263 i = -1;
264 }
265
266 }
267
268 resetInternalState();
269
270 }
271
272 /** Get the current value of the step start time t<sub>i</sub>.
273 * <p>This method can be called during integration (typically by
274 * the object implementing the {@link FirstOrderDifferentialEquations
275 * differential equations} problem) if the value of the current step that
276 * is attempted is needed.</p>
277 * <p>The result is undefined if the method is called outside of
278 * calls to {@link #integrate}</p>
279 * @return current value of the step start time t<sub>i</sub>
280 */
281 public double getCurrentStepStart() {
282 return stepStart;
283 }
284
285 /** Get the current signed value of the integration stepsize.
286 * <p>This method can be called during integration (typically by
287 * the object implementing the {@link FirstOrderDifferentialEquations
288 * differential equations} problem) if the signed value of the current stepsize
289 * that is tried is needed.</p>
290 * <p>The result is undefined if the method is called outside of
291 * calls to {@link #integrate}</p>
292 * @return current signed value of the stepsize
293 */
294 public double getCurrentSignedStepsize() {
295 return stepSize;
296 }
297
298 /** Reset internal state to dummy values. */
299 private void resetInternalState() {
300 stepStart = Double.NaN;
301 stepSize = Double.NaN;
302 }
303
304 /** Time steps from Butcher array (without the first zero). */
305 private double[] c;
306
307 /** Internal weights from Butcher array (without the first empty row). */
308 private double[][] a;
309
310 /** External weights for the high order method from Butcher array. */
311 private double[] b;
312
313 /** Prototype of the step interpolator. */
314 private RungeKuttaStepInterpolator prototype;
315
316 /** Integration step. */
317 private double step;
318
319 /** Step handler. */
320 private StepHandler handler;
321
322 /** Switching functions handler. */
323 protected SwitchingFunctionsHandler switchesHandler;
324
325 /** Current step start time. */
326 private double stepStart;
327
328 /** Current stepsize. */
329 private double stepSize;
330
331 }