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.core.Field;
22  import org.apache.commons.math4.legacy.core.RealFieldElement;
23  import org.apache.commons.math4.legacy.analysis.differentiation.DerivativeStructure;
24  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
25  import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
26  import org.apache.commons.math4.legacy.exception.NoBracketingException;
27  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
28  import org.apache.commons.math4.legacy.ode.FieldExpandableODE;
29  import org.apache.commons.math4.legacy.ode.FirstOrderFieldDifferentialEquations;
30  import org.apache.commons.math4.legacy.ode.FirstOrderFieldIntegrator;
31  import org.apache.commons.math4.legacy.ode.FieldODEState;
32  import org.apache.commons.math4.legacy.ode.FieldODEStateAndDerivative;
33  import org.apache.commons.math4.legacy.ode.TestFieldProblem1;
34  import org.apache.commons.math4.legacy.ode.TestFieldProblem3;
35  import org.apache.commons.math4.legacy.ode.TestFieldProblem4;
36  import org.apache.commons.math4.legacy.ode.TestFieldProblem5;
37  import org.apache.commons.math4.legacy.ode.TestFieldProblemHandler;
38  import org.apache.commons.math4.legacy.ode.events.Action;
39  import org.apache.commons.math4.legacy.ode.events.FieldEventHandler;
40  import org.apache.commons.math4.legacy.ode.sampling.FieldStepHandler;
41  import org.apache.commons.math4.legacy.ode.sampling.FieldStepInterpolator;
42  import org.apache.commons.math4.core.jdkmath.JdkMath;
43  import org.apache.commons.math4.legacy.core.MathArrays;
44  import org.junit.Assert;
45  import org.junit.Test;
46  
47  public abstract class AbstractEmbeddedRungeKuttaFieldIntegratorTest {
48  
49      protected abstract <T extends RealFieldElement<T>> EmbeddedRungeKuttaFieldIntegrator<T>
50      createIntegrator(Field<T> field, double minStep, double maxStep,
51                       double scalAbsoluteTolerance, double scalRelativeTolerance);
52  
53      protected abstract <T extends RealFieldElement<T>> EmbeddedRungeKuttaFieldIntegrator<T>
54      createIntegrator(Field<T> field, double minStep, double maxStep,
55                       double[] vecAbsoluteTolerance, double[] vecRelativeTolerance);
56  
57      @Test
58      public abstract void testNonFieldIntegratorConsistency();
59  
60      protected <T extends RealFieldElement<T>> void doTestNonFieldIntegratorConsistency(final Field<T> field) {
61          try {
62  
63              // get the Butcher arrays from the field integrator
64              EmbeddedRungeKuttaFieldIntegrator<T> fieldIntegrator = createIntegrator(field, 0.001, 1.0, 1.0, 1.0);
65              T[][] fieldA = fieldIntegrator.getA();
66              T[]   fieldB = fieldIntegrator.getB();
67              T[]   fieldC = fieldIntegrator.getC();
68              if (fieldIntegrator instanceof DormandPrince853FieldIntegrator) {
69                  // special case for Dormand-Prince 8(5,3), the array in the regular
70                  // integrator is smaller because as of 3.X, the interpolation steps
71                  // are not performed by the integrator itself
72                  T[][] reducedFieldA = MathArrays.buildArray(field, 12, -1);
73                  T[]   reducedFieldB = MathArrays.buildArray(field, 13);
74                  T[]   reducedFieldC = MathArrays.buildArray(field, 12);
75                  System.arraycopy(fieldA, 0, reducedFieldA, 0, reducedFieldA.length);
76                  System.arraycopy(fieldB, 0, reducedFieldB, 0, reducedFieldB.length);
77                  System.arraycopy(fieldC, 0, reducedFieldC, 0, reducedFieldC.length);
78                  fieldA = reducedFieldA;
79                  fieldB = reducedFieldB;
80                  fieldC = reducedFieldC;
81              }
82  
83              String fieldName   = fieldIntegrator.getClass().getName();
84              String regularName = fieldName.replaceAll("Field", "");
85  
86              // get the Butcher arrays from the regular integrator
87              @SuppressWarnings("unchecked")
88              Class<RungeKuttaIntegrator> c = (Class<RungeKuttaIntegrator>) Class.forName(regularName);
89              java.lang.reflect.Field jlrFieldA = c.getDeclaredField("STATIC_A");
90              jlrFieldA.setAccessible(true);
91              double[][] regularA = (double[][]) jlrFieldA.get(null);
92              java.lang.reflect.Field jlrFieldB = c.getDeclaredField("STATIC_B");
93              jlrFieldB.setAccessible(true);
94              double[]   regularB = (double[])   jlrFieldB.get(null);
95              java.lang.reflect.Field jlrFieldC = c.getDeclaredField("STATIC_C");
96              jlrFieldC.setAccessible(true);
97              double[]   regularC = (double[])   jlrFieldC.get(null);
98  
99              Assert.assertEquals(regularA.length, fieldA.length);
100             for (int i = 0; i < regularA.length; ++i) {
101                 checkArray(regularA[i], fieldA[i]);
102             }
103             checkArray(regularB, fieldB);
104             checkArray(regularC, fieldC);
105         } catch (ClassNotFoundException cnfe) {
106             Assert.fail(cnfe.getLocalizedMessage());
107         } catch (IllegalAccessException iae) {
108             Assert.fail(iae.getLocalizedMessage());
109         } catch (IllegalArgumentException iae) {
110             Assert.fail(iae.getLocalizedMessage());
111         } catch (SecurityException se) {
112             Assert.fail(se.getLocalizedMessage());
113         } catch (NoSuchFieldException nsfe) {
114             Assert.fail(nsfe.getLocalizedMessage());
115         }
116     }
117 
118     private <T extends RealFieldElement<T>> void checkArray(double[] regularArray, T[] fieldArray) {
119         Assert.assertEquals(regularArray.length, fieldArray.length);
120         for (int i = 0; i < regularArray.length; ++i) {
121             if (regularArray[i] == 0) {
122                 Assert.assertEquals(0.0, fieldArray[i].getReal(), 0.0);
123             } else {
124                 Assert.assertEquals(regularArray[i], fieldArray[i].getReal(), JdkMath.ulp(regularArray[i]));
125             }
126         }
127     }
128 
129     @Test
130     public abstract void testForwardBackwardExceptions();
131 
132     protected <T extends RealFieldElement<T>> void doTestForwardBackwardExceptions(final Field<T> field) {
133         FirstOrderFieldDifferentialEquations<T> equations = new FirstOrderFieldDifferentialEquations<T>() {
134 
135             @Override
136             public int getDimension() {
137                 return 1;
138             }
139 
140             @Override
141             public void init(T t0, T[] y0, T t) {
142             }
143 
144             @Override
145             public T[] computeDerivatives(T t, T[] y) {
146                 if (t.getReal() < -0.5) {
147                     throw new LocalException();
148                 } else {
149                     throw new RuntimeException("oops");
150                 }
151             }
152         };
153 
154         EmbeddedRungeKuttaFieldIntegrator<T> integrator = createIntegrator(field, 0.0, 1.0, 1.0e-10, 1.0e-10);
155 
156         try  {
157             integrator.integrate(new FieldExpandableODE<>(equations),
158                                  new FieldODEState<>(field.getOne().negate(),
159                                                       MathArrays.buildArray(field, 1)),
160                                  field.getZero());
161             Assert.fail("an exception should have been thrown");
162           } catch(LocalException de) {
163             // expected behavior
164           }
165 
166           try  {
167               integrator.integrate(new FieldExpandableODE<>(equations),
168                                    new FieldODEState<>(field.getZero(),
169                                                         MathArrays.buildArray(field, 1)),
170                                    field.getOne());
171                Assert.fail("an exception should have been thrown");
172           } catch(RuntimeException de) {
173             // expected behavior
174           }
175     }
176 
177     protected static class LocalException extends RuntimeException {
178         private static final long serialVersionUID = 20151208L;
179     }
180 
181     @Test(expected=NumberIsTooSmallException.class)
182     public abstract void testMinStep();
183 
184     protected <T extends RealFieldElement<T>> void doTestMinStep(final Field<T> field)
185         throws NumberIsTooSmallException {
186 
187         TestFieldProblem1<T> pb = new TestFieldProblem1<>(field);
188         double minStep = pb.getFinalTime().subtract(pb.getInitialState().getTime()).multiply(0.1).getReal();
189         double maxStep = pb.getFinalTime().subtract(pb.getInitialState().getTime()).getReal();
190         double[] vecAbsoluteTolerance = { 1.0e-15, 1.0e-16 };
191         double[] vecRelativeTolerance = { 1.0e-15, 1.0e-16 };
192 
193         FirstOrderFieldIntegrator<T> integ = createIntegrator(field, minStep, maxStep,
194                                                               vecAbsoluteTolerance, vecRelativeTolerance);
195         TestFieldProblemHandler<T> handler = new TestFieldProblemHandler<>(pb, integ);
196         integ.addStepHandler(handler);
197         integ.integrate(new FieldExpandableODE<>(pb), pb.getInitialState(), pb.getFinalTime());
198         Assert.fail("an exception should have been thrown");
199     }
200 
201     @Test
202     public abstract void testIncreasingTolerance();
203 
204     protected <T extends RealFieldElement<T>> void doTestIncreasingTolerance(final Field<T> field,
205                                                                              double factor,
206                                                                              double epsilon) {
207 
208         int previousCalls = Integer.MAX_VALUE;
209         for (int i = -12; i < -2; ++i) {
210             TestFieldProblem1<T> pb = new TestFieldProblem1<>(field);
211             double minStep = 0;
212             double maxStep = pb.getFinalTime().subtract(pb.getInitialState().getTime()).getReal();
213             double scalAbsoluteTolerance = JdkMath.pow(10.0, i);
214             double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
215 
216             FirstOrderFieldIntegrator<T> integ = createIntegrator(field, minStep, maxStep,
217                                                                   scalAbsoluteTolerance, scalRelativeTolerance);
218             TestFieldProblemHandler<T> handler = new TestFieldProblemHandler<>(pb, integ);
219             integ.addStepHandler(handler);
220             integ.integrate(new FieldExpandableODE<>(pb), pb.getInitialState(), pb.getFinalTime());
221 
222             Assert.assertTrue(handler.getMaximalValueError().getReal() < (factor * scalAbsoluteTolerance));
223             Assert.assertEquals(0, handler.getMaximalTimeError().getReal(), epsilon);
224 
225             int calls = pb.getCalls();
226             Assert.assertEquals(integ.getEvaluations(), calls);
227             Assert.assertTrue(calls <= previousCalls);
228             previousCalls = calls;
229         }
230     }
231 
232     @Test
233     public abstract void testEvents();
234 
235     protected <T extends RealFieldElement<T>> void doTestEvents(final Field<T> field,
236                                                                 final double epsilonMaxValue,
237                                                                 final String name) {
238 
239       TestFieldProblem4<T> pb = new TestFieldProblem4<>(field);
240       double minStep = 0;
241       double maxStep = pb.getFinalTime().subtract(pb.getInitialState().getTime()).getReal();
242       double scalAbsoluteTolerance = 1.0e-8;
243       double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
244 
245       FirstOrderFieldIntegrator<T> integ = createIntegrator(field, minStep, maxStep,
246                                                             scalAbsoluteTolerance, scalRelativeTolerance);
247       TestFieldProblemHandler<T> handler = new TestFieldProblemHandler<>(pb, integ);
248       integ.addStepHandler(handler);
249       FieldEventHandler<T>[] functions = pb.getEventsHandlers();
250       double convergence = 1.0e-8 * maxStep;
251       for (int l = 0; l < functions.length; ++l) {
252           integ.addEventHandler(functions[l], Double.POSITIVE_INFINITY, convergence, 1000);
253       }
254       Assert.assertEquals(functions.length, integ.getEventHandlers().size());
255       integ.integrate(new FieldExpandableODE<>(pb), pb.getInitialState(), pb.getFinalTime());
256 
257       Assert.assertEquals(0, handler.getMaximalValueError().getReal(), epsilonMaxValue);
258       Assert.assertEquals(0, handler.getMaximalTimeError().getReal(), convergence);
259       Assert.assertEquals(12.0, handler.getLastTime().getReal(), convergence);
260       Assert.assertEquals(name, integ.getName());
261       integ.clearEventHandlers();
262       Assert.assertEquals(0, integ.getEventHandlers().size());
263     }
264 
265     @Test(expected=LocalException.class)
266     public abstract void testEventsErrors();
267 
268     protected <T extends RealFieldElement<T>> void doTestEventsErrors(final Field<T> field)
269         throws LocalException {
270         final TestFieldProblem1<T> pb = new TestFieldProblem1<>(field);
271         double minStep = 0;
272         double maxStep = pb.getFinalTime().subtract(pb.getInitialState().getTime()).getReal();
273         double scalAbsoluteTolerance = 1.0e-8;
274         double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
275 
276         FirstOrderFieldIntegrator<T> integ = createIntegrator(field, minStep, maxStep,
277                                                               scalAbsoluteTolerance, scalRelativeTolerance);
278         TestFieldProblemHandler<T> handler = new TestFieldProblemHandler<>(pb, integ);
279         integ.addStepHandler(handler);
280 
281         integ.addEventHandler(new FieldEventHandler<T>() {
282           @Override
283         public void init(FieldODEStateAndDerivative<T> state0, T t) {
284           }
285           @Override
286         public Action eventOccurred(FieldODEStateAndDerivative<T> state, boolean increasing) {
287             return Action.CONTINUE;
288           }
289           @Override
290         public T g(FieldODEStateAndDerivative<T> state) {
291             T middle = pb.getInitialState().getTime().add(pb.getFinalTime()).multiply(0.5);
292             T offset = state.getTime().subtract(middle);
293             if (offset.getReal() > 0) {
294               throw new LocalException();
295             }
296             return offset;
297           }
298           @Override
299         public FieldODEState<T> resetState(FieldODEStateAndDerivative<T> state) {
300               return state;
301           }
302         }, Double.POSITIVE_INFINITY, 1.0e-8 * maxStep, 1000);
303 
304         integ.integrate(new FieldExpandableODE<>(pb), pb.getInitialState(), pb.getFinalTime());
305     }
306 
307     @Test
308     public abstract void testEventsNoConvergence();
309 
310     protected <T extends RealFieldElement<T>> void doTestEventsNoConvergence(final Field<T> field){
311 
312         final TestFieldProblem1<T> pb = new TestFieldProblem1<>(field);
313         double minStep = 0;
314         double maxStep = pb.getFinalTime().subtract(pb.getInitialState().getTime()).getReal();
315         double scalAbsoluteTolerance = 1.0e-8;
316         double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
317 
318         FirstOrderFieldIntegrator<T> integ = createIntegrator(field, minStep, maxStep,
319                                                               scalAbsoluteTolerance, scalRelativeTolerance);
320         TestFieldProblemHandler<T> handler = new TestFieldProblemHandler<>(pb, integ);
321         integ.addStepHandler(handler);
322 
323         integ.addEventHandler(new FieldEventHandler<T>() {
324             @Override
325             public void init(FieldODEStateAndDerivative<T> state0, T t) {
326             }
327             @Override
328             public Action eventOccurred(FieldODEStateAndDerivative<T> state, boolean increasing) {
329                 return Action.CONTINUE;
330             }
331             @Override
332             public T g(FieldODEStateAndDerivative<T> state) {
333                 T middle = pb.getInitialState().getTime().add(pb.getFinalTime()).multiply(0.5);
334                 T offset = state.getTime().subtract(middle);
335                 return (offset.getReal() > 0) ? offset.add(0.5) : offset.subtract(0.5);
336             }
337             @Override
338             public FieldODEState<T> resetState(FieldODEStateAndDerivative<T> state) {
339                 return state;
340             }
341         }, Double.POSITIVE_INFINITY, 1.0e-8 * maxStep, 3);
342 
343         try {
344             integ.integrate(new FieldExpandableODE<>(pb), pb.getInitialState(), pb.getFinalTime());
345             Assert.fail("an exception should have been thrown");
346         } catch (MaxCountExceededException mcee) {
347             // Expected.
348         }
349     }
350 
351     @Test
352     public abstract void testSanityChecks();
353 
354     protected <T extends RealFieldElement<T>> void doTestSanityChecks(Field<T> field) {
355         TestFieldProblem3<T> pb = new TestFieldProblem3<>(field);
356         try  {
357             EmbeddedRungeKuttaFieldIntegrator<T> integrator = createIntegrator(field, 0,
358                                                                                pb.getFinalTime().subtract(pb.getInitialState().getTime()).getReal(),
359                                                                                new double[4], new double[4]);
360             integrator.integrate(new FieldExpandableODE<>(pb),
361                                  new FieldODEState<>(pb.getInitialState().getTime(),
362                                                       MathArrays.buildArray(field, 6)),
363                                  pb.getFinalTime());
364             Assert.fail("an exception should have been thrown");
365         } catch(DimensionMismatchException ie) {
366         }
367         try  {
368             EmbeddedRungeKuttaFieldIntegrator<T> integrator =
369                             createIntegrator(field, 0,
370                                              pb.getFinalTime().subtract(pb.getInitialState().getTime()).getReal(),
371                                              new double[2], new double[4]);
372             integrator.integrate(new FieldExpandableODE<>(pb), pb.getInitialState(), pb.getFinalTime());
373             Assert.fail("an exception should have been thrown");
374         } catch(DimensionMismatchException ie) {
375         }
376         try  {
377             EmbeddedRungeKuttaFieldIntegrator<T> integrator =
378                             createIntegrator(field, 0,
379                                              pb.getFinalTime().subtract(pb.getInitialState().getTime()).getReal(),
380                                              new double[4], new double[4]);
381             integrator.integrate(new FieldExpandableODE<>(pb), pb.getInitialState(), pb.getInitialState().getTime());
382             Assert.fail("an exception should have been thrown");
383         } catch(NumberIsTooSmallException ie) {
384         }
385     }
386 
387     @Test
388     public abstract void testBackward();
389 
390     protected <T extends RealFieldElement<T>> void doTestBackward(Field<T> field,
391                                                                   final double epsilonLast,
392                                                                   final double epsilonMaxValue,
393                                                                   final double epsilonMaxTime,
394                                                                   final String name)
395         throws DimensionMismatchException, NumberIsTooSmallException,
396                MaxCountExceededException, NoBracketingException {
397 
398         TestFieldProblem5<T> pb = new TestFieldProblem5<>(field);
399         double minStep = 0;
400         double maxStep = pb.getFinalTime().subtract(pb.getInitialState().getTime()).abs().getReal();
401         double scalAbsoluteTolerance = 1.0e-8;
402         double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
403 
404         EmbeddedRungeKuttaFieldIntegrator<T> integ = createIntegrator(field, minStep, maxStep,
405                                                                       scalAbsoluteTolerance,
406                                                                       scalRelativeTolerance);
407         TestFieldProblemHandler<T> handler = new TestFieldProblemHandler<>(pb, integ);
408         integ.addStepHandler(handler);
409         integ.integrate(new FieldExpandableODE<>(pb), pb.getInitialState(), pb.getFinalTime());
410 
411         Assert.assertEquals(0, handler.getLastError().getReal(),         epsilonLast);
412         Assert.assertEquals(0, handler.getMaximalValueError().getReal(), epsilonMaxValue);
413         Assert.assertEquals(0, handler.getMaximalTimeError().getReal(),  epsilonMaxTime);
414         Assert.assertEquals(name, integ.getName());
415     }
416 
417     @Test
418     public abstract void testKepler();
419 
420     protected <T extends RealFieldElement<T>> void doTestKepler(Field<T> field, double epsilon) {
421 
422         final TestFieldProblem3<T> pb  = new TestFieldProblem3<>(field, field.getZero().add(0.9));
423         double minStep = 0;
424         double maxStep = pb.getFinalTime().subtract(pb.getInitialState().getTime()).getReal();
425         double[] vecAbsoluteTolerance = { 1.0e-8, 1.0e-8, 1.0e-10, 1.0e-10 };
426         double[] vecRelativeTolerance = { 1.0e-10, 1.0e-10, 1.0e-8, 1.0e-8 };
427 
428         FirstOrderFieldIntegrator<T> integ = createIntegrator(field, minStep, maxStep,
429                                                               vecAbsoluteTolerance, vecRelativeTolerance);
430         integ.addStepHandler(new KeplerHandler<>(pb, epsilon));
431         integ.integrate(new FieldExpandableODE<>(pb), pb.getInitialState(), pb.getFinalTime());
432     }
433 
434     private static final class KeplerHandler<T extends RealFieldElement<T>> implements FieldStepHandler<T> {
435         private T maxError;
436         private final TestFieldProblem3<T> pb;
437         private final double epsilon;
438         KeplerHandler(TestFieldProblem3<T> pb, double epsilon) {
439             this.pb      = pb;
440             this.epsilon = epsilon;
441             maxError = pb.getField().getZero();
442         }
443         @Override
444         public void init(FieldODEStateAndDerivative<T> state0, T t) {
445             maxError = pb.getField().getZero();
446         }
447         @Override
448         public void handleStep(FieldStepInterpolator<T> interpolator, boolean isLast)
449                         throws MaxCountExceededException {
450 
451             FieldODEStateAndDerivative<T> current = interpolator.getCurrentState();
452             T[] theoreticalY  = pb.computeTheoreticalState(current.getTime());
453             T dx = current.getState()[0].subtract(theoreticalY[0]);
454             T dy = current.getState()[1].subtract(theoreticalY[1]);
455             T error = dx.multiply(dx).add(dy.multiply(dy));
456             if (error.subtract(maxError).getReal() > 0) {
457                 maxError = error;
458             }
459             if (isLast) {
460                 Assert.assertEquals(0.0, maxError.getReal(), epsilon);
461             }
462         }
463     }
464 
465     @Test
466     public abstract void testPartialDerivatives();
467 
468     protected <T extends RealFieldElement<T>> void doTestPartialDerivatives(final double epsilonY,
469                                                                             final double[] epsilonPartials) {
470 
471         // parameters indices
472         final int parameters = 5;
473         final int order      = 1;
474         final int parOmega   = 0;
475         final int parTO      = 1;
476         final int parY00     = 2;
477         final int parY01     = 3;
478         final int parT       = 4;
479 
480         DerivativeStructure omega = new DerivativeStructure(parameters, order, parOmega, 1.3);
481         DerivativeStructure t0    = new DerivativeStructure(parameters, order, parTO, 1.3);
482         DerivativeStructure[] y0  = new DerivativeStructure[] {
483             new DerivativeStructure(parameters, order, parY00, 3.0),
484             new DerivativeStructure(parameters, order, parY01, 4.0)
485         };
486         DerivativeStructure t     = new DerivativeStructure(parameters, order, parT, 6.0);
487         SinCos sinCos = new SinCos(omega);
488 
489         EmbeddedRungeKuttaFieldIntegrator<DerivativeStructure> integrator =
490                         createIntegrator(omega.getField(),
491                                          t.subtract(t0).multiply(0.001).getReal(), t.subtract(t0).getReal(),
492                                          1.0e-12, 1.0e-12);
493         FieldODEStateAndDerivative<DerivativeStructure> result =
494                         integrator.integrate(new FieldExpandableODE<>(sinCos),
495                                              new FieldODEState<>(t0, y0),
496                                              t);
497 
498         // check values
499         for (int i = 0; i < sinCos.getDimension(); ++i) {
500             Assert.assertEquals(sinCos.theoreticalY(t.getReal())[i], result.getState()[i].getValue(), epsilonY);
501         }
502 
503         // check derivatives
504         final double[][] derivatives = sinCos.getDerivatives(t.getReal());
505         for (int i = 0; i < sinCos.getDimension(); ++i) {
506             for (int parameter = 0; parameter < parameters; ++parameter) {
507                 Assert.assertEquals(derivatives[i][parameter], dYdP(result.getState()[i], parameter), epsilonPartials[parameter]);
508             }
509         }
510     }
511 
512     private double dYdP(final DerivativeStructure y, final int parameter) {
513         int[] orders = new int[y.getFreeParameters()];
514         orders[parameter] = 1;
515         return y.getPartialDerivative(orders);
516     }
517 
518     private static final class SinCos implements FirstOrderFieldDifferentialEquations<DerivativeStructure> {
519 
520         private final DerivativeStructure omega;
521         private       DerivativeStructure r;
522         private       DerivativeStructure alpha;
523 
524         private double dRdY00;
525         private double dRdY01;
526         private double dAlphadOmega;
527         private double dAlphadT0;
528         private double dAlphadY00;
529         private double dAlphadY01;
530 
531         protected SinCos(final DerivativeStructure omega) {
532             this.omega = omega;
533         }
534 
535         @Override
536         public int getDimension() {
537             return 2;
538         }
539 
540         @Override
541         public void init(final DerivativeStructure t0, final DerivativeStructure[] y0,
542                          final DerivativeStructure finalTime) {
543 
544             // theoretical solution is y(t) = { r * sin(omega * t + alpha), r * cos(omega * t + alpha) }
545             // so we retrieve alpha by identification from the initial state
546             final DerivativeStructure r2 = y0[0].multiply(y0[0]).add(y0[1].multiply(y0[1]));
547 
548             this.r            = r2.sqrt();
549             this.dRdY00       = y0[0].divide(r).getReal();
550             this.dRdY01       = y0[1].divide(r).getReal();
551 
552             this.alpha        = y0[0].atan2(y0[1]).subtract(t0.multiply(omega));
553             this.dAlphadOmega = -t0.getReal();
554             this.dAlphadT0    = -omega.getReal();
555             this.dAlphadY00   = y0[1].divide(r2).getReal();
556             this.dAlphadY01   = y0[0].negate().divide(r2).getReal();
557         }
558 
559         @Override
560         public DerivativeStructure[] computeDerivatives(final DerivativeStructure t, final DerivativeStructure[] y) {
561             return new DerivativeStructure[] {
562                 omega.multiply(y[1]),
563                 omega.multiply(y[0]).negate()
564             };
565         }
566 
567         public double[] theoreticalY(final double t) {
568             final double theta = omega.getReal() * t + alpha.getReal();
569             return new double[] {
570                 r.getReal() * JdkMath.sin(theta), r.getReal() * JdkMath.cos(theta)
571             };
572         }
573 
574         public double[][] getDerivatives(final double t) {
575 
576             // intermediate angle and state
577             final double theta        = omega.getReal() * t + alpha.getReal();
578             final double sin          = JdkMath.sin(theta);
579             final double cos          = JdkMath.cos(theta);
580             final double y0           = r.getReal() * sin;
581             final double y1           = r.getReal() * cos;
582 
583             // partial derivatives of the state first component
584             final double dY0dOmega    =                y1 * (t + dAlphadOmega);
585             final double dY0dT0       =                y1 * dAlphadT0;
586             final double dY0dY00      = dRdY00 * sin + y1 * dAlphadY00;
587             final double dY0dY01      = dRdY01 * sin + y1 * dAlphadY01;
588             final double dY0dT        =                y1 * omega.getReal();
589 
590             // partial derivatives of the state second component
591             final double dY1dOmega    =              - y0 * (t + dAlphadOmega);
592             final double dY1dT0       =              - y0 * dAlphadT0;
593             final double dY1dY00      = dRdY00 * cos - y0 * dAlphadY00;
594             final double dY1dY01      = dRdY01 * cos - y0 * dAlphadY01;
595             final double dY1dT        =              - y0 * omega.getReal();
596 
597             return new double[][] {
598                 { dY0dOmega, dY0dT0, dY0dY00, dY0dY01, dY0dT },
599                 { dY1dOmega, dY1dT0, dY1dY00, dY1dY01, dY1dT }
600             };
601         }
602     }
603 }