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.math4.legacy.ode.nonstiff;
19  
20  
21  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
22  import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
23  import org.apache.commons.math4.legacy.exception.NoBracketingException;
24  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
25  import org.apache.commons.math4.legacy.ode.FirstOrderDifferentialEquations;
26  import org.apache.commons.math4.legacy.ode.FirstOrderIntegrator;
27  import org.apache.commons.math4.legacy.ode.TestProblem1;
28  import org.apache.commons.math4.legacy.ode.TestProblem2;
29  import org.apache.commons.math4.legacy.ode.TestProblem3;
30  import org.apache.commons.math4.legacy.ode.TestProblem4;
31  import org.apache.commons.math4.legacy.ode.TestProblem5;
32  import org.apache.commons.math4.legacy.ode.TestProblem6;
33  import org.apache.commons.math4.legacy.ode.TestProblemAbstract;
34  import org.apache.commons.math4.legacy.ode.TestProblemHandler;
35  import org.apache.commons.math4.legacy.ode.events.EventHandler;
36  import org.apache.commons.math4.legacy.ode.sampling.StepHandler;
37  import org.apache.commons.math4.legacy.ode.sampling.StepInterpolator;
38  import org.apache.commons.math4.core.jdkmath.JdkMath;
39  import org.junit.Assert;
40  import org.junit.Test;
41  
42  public class ClassicalRungeKuttaIntegratorTest {
43  
44    @Test
45    public void testMissedEndEvent()
46        throws DimensionMismatchException, NumberIsTooSmallException,
47               MaxCountExceededException, NoBracketingException {
48        final double   t0     = 1878250320.0000029;
49        final double   tEvent = 1878250379.9999986;
50        final double[] k      = { 1.0e-4, 1.0e-5, 1.0e-6 };
51        FirstOrderDifferentialEquations ode = new FirstOrderDifferentialEquations() {
52  
53            @Override
54          public int getDimension() {
55                return k.length;
56            }
57  
58            @Override
59          public void computeDerivatives(double t, double[] y, double[] yDot) {
60                for (int i = 0; i < y.length; ++i) {
61                    yDot[i] = k[i] * y[i];
62                }
63            }
64        };
65  
66        ClassicalRungeKuttaIntegrator integrator = new ClassicalRungeKuttaIntegrator(60.0);
67  
68        double[] y0   = new double[k.length];
69        for (int i = 0; i < y0.length; ++i) {
70            y0[i] = i + 1;
71        }
72        double[] y    = new double[k.length];
73  
74        double finalT = integrator.integrate(ode, t0, y0, tEvent, y);
75        Assert.assertEquals(tEvent, finalT, 5.0e-6);
76        for (int i = 0; i < y.length; ++i) {
77            Assert.assertEquals(y0[i] * JdkMath.exp(k[i] * (finalT - t0)), y[i], 1.0e-9);
78        }
79  
80        integrator.addEventHandler(new EventHandler() {
81  
82            @Override
83          public void init(double t0, double[] y0, double t) {
84            }
85  
86            @Override
87          public void resetState(double t, double[] y) {
88            }
89  
90            @Override
91          public double g(double t, double[] y) {
92                return t - tEvent;
93            }
94  
95            @Override
96          public Action eventOccurred(double t, double[] y, boolean increasing) {
97                Assert.assertEquals(tEvent, t, 5.0e-6);
98                return Action.CONTINUE;
99            }
100       }, Double.POSITIVE_INFINITY, 1.0e-20, 100);
101       finalT = integrator.integrate(ode, t0, y0, tEvent + 120, y);
102       Assert.assertEquals(tEvent + 120, finalT, 5.0e-6);
103       for (int i = 0; i < y.length; ++i) {
104           Assert.assertEquals(y0[i] * JdkMath.exp(k[i] * (finalT - t0)), y[i], 1.0e-9);
105       }
106   }
107 
108   @Test
109   public void testSanityChecks()
110       throws DimensionMismatchException, NumberIsTooSmallException,
111              MaxCountExceededException, NoBracketingException {
112     try  {
113       TestProblem1 pb = new TestProblem1();
114       new ClassicalRungeKuttaIntegrator(0.01).integrate(pb,
115                                                         0.0, new double[pb.getDimension()+10],
116                                                         1.0, new double[pb.getDimension()]);
117         Assert.fail("an exception should have been thrown");
118     } catch(DimensionMismatchException ie) {
119     }
120     try  {
121         TestProblem1 pb = new TestProblem1();
122         new ClassicalRungeKuttaIntegrator(0.01).integrate(pb,
123                                                           0.0, new double[pb.getDimension()],
124                                                           1.0, new double[pb.getDimension()+10]);
125           Assert.fail("an exception should have been thrown");
126       } catch(DimensionMismatchException ie) {
127       }
128     try  {
129       TestProblem1 pb = new TestProblem1();
130       new ClassicalRungeKuttaIntegrator(0.01).integrate(pb,
131                                                         0.0, new double[pb.getDimension()],
132                                                         0.0, new double[pb.getDimension()]);
133         Assert.fail("an exception should have been thrown");
134     } catch(NumberIsTooSmallException ie) {
135     }
136   }
137 
138   @Test
139   public void testDecreasingSteps()
140       throws DimensionMismatchException, NumberIsTooSmallException,
141              MaxCountExceededException, NoBracketingException {
142 
143     for (TestProblemAbstract pb : new TestProblemAbstract[] {
144         new TestProblem1(), new TestProblem2(), new TestProblem3(),
145         new TestProblem4(), new TestProblem5(), new TestProblem6()
146     }) {
147 
148       double previousValueError = Double.NaN;
149       double previousTimeError = Double.NaN;
150       for (int i = 4; i < 10; ++i) {
151 
152         double step = (pb.getFinalTime() - pb.getInitialTime()) * JdkMath.pow(2.0, -i);
153 
154         FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
155         TestProblemHandler handler = new TestProblemHandler(pb, integ);
156         integ.addStepHandler(handler);
157         EventHandler[] functions = pb.getEventsHandlers();
158         for (int l = 0; l < functions.length; ++l) {
159           integ.addEventHandler(functions[l],
160                                      Double.POSITIVE_INFINITY, 1.0e-6 * step, 1000);
161         }
162         Assert.assertEquals(functions.length, integ.getEventHandlers().size());
163         double stopTime = integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
164                                           pb.getFinalTime(), new double[pb.getDimension()]);
165         if (functions.length == 0) {
166             Assert.assertEquals(pb.getFinalTime(), stopTime, 1.0e-10);
167         }
168 
169         double error = handler.getMaximalValueError();
170         if (i > 4) {
171           Assert.assertTrue(error < 1.01 * JdkMath.abs(previousValueError));
172         }
173         previousValueError = error;
174 
175         double timeError = handler.getMaximalTimeError();
176         if (i > 4) {
177           Assert.assertTrue(timeError <= JdkMath.abs(previousTimeError));
178         }
179         previousTimeError = timeError;
180 
181         integ.clearEventHandlers();
182         Assert.assertEquals(0, integ.getEventHandlers().size());
183       }
184     }
185   }
186 
187   @Test
188   public void testSmallStep()
189       throws DimensionMismatchException, NumberIsTooSmallException,
190              MaxCountExceededException, NoBracketingException {
191 
192     TestProblem1 pb = new TestProblem1();
193     double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
194 
195     FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
196     TestProblemHandler handler = new TestProblemHandler(pb, integ);
197     integ.addStepHandler(handler);
198     integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
199                     pb.getFinalTime(), new double[pb.getDimension()]);
200 
201     Assert.assertTrue(handler.getLastError() < 2.0e-13);
202     Assert.assertTrue(handler.getMaximalValueError() < 4.0e-12);
203     Assert.assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
204     Assert.assertEquals("classical Runge-Kutta", integ.getName());
205   }
206 
207   @Test
208   public void testBigStep()
209       throws DimensionMismatchException, NumberIsTooSmallException,
210              MaxCountExceededException, NoBracketingException {
211 
212     TestProblem1 pb = new TestProblem1();
213     double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2;
214 
215     FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
216     TestProblemHandler handler = new TestProblemHandler(pb, integ);
217     integ.addStepHandler(handler);
218     integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
219                     pb.getFinalTime(), new double[pb.getDimension()]);
220 
221     Assert.assertTrue(handler.getLastError() > 0.0004);
222     Assert.assertTrue(handler.getMaximalValueError() > 0.005);
223     Assert.assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
224   }
225 
226   @Test
227   public void testBackward()
228       throws DimensionMismatchException, NumberIsTooSmallException,
229              MaxCountExceededException, NoBracketingException {
230 
231     TestProblem5 pb = new TestProblem5();
232     double step = JdkMath.abs(pb.getFinalTime() - pb.getInitialTime()) * 0.001;
233 
234     FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
235     TestProblemHandler handler = new TestProblemHandler(pb, integ);
236     integ.addStepHandler(handler);
237     integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
238                     pb.getFinalTime(), new double[pb.getDimension()]);
239 
240     Assert.assertTrue(handler.getLastError() < 5.0e-10);
241     Assert.assertTrue(handler.getMaximalValueError() < 7.0e-10);
242     Assert.assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
243     Assert.assertEquals("classical Runge-Kutta", integ.getName());
244   }
245 
246   @Test
247   public void testKepler()
248       throws DimensionMismatchException, NumberIsTooSmallException,
249              MaxCountExceededException, NoBracketingException {
250 
251     final TestProblem3 pb  = new TestProblem3(0.9);
252     double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003;
253 
254     FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
255     integ.addStepHandler(new KeplerHandler(pb));
256     integ.integrate(pb,
257                     pb.getInitialTime(), pb.getInitialState(),
258                     pb.getFinalTime(), new double[pb.getDimension()]);
259   }
260 
261   private static final class KeplerHandler implements StepHandler {
262     KeplerHandler(TestProblem3 pb) {
263       this.pb = pb;
264       maxError = 0;
265     }
266     @Override
267     public void init(double t0, double[] y0, double t) {
268       maxError = 0;
269     }
270     @Override
271     public void handleStep(StepInterpolator interpolator, boolean isLast)
272         throws MaxCountExceededException {
273 
274       double[] interpolatedY = interpolator.getInterpolatedState ();
275       double[] theoreticalY  = pb.computeTheoreticalState(interpolator.getCurrentTime());
276       double dx = interpolatedY[0] - theoreticalY[0];
277       double dy = interpolatedY[1] - theoreticalY[1];
278       double error = dx * dx + dy * dy;
279       if (error > maxError) {
280         maxError = error;
281       }
282       if (isLast) {
283         // even with more than 1000 evaluations per period,
284         // RK4 is not able to integrate such an eccentric
285         // orbit with a good accuracy
286         Assert.assertTrue(maxError > 0.005);
287       }
288     }
289     private double maxError = 0;
290     private TestProblem3 pb;
291   }
292 
293   @Test
294   public void testStepSize()
295       throws DimensionMismatchException, NumberIsTooSmallException,
296              MaxCountExceededException, NoBracketingException {
297       final double step = 1.23456;
298       FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
299       integ.addStepHandler(new StepHandler() {
300           @Override
301         public void handleStep(StepInterpolator interpolator, boolean isLast) {
302               if (! isLast) {
303                   Assert.assertEquals(step,
304                                interpolator.getCurrentTime() - interpolator.getPreviousTime(),
305                                1.0e-12);
306               }
307           }
308           @Override
309         public void init(double t0, double[] y0, double t) {
310           }
311       });
312       integ.integrate(new FirstOrderDifferentialEquations() {
313           @Override
314         public void computeDerivatives(double t, double[] y, double[] dot) {
315               dot[0] = 1.0;
316           }
317           @Override
318         public int getDimension() {
319               return 1;
320           }
321       }, 0.0, new double[] { 0.0 }, 5.0, new double[1]);
322   }
323 
324   @Test
325   public void testTooLargeFirstStep() {
326 
327       RungeKuttaIntegrator integ = new ClassicalRungeKuttaIntegrator(0.5);
328       final double start = 0.0;
329       final double end   = 0.001;
330       FirstOrderDifferentialEquations equations = new FirstOrderDifferentialEquations() {
331 
332           @Override
333         public int getDimension() {
334               return 1;
335           }
336 
337           @Override
338         public void computeDerivatives(double t, double[] y, double[] yDot) {
339               Assert.assertTrue(t >= JdkMath.nextAfter(start, Double.NEGATIVE_INFINITY));
340               Assert.assertTrue(t <= JdkMath.nextAfter(end,   Double.POSITIVE_INFINITY));
341               yDot[0] = -100.0 * y[0];
342           }
343       };
344 
345       integ.integrate(equations, start, new double[] { 1.0 }, end, new double[1]);
346   }
347 }