View Javadoc

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 }