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 embedded Runge-Kutta
22   * integrators for Ordinary Differential Equations.
23   *
24   * <p>These methods are embedded explicit Runge-Kutta methods with two
25   * sets of coefficients allowing to estimate the error, their Butcher
26   * arrays are as follows :
27   * <pre>
28   *    0  |
29   *   c2  | a21
30   *   c3  | a31  a32
31   *   ... |        ...
32   *   cs  | as1  as2  ...  ass-1
33   *       |--------------------------
34   *       |  b1   b2  ...   bs-1  bs
35   *       |  b'1  b'2 ...   b's-1 b's
36   * </pre>
37   * </p>
38   *
39   * <p>In fact, we rather use the array defined by ej = bj - b'j to
40   * compute directly the error rather than computing two estimates and
41   * then comparing them.</p>
42   *
43   * <p>Some methods are qualified as <i>fsal</i> (first same as last)
44   * methods. This means the last evaluation of the derivatives in one
45   * step is the same as the first in the next step. Then, this
46   * evaluation can be reused from one step to the next one and the cost
47   * of such a method is really s-1 evaluations despite the method still
48   * has s stages. This behaviour is true only for successful steps, if
49   * the step is rejected after the error estimation phase, no
50   * evaluation is saved. For an <i>fsal</i> method, we have cs = 1 and
51   * asi = bi for all i.</p>
52   *
53   * @version $Revision: 620312 $ $Date: 2008-02-10 12:28:59 -0700 (Sun, 10 Feb 2008) $
54   * @since 1.2
55   */
56  
57  public abstract class EmbeddedRungeKuttaIntegrator
58    extends AdaptiveStepsizeIntegrator {
59  
60    /** Build a Runge-Kutta integrator with the given Butcher array.
61     * @param fsal indicate that the method is an <i>fsal</i>
62     * @param c time steps from Butcher array (without the first zero)
63     * @param a internal weights from Butcher array (without the first empty row)
64     * @param b propagation weights for the high order method from Butcher array
65     * @param prototype prototype of the step interpolator to use
66     * @param minStep minimal step (must be positive even for backward
67     * integration), the last step can be smaller than this
68     * @param maxStep maximal step (must be positive even for backward
69     * integration)
70     * @param scalAbsoluteTolerance allowed absolute error
71     * @param scalRelativeTolerance allowed relative error
72     */
73    protected EmbeddedRungeKuttaIntegrator(boolean fsal,
74                                           double[] c, double[][] a, double[] b,
75                                           RungeKuttaStepInterpolator prototype,
76                                           double minStep, double maxStep,
77                                           double scalAbsoluteTolerance,
78                                           double scalRelativeTolerance) {
79  
80      super(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
81  
82      this.fsal      = fsal;
83      this.c         = c;
84      this.a         = a;
85      this.b         = b;
86      this.prototype = prototype;
87  
88      exp = -1.0 / getOrder();
89  
90      // set the default values of the algorithm control parameters
91      setSafety(0.9);
92      setMinReduction(0.2);
93      setMaxGrowth(10.0);
94  
95    }
96  
97    /** Build a Runge-Kutta integrator with the given Butcher array.
98     * @param fsal indicate that the method is an <i>fsal</i>
99     * @param c time steps from Butcher array (without the first zero)
100    * @param a internal weights from Butcher array (without the first empty row)
101    * @param b propagation weights for the high order method from Butcher array
102    * @param prototype prototype of the step interpolator to use
103    * @param minStep minimal step (must be positive even for backward
104    * integration), the last step can be smaller than this
105    * @param maxStep maximal step (must be positive even for backward
106    * integration)
107    * @param vecAbsoluteTolerance allowed absolute error
108    * @param vecRelativeTolerance allowed relative error
109    */
110   protected EmbeddedRungeKuttaIntegrator(boolean fsal,
111                                          double[] c, double[][] a, double[] b,
112                                          RungeKuttaStepInterpolator prototype,
113                                          double   minStep, double maxStep,
114                                          double[] vecAbsoluteTolerance,
115                                          double[] vecRelativeTolerance) {
116 
117     super(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
118 
119     this.fsal      = fsal;
120     this.c         = c;
121     this.a         = a;
122     this.b         = b;
123     this.prototype = prototype;
124 
125     exp = -1.0 / getOrder();
126 
127     // set the default values of the algorithm control parameters
128     setSafety(0.9);
129     setMinReduction(0.2);
130     setMaxGrowth(10.0);
131 
132   }
133 
134   /** Get the name of the method.
135    * @return name of the method
136    */
137   public abstract String getName();
138 
139   /** Get the order of the method.
140    * @return order of the method
141    */
142   public abstract int getOrder();
143 
144   /** Get the safety factor for stepsize control.
145    * @return safety factor
146    */
147   public double getSafety() {
148     return safety;
149   }
150 
151   /** Set the safety factor for stepsize control.
152    * @param safety safety factor
153    */
154   public void setSafety(double safety) {
155     this.safety = safety;
156   }
157 
158   /** Integrate the differential equations up to the given time.
159    * <p>This method solves an Initial Value Problem (IVP).</p>
160    * <p>Since this method stores some internal state variables made
161    * available in its public interface during integration ({@link
162    * #getCurrentSignedStepsize()}), it is <em>not</em> thread-safe.</p>
163    * @param equations differential equations to integrate
164    * @param t0 initial time
165    * @param y0 initial value of the state vector at t0
166    * @param t target time for the integration
167    * (can be set to a value smaller than <code>t0</code> for backward integration)
168    * @param y placeholder where to put the state vector at each successful
169    *  step (and hence at the end of integration), can be the same object as y0
170    * @throws IntegratorException if the integrator cannot perform integration
171    * @throws DerivativeException this exception is propagated to the caller if
172    * the underlying user function triggers one
173    */
174   public void integrate(FirstOrderDifferentialEquations equations,
175                         double t0, double[] y0,
176                         double t, double[] y)
177   throws DerivativeException, IntegratorException {
178 
179     sanityChecks(equations, t0, y0, t, y);
180     boolean forward = (t > t0);
181 
182     // create some internal working arrays
183     int stages = c.length + 1;
184     if (y != y0) {
185       System.arraycopy(y0, 0, y, 0, y0.length);
186     }
187     double[][] yDotK = new double[stages][];
188     for (int i = 0; i < stages; ++i) {
189       yDotK [i] = new double[y0.length];
190     }
191     double[] yTmp = new double[y0.length];
192 
193     // set up an interpolator sharing the integrator arrays
194     AbstractStepInterpolator interpolator;
195     if (handler.requiresDenseOutput() || (! switchesHandler.isEmpty())) {
196       RungeKuttaStepInterpolator rki = (RungeKuttaStepInterpolator) prototype.copy();
197       rki.reinitialize(equations, yTmp, yDotK, forward);
198       interpolator = rki;
199     } else {
200       interpolator = new DummyStepInterpolator(yTmp, forward);
201     }
202     interpolator.storeTime(t0);
203 
204     stepStart  = t0;
205     double  hNew      = 0;
206     boolean firstTime = true;
207     boolean lastStep;
208     handler.reset();
209     do {
210 
211       interpolator.shift();
212 
213       double error = 0;
214       for (boolean loop = true; loop;) {
215 
216         if (firstTime || !fsal) {
217           // first stage
218           equations.computeDerivatives(stepStart, y, yDotK[0]);
219         }
220 
221         if (firstTime) {
222           double[] scale;
223           if (vecAbsoluteTolerance != null) {
224             scale = vecAbsoluteTolerance;
225           } else {
226             scale = new double[y0.length];
227             for (int i = 0; i < scale.length; ++i) {
228               scale[i] = scalAbsoluteTolerance;
229             }
230           }
231           hNew = initializeStep(equations, forward, getOrder(), scale,
232                                 stepStart, y, yDotK[0], yTmp, yDotK[1]);
233           firstTime = false;
234         }
235 
236         stepSize = hNew;
237 
238         // step adjustment near bounds
239         if ((forward && (stepStart + stepSize > t)) ||
240             ((! forward) && (stepStart + stepSize < t))) {
241           stepSize = t - stepStart;
242         }
243 
244         // next stages
245         for (int k = 1; k < stages; ++k) {
246 
247           for (int j = 0; j < y0.length; ++j) {
248             double sum = a[k-1][0] * yDotK[0][j];
249             for (int l = 1; l < k; ++l) {
250               sum += a[k-1][l] * yDotK[l][j];
251             }
252             yTmp[j] = y[j] + stepSize * sum;
253           }
254 
255           equations.computeDerivatives(stepStart + c[k-1] * stepSize, yTmp, yDotK[k]);
256 
257         }
258 
259         // estimate the state at the end of the step
260         for (int j = 0; j < y0.length; ++j) {
261           double sum    = b[0] * yDotK[0][j];
262           for (int l = 1; l < stages; ++l) {
263             sum    += b[l] * yDotK[l][j];
264           }
265           yTmp[j] = y[j] + stepSize * sum;
266         }
267 
268         // estimate the error at the end of the step
269         error = estimateError(yDotK, y, yTmp, stepSize);
270         if (error <= 1.0) {
271 
272           // Switching functions handling
273           interpolator.storeTime(stepStart + stepSize);
274           if (switchesHandler.evaluateStep(interpolator)) {
275             // reject the step to match exactly the next switch time
276             hNew = switchesHandler.getEventTime() - stepStart;
277           } else {
278             // accept the step
279             loop = false;
280           }
281 
282         } else {
283           // reject the step and attempt to reduce error by stepsize control
284           double factor = Math.min(maxGrowth,
285                                    Math.max(minReduction,
286                                             safety * Math.pow(error, exp)));
287           hNew = filterStep(stepSize * factor, false);
288         }
289 
290       }
291 
292       // the step has been accepted
293       double nextStep = stepStart + stepSize;
294       System.arraycopy(yTmp, 0, y, 0, y0.length);
295       switchesHandler.stepAccepted(nextStep, y);
296       if (switchesHandler.stop()) {
297         lastStep = true;
298       } else {
299         lastStep = forward ? (nextStep >= t) : (nextStep <= t);
300       }
301 
302       // provide the step data to the step handler
303       interpolator.storeTime(nextStep);
304       handler.handleStep(interpolator, lastStep);
305       stepStart = nextStep;
306 
307       if (fsal) {
308         // save the last evaluation for the next step
309         System.arraycopy(yDotK[stages - 1], 0, yDotK[0], 0, y0.length);
310       }
311 
312       if (switchesHandler.reset(stepStart, y) && ! lastStep) {
313         // some switching function has triggered changes that
314         // invalidate the derivatives, we need to recompute them
315         equations.computeDerivatives(stepStart, y, yDotK[0]);
316       }
317 
318       if (! lastStep) {
319         // stepsize control for next step
320         double  factor     = Math.min(maxGrowth,
321                                       Math.max(minReduction,
322                                                safety * Math.pow(error, exp)));
323         double  scaledH    = stepSize * factor;
324         double  nextT      = stepStart + scaledH;
325         boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t);
326         hNew = filterStep(scaledH, nextIsLast);
327       }
328 
329     } while (! lastStep);
330 
331     resetInternalState();
332 
333   }
334 
335   /** Get the minimal reduction factor for stepsize control.
336    * @return minimal reduction factor
337    */
338   public double getMinReduction() {
339     return minReduction;
340   }
341 
342   /** Set the minimal reduction factor for stepsize control.
343    * @param minReduction minimal reduction factor
344    */
345   public void setMinReduction(double minReduction) {
346     this.minReduction = minReduction;
347   }
348 
349   /** Get the maximal growth factor for stepsize control.
350    * @return maximal growth factor
351    */
352   public double getMaxGrowth() {
353     return maxGrowth;
354   }
355 
356   /** Set the maximal growth factor for stepsize control.
357    * @param maxGrowth maximal growth factor
358    */
359   public void setMaxGrowth(double maxGrowth) {
360     this.maxGrowth = maxGrowth;
361   }
362 
363   /** Compute the error ratio.
364    * @param yDotK derivatives computed during the first stages
365    * @param y0 estimate of the step at the start of the step
366    * @param y1 estimate of the step at the end of the step
367    * @param h  current step
368    * @return error ratio, greater than 1 if step should be rejected
369    */
370   protected abstract double estimateError(double[][] yDotK,
371                                           double[] y0, double[] y1,
372                                           double h);
373 
374   /** Indicator for <i>fsal</i> methods. */
375   private boolean fsal;
376 
377   /** Time steps from Butcher array (without the first zero). */
378   private double[] c;
379 
380   /** Internal weights from Butcher array (without the first empty row). */
381   private double[][] a;
382 
383   /** External weights for the high order method from Butcher array. */
384   private double[] b;
385 
386   /** Prototype of the step interpolator. */
387   private RungeKuttaStepInterpolator prototype;
388                                          
389   /** Stepsize control exponent. */
390   private double exp;
391 
392   /** Safety factor for stepsize control. */
393   private double safety;
394 
395   /** Minimal reduction factor for stepsize control. */
396   private double minReduction;
397 
398   /** Maximal growth factor for stepsize control. */
399   private double maxGrowth;
400 
401 }