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    *      https://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  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  
23  package org.hipparchus.analysis.differentiation;
24  
25  import org.hipparchus.CalculusFieldElementAbstractTest;
26  import org.hipparchus.Field;
27  import org.hipparchus.UnitTestUtils;
28  import org.hipparchus.analysis.CalculusFieldMultivariateFunction;
29  import org.hipparchus.analysis.CalculusFieldMultivariateVectorFunction;
30  import org.hipparchus.analysis.polynomials.PolynomialFunction;
31  import org.hipparchus.exception.LocalizedCoreFormats;
32  import org.hipparchus.exception.MathIllegalArgumentException;
33  import org.hipparchus.random.RandomGenerator;
34  import org.hipparchus.random.Well1024a;
35  import org.hipparchus.random.Well19937a;
36  import org.hipparchus.util.ArithmeticUtils;
37  import org.hipparchus.util.Binary64Field;
38  import org.hipparchus.util.CombinatoricsUtils;
39  import org.hipparchus.util.FastMath;
40  import org.hipparchus.util.FieldSinCos;
41  import org.hipparchus.util.FieldSinhCosh;
42  import org.hipparchus.util.Precision;
43  import org.junit.jupiter.api.Test;
44  
45  import java.util.ArrayList;
46  import java.util.Arrays;
47  import java.util.HashMap;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.stream.IntStream;
51  
52  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
53  import static org.junit.jupiter.api.Assertions.assertEquals;
54  import static org.junit.jupiter.api.Assertions.assertNotEquals;
55  import static org.junit.jupiter.api.Assertions.assertSame;
56  import static org.junit.jupiter.api.Assertions.assertThrows;
57  import static org.junit.jupiter.api.Assertions.assertTrue;
58  import static org.junit.jupiter.api.Assertions.fail;
59  
60  /**
61   * Test for class {@link DerivativeStructure}.
62   */
63  public class DerivativeStructureTest extends CalculusFieldElementAbstractTest<DerivativeStructure> {
64  
65      @Override
66      protected DerivativeStructure build(final double x) {
67          return new DSFactory(2, 1).variable(0, x);
68      }
69  
70      @Test
71      void testWrongVariableIndex() {
72          assertThrows(MathIllegalArgumentException.class, () -> {
73              new DSFactory(3, 1).variable(3, 1.0);
74          });
75      }
76  
77      @Test
78      void testMissingOrders() {
79          assertThrows(MathIllegalArgumentException.class, () -> {
80              new DSFactory(3, 1).variable(0, 1.0).getPartialDerivative(0, 1);
81          });
82      }
83  
84      @Test
85      void testTooLargeOrder() {
86          assertThrows(MathIllegalArgumentException.class, () -> {
87              new DSFactory(3, 1).variable(0, 1.0).getPartialDerivative(1, 1, 2);
88          });
89      }
90  
91      @Test
92      void testVariableWithoutDerivative0() {
93          DerivativeStructure v = new DSFactory(1, 0).variable(0, 1.0);
94          assertEquals(1.0, v.getValue(), 1.0e-15);
95      }
96  
97      @Test
98      void testVariableWithoutDerivative1() {
99          assertThrows(MathIllegalArgumentException.class, () -> {
100             DerivativeStructure v = new DSFactory(1, 0).variable(0, 1.0);
101             assertEquals(1.0, v.getPartialDerivative(1), 1.0e-15);
102         });
103     }
104 
105     @Test
106     void testVariable() {
107         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
108             DSFactory factory = new DSFactory(3, maxOrder);
109             checkF0F1(factory.variable(0, 1.0), 1.0, 1.0, 0.0, 0.0);
110             checkF0F1(factory.variable(1, 2.0), 2.0, 0.0, 1.0, 0.0);
111             checkF0F1(factory.variable(2, 3.0), 3.0, 0.0, 0.0, 1.0);
112         }
113     }
114 
115     @Test
116     void testConstant() {
117         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
118             DSFactory factory = new DSFactory(3, maxOrder);
119             checkF0F1(factory.constant(FastMath.PI), FastMath.PI, 0.0, 0.0, 0.0);
120         }
121     }
122 
123     @Test
124     void testPrimitiveAdd() {
125         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
126             DSFactory factory = new DSFactory(3, maxOrder);
127             checkF0F1(factory.variable(0, 1.0).add(5), 6.0, 1.0, 0.0, 0.0);
128             checkF0F1(factory.variable(1, 2.0).add(5), 7.0, 0.0, 1.0, 0.0);
129             checkF0F1(factory.variable(2, 3.0).add(5), 8.0, 0.0, 0.0, 1.0);
130         }
131     }
132 
133     @Test
134     void testAdd() {
135         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
136             DSFactory factory = new DSFactory(3, maxOrder);
137             DerivativeStructure x = factory.variable(0, 1.0);
138             DerivativeStructure y = factory.variable(1, 2.0);
139             DerivativeStructure z = factory.variable(2, 3.0);
140             DerivativeStructure xyz = x.add(y.add(z));
141             checkF0F1(xyz, x.getValue() + y.getValue() + z.getValue(), 1.0, 1.0, 1.0);
142         }
143     }
144 
145     @Test
146     void testPrimitiveSubtract() {
147         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
148             DSFactory factory = new DSFactory(3, maxOrder);
149             checkF0F1(factory.variable(0, 1.0).subtract(5), -4.0, 1.0, 0.0, 0.0);
150             checkF0F1(factory.variable(1, 2.0).subtract(5), -3.0, 0.0, 1.0, 0.0);
151             checkF0F1(factory.variable(2, 3.0).subtract(5), -2.0, 0.0, 0.0, 1.0);
152         }
153     }
154 
155     @Test
156     void testSubtract() {
157         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
158             DSFactory factory = new DSFactory(3, maxOrder);
159             DerivativeStructure x = factory.variable(0, 1.0);
160             DerivativeStructure y = factory.variable(1, 2.0);
161             DerivativeStructure z = factory.variable(2, 3.0);
162             DerivativeStructure xyz = x.subtract(y.subtract(z));
163             checkF0F1(xyz, x.getValue() - (y.getValue() - z.getValue()), 1.0, -1.0, 1.0);
164         }
165     }
166 
167     @Test
168     void testPrimitiveMultiply() {
169         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
170             DSFactory factory = new DSFactory(3, maxOrder);
171             checkF0F1(factory.variable(0, 1.0).multiply(5),  5.0, 5.0, 0.0, 0.0);
172             checkF0F1(factory.variable(1, 2.0).multiply(5), 10.0, 0.0, 5.0, 0.0);
173             checkF0F1(factory.variable(2, 3.0).multiply(5), 15.0, 0.0, 0.0, 5.0);
174         }
175     }
176 
177     @Test
178     void testMultiply() {
179         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
180             DSFactory factory = new DSFactory(3, maxOrder);
181             DerivativeStructure x = factory.variable(0, 1.0);
182             DerivativeStructure y = factory.variable(1, 2.0);
183             DerivativeStructure z = factory.variable(2, 3.0);
184             DerivativeStructure xyz = x.multiply(y.multiply(z));
185             for (int i = 0; i <= maxOrder; ++i) {
186                 for (int j = 0; j <= maxOrder; ++j) {
187                     for (int k = 0; k <= maxOrder; ++k) {
188                         if (i + j + k <= maxOrder) {
189                             assertEquals((i == 0 ? x.getValue() : (i == 1 ? 1.0 : 0.0)) *
190                                                 (j == 0 ? y.getValue() : (j == 1 ? 1.0 : 0.0)) *
191                                                 (k == 0 ? z.getValue() : (k == 1 ? 1.0 : 0.0)),
192                                                 xyz.getPartialDerivative(i, j, k),
193                                                 1.0e-15);
194                         }
195                     }
196                 }
197             }
198         }
199     }
200 
201     @Test
202     void testNegate() {
203         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
204             DSFactory factory = new DSFactory(3, maxOrder);
205             checkF0F1(factory.variable(0, 1.0).negate(), -1.0, -1.0, 0.0, 0.0);
206             checkF0F1(factory.variable(1, 2.0).negate(), -2.0, 0.0, -1.0, 0.0);
207             checkF0F1(factory.variable(2, 3.0).negate(), -3.0, 0.0, 0.0, -1.0);
208         }
209     }
210 
211     @Test
212     void testReciprocal() {
213         for (double x = 0.1; x < 1.2; x += 0.1) {
214             DerivativeStructure r = new DSFactory(1, 6).variable(0, x).reciprocal();
215             assertEquals(1 / x, r.getValue(), 1.0e-15);
216             for (int i = 1; i < r.getOrder(); ++i) {
217                 double expected = ArithmeticUtils.pow(-1, i) * CombinatoricsUtils.factorial(i) /
218                                   FastMath.pow(x, i + 1);
219                 assertEquals(expected, r.getPartialDerivative(i), 1.0e-15 * FastMath.abs(expected));
220             }
221         }
222     }
223 
224     @Test
225     void testPow() {
226         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
227             DSFactory factory = new DSFactory(3, maxOrder);
228             for (int n = 0; n < 10; ++n) {
229 
230                 DerivativeStructure x = factory.variable(0, 1.0);
231                 DerivativeStructure y = factory.variable(1, 2.0);
232                 DerivativeStructure z = factory.variable(2, 3.0);
233                 List<DerivativeStructure> list = Arrays.asList(x, y, z,
234                                                                x.add(y).add(z),
235                                                                x.multiply(y).multiply(z));
236 
237                 if (n == 0) {
238                     for (DerivativeStructure ds : list) {
239                         checkEquals(ds.getField().getOne(), FastMath.pow(ds, n), 1.0e-15);
240                     }
241                 } else if (n == 1) {
242                     for (DerivativeStructure ds : list) {
243                         checkEquals(ds, FastMath.pow(ds, n), 1.0e-15);
244                     }
245                 } else {
246                     for (DerivativeStructure ds : list) {
247                         DerivativeStructure p = ds.getField().getOne();
248                         for (int i = 0; i < n; ++i) {
249                             p = p.multiply(ds);
250                         }
251                         checkEquals(p, FastMath.pow(ds, n), 1.0e-15);
252                     }
253                 }
254             }
255         }
256     }
257 
258     @Test
259     void testPowDoubleDS() {
260         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
261 
262             DSFactory factory = new DSFactory(3, maxOrder);
263             DerivativeStructure x = factory.variable(0, 0.1);
264             DerivativeStructure y = factory.variable(1, 0.2);
265             DerivativeStructure z = factory.variable(2, 0.3);
266             List<DerivativeStructure> list = Arrays.asList(x, y, z,
267                                                            x.add(y).add(z),
268                                                            x.multiply(y).multiply(z));
269 
270             for (DerivativeStructure ds : list) {
271                 // the special case a = 0 is included here
272                 for (double a : new double[] { 0.0, 0.1, 1.0, 2.0, 5.0 }) {
273                     DerivativeStructure reference = (a == 0) ?
274                                                     x.getField().getZero() :
275                                                     FastMath.pow(new DSFactory(3, maxOrder).constant(a), ds);
276                     DerivativeStructure result = DerivativeStructure.pow(a, ds);
277                     checkEquals(reference, result, 1.0e-15);
278                 }
279 
280             }
281 
282             // negative base: -1^x can be evaluated for integers only, so value is sometimes OK, derivatives are always NaN
283             DerivativeStructure negEvenInteger = DerivativeStructure.pow(-2.0, factory.variable(0, 2.0));
284             assertEquals(4.0, negEvenInteger.getValue(), 1.0e-15);
285             assertTrue(Double.isNaN(negEvenInteger.getPartialDerivative(1, 0, 0)));
286             DerivativeStructure negOddInteger = DerivativeStructure.pow(-2.0, factory.variable(0, 3.0));
287             assertEquals(-8.0, negOddInteger.getValue(), 1.0e-15);
288             assertTrue(Double.isNaN(negOddInteger.getPartialDerivative(1, 0, 0)));
289             DerivativeStructure negNonInteger = DerivativeStructure.pow(-2.0, factory.variable(0, 2.001));
290             assertTrue(Double.isNaN(negNonInteger.getValue()));
291             assertTrue(Double.isNaN(negNonInteger.getPartialDerivative(1, 0, 0)));
292 
293             DerivativeStructure zeroNeg = DerivativeStructure.pow(0.0, factory.variable(0, -1.0));
294             assertTrue(Double.isNaN(zeroNeg.getValue()));
295             assertTrue(Double.isNaN(zeroNeg.getPartialDerivative(1, 0, 0)));
296             DerivativeStructure posNeg = DerivativeStructure.pow(2.0, factory.variable(0, -2.0));
297             assertEquals(1.0 / 4.0, posNeg.getValue(), 1.0e-15);
298             assertEquals(FastMath.log(2.0) / 4.0, posNeg.getPartialDerivative(1, 0, 0), 1.0e-15);
299 
300             // very special case: a = 0 and power = 0
301             DerivativeStructure zeroZero = DerivativeStructure.pow(0.0, factory.variable(0, 0.0));
302 
303             // this should be OK for simple first derivative with one variable only ...
304             assertEquals(1.0, zeroZero.getValue(), 1.0e-15);
305             assertEquals(Double.NEGATIVE_INFINITY, zeroZero.getPartialDerivative(1, 0, 0), 1.0e-15);
306 
307             // the following checks show a LIMITATION of the current implementation
308             // we have no way to tell x is a pure linear variable x = 0
309             // we only say: "x is a structure with value = 0.0,
310             // first derivative with respect to x = 1.0, and all other derivatives
311             // (first order with respect to y and z and higher derivatives) all 0.0.
312             // We have function f(x) = a^x and x = 0 so we compute:
313             // f(0) = 1, f'(0) = ln(a), f''(0) = ln(a)^2. The limit of these values
314             // when a converges to 0 implies all derivatives keep switching between
315             // +infinity and -infinity.
316             //
317             // Function composition rule for first derivatives is:
318             // d[f(g(x,y,z))]/dy = f'(g(x,y,z)) * dg(x,y,z)/dy
319             // so given that in our case x represents g and does not depend
320             // on y or z, we have dg(x,y,z)/dy = 0
321             // applying the composition rules gives:
322             // d[f(g(x,y,z))]/dy = f'(g(x,y,z)) * dg(x,y,z)/dy
323             //                 = -infinity * 0
324             //                 = NaN
325             // if we knew x is really the x variable and not the identity
326             // function applied to x, we would not have computed f'(g(x,y,z)) * dg(x,y,z)/dy
327             // and we would have found that the result was 0 and not NaN
328             assertTrue(Double.isNaN(zeroZero.getPartialDerivative(0, 1, 0)));
329             assertTrue(Double.isNaN(zeroZero.getPartialDerivative(0, 0, 1)));
330 
331             // Function composition rule for second derivatives is:
332             // d2[f(g(x))]/dx2 = f''(g(x)) * [g'(x)]^2 + f'(g(x)) * g''(x)
333             // when function f is the a^x root and x = 0 we have:
334             // f(0) = 1, f'(0) = ln(a), f''(0) = ln(a)^2 which for a = 0 implies
335             // all derivatives keep switching between +infinity and -infinity
336             // so given that in our case x represents g, we have g(x) = 0,
337             // g'(x) = 1 and g''(x) = 0
338             // applying the composition rules gives:
339             // d2[f(g(x))]/dx2 = f''(g(x)) * [g'(x)]^2 + f'(g(x)) * g''(x)
340             //                 = +infinity * 1^2 + -infinity * 0
341             //                 = +infinity + NaN
342             //                 = NaN
343             // if we knew x is really the x variable and not the identity
344             // function applied to x, we would not have computed f'(g(x)) * g''(x)
345             // and we would have found that the result was +infinity and not NaN
346             if (maxOrder > 1) {
347                 assertTrue(Double.isNaN(zeroZero.getPartialDerivative(2, 0, 0)));
348                 assertTrue(Double.isNaN(zeroZero.getPartialDerivative(0, 2, 0)));
349                 assertTrue(Double.isNaN(zeroZero.getPartialDerivative(0, 0, 2)));
350                 assertTrue(Double.isNaN(zeroZero.getPartialDerivative(1, 1, 0)));
351                 assertTrue(Double.isNaN(zeroZero.getPartialDerivative(0, 1, 1)));
352                 assertTrue(Double.isNaN(zeroZero.getPartialDerivative(1, 1, 0)));
353             }
354 
355             // very special case: 0^0 where the power is a primitive
356             DerivativeStructure zeroDsZeroDouble = factory.variable(0, 0.0).pow(0.0);
357             boolean first = true;
358             for (final double d : zeroDsZeroDouble.getAllDerivatives()) {
359                 if (first) {
360                     assertEquals(1.0, d, Precision.EPSILON);
361                     first = false;
362                 } else {
363                     assertEquals(0.0, d, Precision.SAFE_MIN);
364                 }
365             }
366             DerivativeStructure zeroDsZeroInt = factory.variable(0, 0.0).pow(0);
367             first = true;
368             for (final double d : zeroDsZeroInt.getAllDerivatives()) {
369                 if (first) {
370                     assertEquals(1.0, d, Precision.EPSILON);
371                     first = false;
372                 } else {
373                     assertEquals(0.0, d, Precision.SAFE_MIN);
374                 }
375             }
376 
377             // 0^p with p smaller than 1.0
378             DerivativeStructure u = factory.variable(1, -0.0).pow(0.25);
379             for (int i0 = 0; i0 <= maxOrder; ++i0) {
380                 for (int i1 = 0; i1 <= maxOrder; ++i1) {
381                     for (int i2 = 0; i2 <= maxOrder; ++i2) {
382                         if (i0 + i1 + i2 <= maxOrder) {
383                             assertEquals(0.0, u.getPartialDerivative(i0, i1, i2), 1.0e-10);
384                         }
385                     }
386                 }
387             }
388         }
389 
390     }
391 
392     @Test
393     public void testScalb() {
394         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
395             DSFactory factory = new DSFactory(3, maxOrder);
396             DerivativeStructure x = factory.variable(0, 1.0);
397             DerivativeStructure y = factory.variable(1, 2.0);
398             DerivativeStructure z = factory.variable(2, 3.0);
399             DerivativeStructure xyz = x.multiply(y.multiply(z));
400             double s = 0.125;
401             for (int n = -3; n <= 3; ++n) {
402                 DerivativeStructure scaled = xyz.scalb(n);
403                 for (int i = 0; i <= maxOrder; ++i) {
404                     for (int j = 0; j <= maxOrder; ++j) {
405                         for (int k = 0; k <= maxOrder; ++k) {
406                             if (i + j + k <= maxOrder) {
407                                 assertEquals((i == 0 ? x.getValue() : (i == 1 ? 1.0 : 0.0)) *
408                                                     (j == 0 ? y.getValue() : (j == 1 ? 1.0 : 0.0)) *
409                                                     (k == 0 ? z.getValue() : (k == 1 ? 1.0 : 0.0)) *
410                                                     s,
411                                                     scaled.getPartialDerivative(i, j, k),
412                                                     1.0e-15);
413                             }
414                         }
415                     }
416                 }
417                 s *= 2;
418             }
419         }
420     }
421 
422     @Test
423     public void testUlp() {
424         final RandomGenerator random = new Well19937a(0x85d201920b5be954l);
425         for (int k = 0; k < 10000; ++k) {
426             int maxOrder = 1 + random.nextInt(5);
427             DSFactory factory = new DSFactory(3, maxOrder);
428             DerivativeStructure x = factory.variable(0, FastMath.scalb(2 * random.nextDouble() - 1, random.nextInt(600) - 300));
429             DerivativeStructure y = factory.variable(1, FastMath.scalb(2 * random.nextDouble() - 1, random.nextInt(600) - 300));
430             DerivativeStructure z = factory.variable(2, FastMath.scalb(2 * random.nextDouble() - 1, random.nextInt(600) - 300));
431             DerivativeStructure xyz = x.multiply(y.multiply(z));
432             DerivativeStructure ulp = xyz.ulp();
433             boolean first = true;
434             for (double d : ulp.getAllDerivatives()) {
435                 assertEquals(first ? FastMath.ulp(xyz.getValue()) : 0.0, d, 1.0e-15 * FastMath.ulp(xyz.getValue()));
436                 first = false;
437             }
438         }
439     }
440 
441     @Test
442     void testExpression() {
443         DSFactory factory = new DSFactory(3, 5);
444         double epsilon = 2.5e-13;
445         for (double x = 0; x < 2; x += 0.2) {
446             DerivativeStructure dsX = factory.variable(0, x);
447             for (double y = 0; y < 2; y += 0.2) {
448                 DerivativeStructure dsY = factory.variable(1, y);
449                 for (double z = 0; z >- 2; z -= 0.2) {
450                     DerivativeStructure dsZ = factory.variable(2, z);
451 
452                     // f(x, y, z) = x + 5 x y - 2 z + (8 z x - y)^3
453                     DerivativeStructure ds =
454                                     dsX.linearCombination(1, dsX,
455                                                           5, dsX.multiply(dsY),
456                                                          -2, dsZ,
457                                                           1, dsX.linearCombination(8, dsZ.multiply(dsX),
458                                                                                   -1, dsY).pow(3));
459                     DerivativeStructure dsOther =
460                                     dsX.linearCombination(1, dsX,
461                                                           5, dsX.multiply(dsY),
462                                                          -2, dsZ).add(dsX.linearCombination(8, dsZ.multiply(dsX),
463                                                                                            -1, dsY).pow(3));
464                     double f = x + 5 * x * y - 2 * z + FastMath.pow(8 * z * x - y, 3);
465                     assertEquals(f, ds.getValue(),
466                                         FastMath.abs(epsilon * f));
467                     assertEquals(f, dsOther.getValue(),
468                                         FastMath.abs(epsilon * f));
469 
470                     // df/dx = 1 + 5 y + 24 (8 z x - y)^2 z
471                     double dfdx = 1 + 5 * y + 24 * z * FastMath.pow(8 * z * x - y, 2);
472                     assertEquals(dfdx, ds.getPartialDerivative(1, 0, 0),
473                                         FastMath.abs(epsilon * dfdx));
474                     assertEquals(dfdx, dsOther.getPartialDerivative(1, 0, 0),
475                                         FastMath.abs(epsilon * dfdx));
476 
477                     // df/dxdy = 5 + 48 z*(y - 8 z x)
478                     double dfdxdy = 5 + 48 * z * (y - 8 * z * x);
479                     assertEquals(dfdxdy, ds.getPartialDerivative(1, 1, 0),
480                                         FastMath.abs(epsilon * dfdxdy));
481                     assertEquals(dfdxdy, dsOther.getPartialDerivative(1, 1, 0),
482                                         FastMath.abs(epsilon * dfdxdy));
483 
484                     // df/dxdydz = 48 (y - 16 z x)
485                     double dfdxdydz = 48 * (y - 16 * z * x);
486                     assertEquals(dfdxdydz, ds.getPartialDerivative(1, 1, 1),
487                                         FastMath.abs(epsilon * dfdxdydz));
488                     assertEquals(dfdxdydz, dsOther.getPartialDerivative(1, 1, 1),
489                                         FastMath.abs(epsilon * dfdxdydz));
490 
491                 }
492 
493             }
494         }
495     }
496 
497     @Test
498     void testCompositionOneVariableX() {
499         double epsilon = 1.0e-13;
500         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
501             DSFactory factory = new DSFactory(1, maxOrder);
502             for (double x = 0.1; x < 1.2; x += 0.1) {
503                 DerivativeStructure dsX = factory.variable(0, x);
504                 for (double y = 0.1; y < 1.2; y += 0.1) {
505                     DerivativeStructure dsY = factory.constant(y);
506                     DerivativeStructure f = dsX.divide(dsY).sqrt();
507                     double f0 = FastMath.sqrt(x / y);
508                     assertEquals(f0, f.getValue(), FastMath.abs(epsilon * f0));
509                     if (f.getOrder() > 0) {
510                         double f1 = 1 / (2 * FastMath.sqrt(x * y));
511                         assertEquals(f1, f.getPartialDerivative(1), FastMath.abs(epsilon * f1));
512                         if (f.getOrder() > 1) {
513                             double f2 = -f1 / (2 * x);
514                             assertEquals(f2, f.getPartialDerivative(2), FastMath.abs(epsilon * f2));
515                             if (f.getOrder() > 2) {
516                                 double f3 = (f0 + x / (2 * y * f0)) / (4 * x * x * x);
517                                 assertEquals(f3, f.getPartialDerivative(3), FastMath.abs(epsilon * f3));
518                             }
519                         }
520                     }
521                 }
522             }
523         }
524     }
525 
526     @Test
527     void testTrigo() {
528         double epsilon = 2.0e-12;
529         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
530             DSFactory factory = new DSFactory(3, maxOrder);
531             for (double x = 0.1; x < 1.2; x += 0.1) {
532                 DerivativeStructure dsX = factory.variable(0, x);
533                 for (double y = 0.1; y < 1.2; y += 0.1) {
534                     DerivativeStructure dsY = factory.variable(1, y);
535                     for (double z = 0.1; z < 1.2; z += 0.1) {
536                         DerivativeStructure dsZ = factory.variable(2, z);
537                         DerivativeStructure f = FastMath.sin(dsX.divide(FastMath.cos(dsY).add(FastMath.tan(dsZ))));
538                         double a = FastMath.cos(y) + FastMath.tan(z);
539                         double f0 = FastMath.sin(x / a);
540                         assertEquals(f0, f.getValue(), FastMath.abs(epsilon * f0));
541                         if (f.getOrder() > 0) {
542                             double dfdx = FastMath.cos(x / a) / a;
543                             assertEquals(dfdx, f.getPartialDerivative(1, 0, 0), FastMath.abs(epsilon * dfdx));
544                             double dfdy =  x * FastMath.sin(y) * dfdx / a;
545                             assertEquals(dfdy, f.getPartialDerivative(0, 1, 0), FastMath.abs(epsilon * dfdy));
546                             double cz = FastMath.cos(z);
547                             double cz2 = cz * cz;
548                             double dfdz = -x * dfdx / (a * cz2);
549                             assertEquals(dfdz, f.getPartialDerivative(0, 0, 1), FastMath.abs(epsilon * dfdz));
550                             if (f.getOrder() > 1) {
551                                 double df2dx2 = -(f0 / (a * a));
552                                 assertEquals(df2dx2, f.getPartialDerivative(2, 0, 0), FastMath.abs(epsilon * df2dx2));
553                                 double df2dy2 = x * FastMath.cos(y) * dfdx / a -
554                                                 x * x * FastMath.sin(y) * FastMath.sin(y) * f0 / (a * a * a * a) +
555                                                 2 * FastMath.sin(y) * dfdy / a;
556                                 assertEquals(df2dy2, f.getPartialDerivative(0, 2, 0), FastMath.abs(epsilon * df2dy2));
557                                 double c4 = cz2 * cz2;
558                                 double df2dz2 = x * (2 * a * (1 - a * cz * FastMath.sin(z)) * dfdx - x * f0 / a ) / (a * a * a * c4);
559                                 assertEquals(df2dz2, f.getPartialDerivative(0, 0, 2), FastMath.abs(epsilon * df2dz2));
560                                 double df2dxdy = dfdy / x  - x * FastMath.sin(y) * f0 / (a * a * a);
561                                 assertEquals(df2dxdy, f.getPartialDerivative(1, 1, 0), FastMath.abs(epsilon * df2dxdy));
562                             }
563                         }
564                     }
565                 }
566             }
567         }
568     }
569 
570     @Test
571     void testSqrtDefinition() {
572         double[] epsilon = new double[] { 5.0e-16, 5.0e-16, 2.7e-15, 5.7e-14, 2.0e-12 };
573         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
574             DSFactory factory = new DSFactory(1, maxOrder);
575             for (double x = 0.1; x < 1.2; x += 0.001) {
576                 DerivativeStructure dsX = factory.variable(0, x);
577                 DerivativeStructure sqrt1 = dsX.pow(0.5);
578                 DerivativeStructure sqrt2 = FastMath.sqrt(dsX);
579                 DerivativeStructure zero = sqrt1.subtract(sqrt2);
580                 for (int n = 0; n <= maxOrder; ++n) {
581                     assertEquals(0, zero.getPartialDerivative(n), epsilon[n]);
582                 }
583             }
584         }
585     }
586 
587     @Test
588     void testRootNSingularity() {
589         for (int n = 2; n < 10; ++n) {
590             for (int maxOrder = 0; maxOrder < 12; ++maxOrder) {
591                 DSFactory factory = new DSFactory(1, maxOrder);
592                 DerivativeStructure dsZero = factory.variable(0, 0.0);
593                 DerivativeStructure rootN  = dsZero.rootN(n);
594                 assertEquals(0.0, rootN.getValue(), 1.0e-20);
595                 if (maxOrder > 0) {
596                     assertTrue(Double.isInfinite(rootN.getPartialDerivative(1)));
597                     assertTrue(rootN.getPartialDerivative(1) > 0);
598                     for (int order = 2; order <= maxOrder; ++order) {
599                         // the following checks shows a LIMITATION of the current implementation
600                         // we have no way to tell dsZero is a pure linear variable x = 0
601                         // we only say: "dsZero is a structure with value = 0.0,
602                         // first derivative = 1.0, second and higher derivatives = 0.0".
603                         // Function composition rule for second derivatives is:
604                         // d2[f(g(x))]/dx2 = f''(g(x)) * [g'(x)]^2 + f'(g(x)) * g''(x)
605                         // when function f is the nth root and x = 0 we have:
606                         // f(0) = 0, f'(0) = +infinity, f''(0) = -infinity (and higher
607                         // derivatives keep switching between +infinity and -infinity)
608                         // so given that in our case dsZero represents g, we have g(x) = 0,
609                         // g'(x) = 1 and g''(x) = 0
610                         // applying the composition rules gives:
611                         // d2[f(g(x))]/dx2 = f''(g(x)) * [g'(x)]^2 + f'(g(x)) * g''(x)
612                         //                 = -infinity * 1^2 + +infinity * 0
613                         //                 = -infinity + NaN
614                         //                 = NaN
615                         // if we knew dsZero is really the x variable and not the identity
616                         // function applied to x, we would not have computed f'(g(x)) * g''(x)
617                         // and we would have found that the result was -infinity and not NaN
618                         assertTrue(Double.isNaN(rootN.getPartialDerivative(order)));
619                     }
620                 }
621 
622                 // the following shows that the limitation explained above is NOT a bug...
623                 // if we set up the higher order derivatives for g appropriately, we do
624                 // compute the higher order derivatives of the composition correctly
625                 double[] gDerivatives = new double[ 1 + maxOrder];
626                 gDerivatives[0] = 0.0;
627                 for (int k = 1; k <= maxOrder; ++k) {
628                     gDerivatives[k] = FastMath.pow(-1.0, k + 1);
629                 }
630                 DerivativeStructure correctRoot = factory.build(gDerivatives).rootN(n);
631                 assertEquals(0.0, correctRoot.getValue(), 1.0e-20);
632                 if (maxOrder > 0) {
633                     assertTrue(Double.isInfinite(correctRoot.getPartialDerivative(1)));
634                     assertTrue(correctRoot.getPartialDerivative(1) > 0);
635                     for (int order = 2; order <= maxOrder; ++order) {
636                         assertTrue(Double.isInfinite(correctRoot.getPartialDerivative(order)));
637                         if ((order % 2) == 0) {
638                             assertTrue(correctRoot.getPartialDerivative(order) < 0);
639                         } else {
640                             assertTrue(correctRoot.getPartialDerivative(order) > 0);
641                         }
642                     }
643                 }
644 
645             }
646 
647         }
648 
649     }
650 
651     @Test
652     void testSqrtPow2() {
653         double[] epsilon = new double[] { 1.0e-16, 3.0e-16, 2.0e-15, 6.0e-14, 6.0e-12 };
654         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
655             DSFactory factory = new DSFactory(1, maxOrder);
656             for (double x = 0.1; x < 1.2; x += 0.001) {
657                 DerivativeStructure dsX = factory.variable(0, x);
658                 DerivativeStructure rebuiltX = dsX.multiply(dsX).sqrt();
659                 DerivativeStructure zero = rebuiltX.subtract(dsX);
660                 for (int n = 0; n <= maxOrder; ++n) {
661                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
662                 }
663             }
664         }
665     }
666 
667     @Test
668     void testCbrtDefinition() {
669         double[] epsilon = new double[] { 4.0e-16, 9.0e-16, 6.0e-15, 2.0e-13, 4.0e-12 };
670         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
671             DSFactory factory = new DSFactory(1, maxOrder);
672             for (double x = 0.1; x < 1.2; x += 0.001) {
673                 DerivativeStructure dsX = factory.variable(0, x);
674                 DerivativeStructure cbrt1 = dsX.pow(1.0 / 3.0);
675                 DerivativeStructure cbrt2 = FastMath.cbrt(dsX);
676                 DerivativeStructure zero = cbrt1.subtract(cbrt2);
677                 for (int n = 0; n <= maxOrder; ++n) {
678                     assertEquals(0, zero.getPartialDerivative(n), epsilon[n]);
679                 }
680             }
681         }
682     }
683 
684     @Test
685     void testCbrtPow3() {
686         double[] epsilon = new double[] { 1.0e-16, 5.0e-16, 8.0e-15, 3.0e-13, 4.0e-11 };
687         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
688             DSFactory factory = new DSFactory(1, maxOrder);
689             for (double x = 0.1; x < 1.2; x += 0.001) {
690                 DerivativeStructure dsX = factory.variable(0, x);
691                 DerivativeStructure rebuiltX = dsX.multiply(dsX.multiply(dsX)).cbrt();
692                 DerivativeStructure zero = rebuiltX.subtract(dsX);
693                 for (int n = 0; n <= maxOrder; ++n) {
694                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
695                 }
696             }
697         }
698     }
699 
700     @Test
701     void testPowReciprocalPow() {
702         double[] epsilon = new double[] { 2.0e-15, 2.0e-14, 3.0e-13, 8.0e-12, 3.0e-10 };
703         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
704             DSFactory factory = new DSFactory(2, maxOrder);
705             for (double x = 0.1; x < 1.2; x += 0.01) {
706                 DerivativeStructure dsX = factory.variable(0, x);
707                 for (double y = 0.1; y < 1.2; y += 0.01) {
708                     DerivativeStructure dsY = factory.variable(1, y);
709                     DerivativeStructure rebuiltX = dsX.pow(dsY).pow(dsY.reciprocal());
710                     DerivativeStructure zero = rebuiltX.subtract(dsX);
711                     for (int n = 0; n <= maxOrder; ++n) {
712                         for (int m = 0; m <= maxOrder; ++m) {
713                             if (n + m <= maxOrder) {
714                                 assertEquals(0.0, zero.getPartialDerivative(n, m), epsilon[n + m]);
715                             }
716                         }
717                     }
718                 }
719             }
720         }
721     }
722 
723     @Test
724     void testHypotDefinition() {
725         double epsilon = 1.0e-20;
726         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
727             DSFactory factory = new DSFactory(2, maxOrder);
728             for (double x = -1.7; x < 2; x += 0.2) {
729                 DerivativeStructure dsX = factory.variable(0, x);
730                 for (double y = -1.7; y < 2; y += 0.2) {
731                     DerivativeStructure dsY = factory.variable(1, y);
732                     DerivativeStructure hypot = FastMath.hypot(dsY, dsX);
733                     DerivativeStructure ref = dsX.multiply(dsX).add(dsY.multiply(dsY)).sqrt();
734                     DerivativeStructure zero = hypot.subtract(ref);
735                     for (int n = 0; n <= maxOrder; ++n) {
736                         for (int m = 0; m <= maxOrder; ++m) {
737                             if (n + m <= maxOrder) {
738                                 assertEquals(0, zero.getPartialDerivative(n, m), epsilon);
739                             }
740                         }
741                     }
742                 }
743             }
744         }
745     }
746 
747     @Test
748     void testHypotNoOverflow() {
749 
750         DSFactory factory = new DSFactory(2, 5);
751         DerivativeStructure dsX = factory.variable(0, +3.0e250);
752         DerivativeStructure dsY = factory.variable(1, -4.0e250);
753         DerivativeStructure hypot = FastMath.hypot(dsX, dsY);
754         assertEquals(5.0e250, hypot.getValue(), 1.0e235);
755         assertEquals(dsX.getValue() / hypot.getValue(), hypot.getPartialDerivative(1, 0), 1.0e-10);
756         assertEquals(dsY.getValue() / hypot.getValue(), hypot.getPartialDerivative(0, 1), 1.0e-10);
757 
758         DerivativeStructure sqrt  = dsX.multiply(dsX).add(dsY.multiply(dsY)).sqrt();
759         assertTrue(Double.isInfinite(sqrt.getValue()));
760 
761     }
762 
763     @Test
764     void testHypotNeglectible() {
765 
766         DSFactory factory = new DSFactory(2, 5);
767         DerivativeStructure dsSmall = factory.variable(0, +3.0e-10);
768         DerivativeStructure dsLarge = factory.variable(1, -4.0e25);
769 
770         assertEquals(dsLarge.abs().getValue(),
771                             DerivativeStructure.hypot(dsSmall, dsLarge).getValue(),
772                             1.0e-10);
773         assertEquals(0,
774                             DerivativeStructure.hypot(dsSmall, dsLarge).getPartialDerivative(1, 0),
775                             1.0e-10);
776         assertEquals(-1,
777                             DerivativeStructure.hypot(dsSmall, dsLarge).getPartialDerivative(0, 1),
778                             1.0e-10);
779 
780         assertEquals(dsLarge.abs().getValue(),
781                             DerivativeStructure.hypot(dsLarge, dsSmall).getValue(),
782                             1.0e-10);
783         assertEquals(0,
784                             DerivativeStructure.hypot(dsLarge, dsSmall).getPartialDerivative(1, 0),
785                             1.0e-10);
786         assertEquals(-1,
787                             DerivativeStructure.hypot(dsLarge, dsSmall).getPartialDerivative(0, 1),
788                             1.0e-10);
789 
790     }
791 
792     @Test
793     void testHypotSpecial() {
794         DSFactory factory = new DSFactory(2, 5);
795         assertTrue(Double.isNaN(DerivativeStructure.hypot(factory.variable(0, Double.NaN),
796                                                                  factory.variable(0, +3.0e250)).getValue()));
797         assertTrue(Double.isNaN(DerivativeStructure.hypot(factory.variable(0, +3.0e250),
798                                                                  factory.variable(0, Double.NaN)).getValue()));
799         assertTrue(Double.isInfinite(DerivativeStructure.hypot(factory.variable(0, Double.POSITIVE_INFINITY),
800                                                                       factory.variable(0, +3.0e250)).getValue()));
801         assertTrue(Double.isInfinite(DerivativeStructure.hypot(factory.variable(0, +3.0e250),
802                                                                       factory.variable(0, Double.POSITIVE_INFINITY)).getValue()));
803     }
804 
805     @Test
806     void testPrimitiveRemainder() {
807         double epsilon = 1.0e-15;
808         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
809             DSFactory factory = new DSFactory(2, maxOrder);
810             for (double x = -1.7; x < 2; x += 0.2) {
811                 DerivativeStructure dsX = factory.variable(0, x);
812                 for (double y = -1.7; y < 2; y += 0.2) {
813                     DerivativeStructure remainder = FastMath.IEEEremainder(dsX, y);
814                     DerivativeStructure ref = dsX.subtract(x - FastMath.IEEEremainder(x, y));
815                     DerivativeStructure zero = remainder.subtract(ref);
816                     for (int n = 0; n <= maxOrder; ++n) {
817                         for (int m = 0; m <= maxOrder; ++m) {
818                             if (n + m <= maxOrder) {
819                                 assertEquals(0, zero.getPartialDerivative(n, m), epsilon);
820                             }
821                         }
822                     }
823                 }
824             }
825         }
826     }
827 
828     @Test
829     void testRemainder() {
830         double epsilon = 2.0e-15;
831         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
832             DSFactory factory = new DSFactory(2, maxOrder);
833             for (double x = -1.7; x < 2; x += 0.2) {
834                 DerivativeStructure dsX = factory.variable(0, x);
835                 for (double y = -1.7; y < 2; y += 0.2) {
836                     DerivativeStructure dsY = factory.variable(1, y);
837                     DerivativeStructure remainder = FastMath.IEEEremainder(dsX, dsY);
838                     DerivativeStructure ref = dsX.subtract(dsY.multiply((x - FastMath.IEEEremainder(x, y)) / y));
839                     DerivativeStructure zero = remainder.subtract(ref);
840                     for (int n = 0; n <= maxOrder; ++n) {
841                         for (int m = 0; m <= maxOrder; ++m) {
842                             if (n + m <= maxOrder) {
843                                 assertEquals(0, zero.getPartialDerivative(n, m), epsilon);
844                             }
845                         }
846                     }
847                 }
848             }
849         }
850     }
851 
852     @Override
853     @Test
854     public void testExp() {
855         double[] epsilon = new double[] { 1.0e-16, 1.0e-16, 1.0e-16, 1.0e-16, 1.0e-16 };
856         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
857             DSFactory factory = new DSFactory(1, maxOrder);
858             for (double x = 0.1; x < 1.2; x += 0.001) {
859                 double refExp = FastMath.exp(x);
860                 DerivativeStructure exp = FastMath.exp(factory.variable(0, x));
861                 for (int n = 0; n <= maxOrder; ++n) {
862                     assertEquals(refExp, exp.getPartialDerivative(n), epsilon[n]);
863                 }
864             }
865         }
866     }
867 
868     @Test
869     void testExpm1Definition() {
870         double epsilon = 3.0e-16;
871         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
872             DSFactory factory = new DSFactory(1, maxOrder);
873             for (double x = 0.1; x < 1.2; x += 0.001) {
874                 DerivativeStructure dsX = factory.variable(0, x);
875                 DerivativeStructure expm11 = FastMath.expm1(dsX);
876                 DerivativeStructure expm12 = dsX.exp().subtract(dsX.getField().getOne());
877                 DerivativeStructure zero = expm11.subtract(expm12);
878                 for (int n = 0; n <= maxOrder; ++n) {
879                     assertEquals(0, zero.getPartialDerivative(n), epsilon);
880                 }
881             }
882         }
883     }
884 
885     @Override
886     @Test
887     public void testLog() {
888         double[] epsilon = new double[] { 1.0e-16, 1.0e-16, 3.0e-14, 7.0e-13, 3.0e-11 };
889         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
890             DSFactory factory = new DSFactory(1, maxOrder);
891             for (double x = 0.1; x < 1.2; x += 0.001) {
892                 DerivativeStructure log = FastMath.log(factory.variable(0, x));
893                 assertEquals(FastMath.log(x), log.getValue(), epsilon[0]);
894                 for (int n = 1; n <= maxOrder; ++n) {
895                     double refDer = -CombinatoricsUtils.factorial(n - 1) / FastMath.pow(-x, n);
896                     assertEquals(refDer, log.getPartialDerivative(n), epsilon[n]);
897                 }
898             }
899         }
900     }
901 
902     @Test
903     void testLog1pDefinition() {
904         double epsilon = 3.0e-16;
905         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
906             for (double x = 0.1; x < 1.2; x += 0.001) {
907                 DSFactory factory = new DSFactory(1, maxOrder);
908                 DerivativeStructure dsX = factory.variable(0, x);
909                 DerivativeStructure log1p1 = FastMath.log1p(dsX);
910                 DerivativeStructure log1p2 = FastMath.log(dsX.add(dsX.getField().getOne()));
911                 DerivativeStructure zero = log1p1.subtract(log1p2);
912                 for (int n = 0; n <= maxOrder; ++n) {
913                     assertEquals(0, zero.getPartialDerivative(n), epsilon);
914                 }
915             }
916         }
917     }
918 
919     @Test
920     void testLog10Definition() {
921         double[] epsilon = new double[] { 3.0e-16, 9.0e-16, 8.0e-15, 3.0e-13, 8.0e-12 };
922         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
923             DSFactory factory = new DSFactory(1, maxOrder);
924             for (double x = 0.1; x < 1.2; x += 0.001) {
925                 DerivativeStructure dsX = factory.variable(0, x);
926                 DerivativeStructure log101 = FastMath.log10(dsX);
927                 DerivativeStructure log102 = dsX.log().divide(FastMath.log(10.0));
928                 DerivativeStructure zero = log101.subtract(log102);
929                 for (int n = 0; n <= maxOrder; ++n) {
930                     assertEquals(0, zero.getPartialDerivative(n), epsilon[n]);
931                 }
932             }
933         }
934     }
935 
936     @Test
937     void testLogExp() {
938         double[] epsilon = new double[] { 2.0e-16, 2.0e-16, 3.0e-16, 2.0e-15, 6.0e-15 };
939         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
940             DSFactory factory = new DSFactory(1, maxOrder);
941             for (double x = 0.1; x < 1.2; x += 0.001) {
942                 DerivativeStructure dsX = factory.variable(0, x);
943                 DerivativeStructure rebuiltX = dsX.exp().log();
944                 DerivativeStructure zero = rebuiltX.subtract(dsX);
945                 for (int n = 0; n <= maxOrder; ++n) {
946                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
947                 }
948             }
949         }
950     }
951 
952     @Test
953     void testLog1pExpm1() {
954         double[] epsilon = new double[] { 6.0e-17, 3.0e-16, 5.0e-16, 9.0e-16, 6.0e-15 };
955         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
956             DSFactory factory = new DSFactory(1, maxOrder);
957             for (double x = 0.1; x < 1.2; x += 0.001) {
958                 DerivativeStructure dsX = factory.variable(0, x);
959                 DerivativeStructure rebuiltX = dsX.expm1().log1p();
960                 DerivativeStructure zero = rebuiltX.subtract(dsX);
961                 for (int n = 0; n <= maxOrder; ++n) {
962                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
963                 }
964             }
965         }
966     }
967 
968     @Test
969     void testLog10Power() {
970         double[] epsilon = new double[] { 3.0e-16, 3.0e-16, 9.0e-16, 6.0e-15, 6.0e-14 };
971         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
972             DSFactory factory = new DSFactory(1, maxOrder);
973             for (double x = 0.1; x < 1.2; x += 0.001) {
974                 DerivativeStructure dsX = factory.variable(0, x);
975                 DerivativeStructure rebuiltX = factory.constant(10.0).pow(dsX).log10();
976                 DerivativeStructure zero = rebuiltX.subtract(dsX);
977                 for (int n = 0; n <= maxOrder; ++n) {
978                     assertEquals(0, zero.getPartialDerivative(n), epsilon[n]);
979                 }
980             }
981         }
982     }
983 
984     @Test
985     void testSinCosSeparated() {
986         double epsilon = 5.0e-16;
987         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
988             DSFactory factory = new DSFactory(1, maxOrder);
989             for (double x = 0.1; x < 1.2; x += 0.001) {
990                 DerivativeStructure dsX = factory.variable(0, x);
991                 DerivativeStructure sin = FastMath.sin(dsX);
992                 DerivativeStructure cos = FastMath.cos(dsX);
993                 double s = FastMath.sin(x);
994                 double c = FastMath.cos(x);
995                 for (int n = 0; n <= maxOrder; ++n) {
996                     switch (n % 4) {
997                     case 0 :
998                         assertEquals( s, sin.getPartialDerivative(n), epsilon);
999                         assertEquals( c, cos.getPartialDerivative(n), epsilon);
1000                         break;
1001                     case 1 :
1002                         assertEquals( c, sin.getPartialDerivative(n), epsilon);
1003                         assertEquals(-s, cos.getPartialDerivative(n), epsilon);
1004                         break;
1005                     case 2 :
1006                         assertEquals(-s, sin.getPartialDerivative(n), epsilon);
1007                         assertEquals(-c, cos.getPartialDerivative(n), epsilon);
1008                         break;
1009                     default :
1010                         assertEquals(-c, sin.getPartialDerivative(n), epsilon);
1011                         assertEquals( s, cos.getPartialDerivative(n), epsilon);
1012                         break;
1013                     }
1014                 }
1015             }
1016         }
1017     }
1018 
1019     @Test
1020     void testSinCosCombined() {
1021         double epsilon = 5.0e-16;
1022         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
1023             DSFactory factory = new DSFactory(1, maxOrder);
1024             for (double x = 0.1; x < 1.2; x += 0.001) {
1025                 DerivativeStructure dsX = factory.variable(0, x);
1026                 FieldSinCos<DerivativeStructure> sinCos = FastMath.sinCos(dsX);
1027                 double s = FastMath.sin(x);
1028                 double c = FastMath.cos(x);
1029                 for (int n = 0; n <= maxOrder; ++n) {
1030                     switch (n % 4) {
1031                     case 0 :
1032                         assertEquals( s, sinCos.sin().getPartialDerivative(n), epsilon);
1033                         assertEquals( c, sinCos.cos().getPartialDerivative(n), epsilon);
1034                         break;
1035                     case 1 :
1036                         assertEquals( c, sinCos.sin().getPartialDerivative(n), epsilon);
1037                         assertEquals(-s, sinCos.cos().getPartialDerivative(n), epsilon);
1038                         break;
1039                     case 2 :
1040                         assertEquals(-s, sinCos.sin().getPartialDerivative(n), epsilon);
1041                         assertEquals(-c, sinCos.cos().getPartialDerivative(n), epsilon);
1042                         break;
1043                     default :
1044                         assertEquals(-c, sinCos.sin().getPartialDerivative(n), epsilon);
1045                         assertEquals( s, sinCos.cos().getPartialDerivative(n), epsilon);
1046                         break;
1047                     }
1048                 }
1049             }
1050         }
1051     }
1052 
1053     @Test
1054     void testSinAsin() {
1055         double[] epsilon = new double[] { 3.0e-16, 5.0e-16, 3.0e-15, 2.0e-14, 4.0e-13 };
1056         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1057             DSFactory factory = new DSFactory(1, maxOrder);
1058             for (double x = 0.1; x < 1.2; x += 0.001) {
1059                 DerivativeStructure dsX = factory.variable(0, x);
1060                 DerivativeStructure rebuiltX = FastMath.asin(FastMath.sin(dsX));
1061                 DerivativeStructure zero = rebuiltX.subtract(dsX);
1062                 for (int n = 0; n <= maxOrder; ++n) {
1063                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
1064                 }
1065             }
1066         }
1067     }
1068 
1069     @Test
1070     void testCosAcos() {
1071         double[] epsilon = new double[] { 6.0e-16, 6.0e-15, 2.0e-13, 4.0e-12, 2.0e-10 };
1072         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1073             DSFactory factory = new DSFactory(1, maxOrder);
1074             for (double x = 0.1; x < 1.2; x += 0.001) {
1075                 DerivativeStructure dsX = factory.variable(0, x);
1076                 DerivativeStructure rebuiltX = FastMath.acos(FastMath.cos(dsX));
1077                 DerivativeStructure zero = rebuiltX.subtract(dsX);
1078                 for (int n = 0; n <= maxOrder; ++n) {
1079                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
1080                 }
1081             }
1082         }
1083     }
1084 
1085     @Test
1086     void testTanAtan() {
1087         double[] epsilon = new double[] { 6.0e-17, 2.0e-16, 2.0e-15, 4.0e-14, 2.0e-12 };
1088         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1089             DSFactory factory = new DSFactory(1, maxOrder);
1090             for (double x = 0.1; x < 1.2; x += 0.001) {
1091                 DerivativeStructure dsX = factory.variable(0, x);
1092                 DerivativeStructure rebuiltX = FastMath.atan(FastMath.tan(dsX));
1093                 DerivativeStructure zero = rebuiltX.subtract(dsX);
1094                 for (int n = 0; n <= maxOrder; ++n) {
1095                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
1096                 }
1097             }
1098         }
1099     }
1100 
1101     @Test
1102     void testTangentDefinition() {
1103         double[] epsilon = new double[] { 5.0e-16, 2.7e-15, 3.0e-14, 5.0e-13, 2.0e-11 };
1104         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1105             DSFactory factory = new DSFactory(1, maxOrder);
1106             for (double x = 0.1; x < 1.2; x += 0.001) {
1107                 DerivativeStructure dsX = factory.variable(0, x);
1108                 DerivativeStructure tan1 = dsX.sin().divide(dsX.cos());
1109                 DerivativeStructure tan2 = dsX.tan();
1110                 DerivativeStructure zero = tan1.subtract(tan2);
1111                 for (int n = 0; n <= maxOrder; ++n) {
1112                     assertEquals(0, zero.getPartialDerivative(n), epsilon[n]);
1113                 }
1114             }
1115         }
1116     }
1117 
1118     @Override
1119     @Test
1120     public void testAtan2() {
1121         double[] epsilon = new double[] { 5.0e-16, 3.0e-15, 2.9e-14, 1.0e-12, 8.0e-11 };
1122         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1123             DSFactory factory = new DSFactory(2, maxOrder);
1124             for (double x = -1.7; x < 2; x += 0.2) {
1125                 DerivativeStructure dsX = factory.variable(0, x);
1126                 for (double y = -1.7; y < 2; y += 0.2) {
1127                     DerivativeStructure dsY = factory.variable(1, y);
1128                     DerivativeStructure atan2 = FastMath.atan2(dsY, dsX);
1129                     DerivativeStructure ref = dsY.divide(dsX).atan();
1130                     if (x < 0) {
1131                         ref = (y < 0) ? ref.subtract(FastMath.PI) : ref.add(FastMath.PI);
1132                     }
1133                     DerivativeStructure zero = atan2.subtract(ref);
1134                     for (int n = 0; n <= maxOrder; ++n) {
1135                         for (int m = 0; m <= maxOrder; ++m) {
1136                             if (n + m <= maxOrder) {
1137                                 assertEquals(0, zero.getPartialDerivative(n, m), epsilon[n + m]);
1138                             }
1139                         }
1140                     }
1141                 }
1142             }
1143         }
1144     }
1145 
1146     @Test
1147     void testAtan2SpecialCasesDerivative() {
1148 
1149         DSFactory factory = new DSFactory(2, 2);
1150         DerivativeStructure pp =
1151                 DerivativeStructure.atan2(factory.variable(1, +0.0),
1152                                           factory.variable(1, +0.0));
1153         assertEquals(0, pp.getValue(), 1.0e-15);
1154         assertEquals(+1, FastMath.copySign(1, pp.getValue()), 1.0e-15);
1155 
1156         DerivativeStructure pn =
1157                 DerivativeStructure.atan2(factory.variable(1, +0.0),
1158                                           factory.variable(1, -0.0));
1159         assertEquals(FastMath.PI, pn.getValue(), 1.0e-15);
1160 
1161         DerivativeStructure np =
1162                 DerivativeStructure.atan2(factory.variable(1, -0.0),
1163                                           factory.variable(1, +0.0));
1164         assertEquals(0, np.getValue(), 1.0e-15);
1165         assertEquals(-1, FastMath.copySign(1, np.getValue()), 1.0e-15);
1166 
1167         DerivativeStructure nn =
1168                 DerivativeStructure.atan2(factory.variable(1, -0.0),
1169                                           factory.variable(1, -0.0));
1170         assertEquals(-FastMath.PI, nn.getValue(), 1.0e-15);
1171 
1172     }
1173 
1174     @Test
1175     void testSinhCoshCombined() {
1176         double epsilon = 5.0e-16;
1177         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
1178             DSFactory factory = new DSFactory(1, maxOrder);
1179             for (double x = 0.1; x < 1.2; x += 0.001) {
1180                 DerivativeStructure dsX = factory.variable(0, x);
1181                 FieldSinhCosh<DerivativeStructure> sinhCosh = FastMath.sinhCosh(dsX);
1182                 double sh = FastMath.sinh(x);
1183                 double ch = FastMath.cosh(x);
1184                 for (int n = 0; n <= maxOrder; ++n) {
1185                     if (n % 2 == 0) {
1186                         assertEquals(sh, sinhCosh.sinh().getPartialDerivative(n), epsilon);
1187                         assertEquals(ch, sinhCosh.cosh().getPartialDerivative(n), epsilon);
1188                     } else {
1189                         assertEquals(ch, sinhCosh.sinh().getPartialDerivative(n), epsilon);
1190                         assertEquals(sh, sinhCosh.cosh().getPartialDerivative(n), epsilon);
1191                     }
1192                 }
1193             }
1194         }
1195     }
1196 
1197     @Test
1198     void testSinhDefinition() {
1199         double[] epsilon = new double[] { 3.0e-16, 3.0e-16, 5.0e-16, 2.0e-15, 6.0e-15 };
1200         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1201             DSFactory factory = new DSFactory(1, maxOrder);
1202             for (double x = 0.1; x < 1.2; x += 0.001) {
1203                 DerivativeStructure dsX = factory.variable(0, x);
1204                 DerivativeStructure sinh1 = dsX.exp().subtract(dsX.exp().reciprocal()).multiply(0.5);
1205                 DerivativeStructure sinh2 = FastMath.sinh(dsX);
1206                 DerivativeStructure zero = sinh1.subtract(sinh2);
1207                 for (int n = 0; n <= maxOrder; ++n) {
1208                     assertEquals(0, zero.getPartialDerivative(n), epsilon[n]);
1209                 }
1210             }
1211         }
1212     }
1213 
1214     @Test
1215     void testCoshDefinition() {
1216         double[] epsilon = new double[] { 3.0e-16, 3.0e-16, 5.0e-16, 2.0e-15, 6.0e-15 };
1217         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1218             DSFactory factory = new DSFactory(1, maxOrder);
1219             for (double x = 0.1; x < 1.2; x += 0.001) {
1220                 DerivativeStructure dsX = factory.variable(0, x);
1221                 DerivativeStructure cosh1 = dsX.exp().add(dsX.exp().reciprocal()).multiply(0.5);
1222                 DerivativeStructure cosh2 = FastMath.cosh(dsX);
1223                 DerivativeStructure zero = cosh1.subtract(cosh2);
1224                 for (int n = 0; n <= maxOrder; ++n) {
1225                     assertEquals(0, zero.getPartialDerivative(n), epsilon[n]);
1226                 }
1227             }
1228         }
1229     }
1230 
1231     @Test
1232     void testTanhDefinition() {
1233         double[] epsilon = new double[] { 3.0e-16, 5.0e-16, 7.0e-16, 3.0e-15, 2.0e-14 };
1234         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1235             DSFactory factory = new DSFactory(1, maxOrder);
1236             for (double x = 0.1; x < 1.2; x += 0.001) {
1237                 DerivativeStructure dsX = factory.variable(0, x);
1238                 DerivativeStructure tanh1 = dsX.exp().subtract(dsX.exp().reciprocal()).divide(dsX.exp().add(dsX.exp().reciprocal()));
1239                 DerivativeStructure tanh2 = FastMath.tanh(dsX);
1240                 DerivativeStructure zero = tanh1.subtract(tanh2);
1241                 for (int n = 0; n <= maxOrder; ++n) {
1242                     assertEquals(0, zero.getPartialDerivative(n), epsilon[n]);
1243                 }
1244             }
1245         }
1246     }
1247 
1248     @Test
1249     void testSinhAsinh() {
1250         double[] epsilon = new double[] { 3.0e-16, 3.0e-16, 4.0e-16, 7.0e-16, 3.0e-15, 8.0e-15 };
1251         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
1252             DSFactory factory = new DSFactory(1, maxOrder);
1253             for (double x = 0.1; x < 1.2; x += 0.001) {
1254                 DerivativeStructure dsX = factory.variable(0, x);
1255                 DerivativeStructure rebuiltX = FastMath.asinh(dsX.sinh());
1256                 DerivativeStructure zero = rebuiltX.subtract(dsX);
1257                 for (int n = 0; n <= maxOrder; ++n) {
1258                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
1259                 }
1260             }
1261         }
1262     }
1263 
1264     @Test
1265     void testCoshAcosh() {
1266         double[] epsilon = new double[] { 2.0e-15, 1.0e-14, 2.0e-13, 6.0e-12, 3.0e-10, 2.0e-8 };
1267         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
1268             DSFactory factory = new DSFactory(1, maxOrder);
1269             for (double x = 0.1; x < 1.2; x += 0.001) {
1270                 DerivativeStructure dsX = factory.variable(0, x);
1271                 DerivativeStructure rebuiltX = FastMath.acosh(dsX.cosh());
1272                 DerivativeStructure zero = rebuiltX.subtract(dsX);
1273                 for (int n = 0; n <= maxOrder; ++n) {
1274                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
1275                 }
1276             }
1277         }
1278     }
1279 
1280     @Test
1281     void testTanhAtanh() {
1282         double[] epsilon = new double[] { 3.0e-16, 2.0e-16, 7.0e-16, 4.0e-15, 3.0e-14, 4.0e-13 };
1283         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
1284             DSFactory factory = new DSFactory(1, maxOrder);
1285             for (double x = 0.1; x < 1.2; x += 0.001) {
1286                 DerivativeStructure dsX = factory.variable(0, x);
1287                 DerivativeStructure rebuiltX = FastMath.atanh(dsX.tanh());
1288                 DerivativeStructure zero = rebuiltX.subtract(dsX);
1289                 for (int n = 0; n <= maxOrder; ++n) {
1290                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
1291                 }
1292             }
1293         }
1294     }
1295 
1296     @Test
1297     void testCompositionOneVariableY() {
1298         double epsilon = 1.0e-13;
1299         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1300             DSFactory factory = new DSFactory(1, maxOrder);
1301             for (double x = 0.1; x < 1.2; x += 0.1) {
1302                 DerivativeStructure dsX = factory.constant(x);
1303                 for (double y = 0.1; y < 1.2; y += 0.1) {
1304                     DerivativeStructure dsY = factory.variable(0, y);
1305                     DerivativeStructure f = dsX.divide(dsY).sqrt();
1306                     double f0 = FastMath.sqrt(x / y);
1307                     assertEquals(f0, f.getValue(), FastMath.abs(epsilon * f0));
1308                     if (f.getOrder() > 0) {
1309                         double f1 = -x / (2 * y * y * f0);
1310                         assertEquals(f1, f.getPartialDerivative(1), FastMath.abs(epsilon * f1));
1311                         if (f.getOrder() > 1) {
1312                             double f2 = (f0 - x / (4 * y * f0)) / (y * y);
1313                             assertEquals(f2, f.getPartialDerivative(2), FastMath.abs(epsilon * f2));
1314                             if (f.getOrder() > 2) {
1315                                 double f3 = (x / (8 * y * f0) - 2 * f0) / (y * y * y);
1316                                 assertEquals(f3, f.getPartialDerivative(3), FastMath.abs(epsilon * f3));
1317                             }
1318                         }
1319                     }
1320                 }
1321             }
1322         }
1323     }
1324 
1325     @Test
1326     void testTaylorPolynomial() {
1327         DSFactory factory = new DSFactory(3, 4);
1328         for (double x = 0; x < 1.2; x += 0.1) {
1329             DerivativeStructure dsX = factory.variable(0, x);
1330             for (double y = 0; y < 1.2; y += 0.2) {
1331                 DerivativeStructure dsY = factory.variable(1, y);
1332                 for (double z = 0; z < 1.2; z += 0.2) {
1333                     DerivativeStructure dsZ = factory.variable(2, z);
1334                     DerivativeStructure f = dsX.multiply(dsY).add(dsZ).multiply(dsX).multiply(dsY);
1335                     for (double dx = -0.2; dx < 0.2; dx += 0.2) {
1336                         for (double dy = -0.2; dy < 0.2; dy += 0.1) {
1337                             for (double dz = -0.2; dz < 0.2; dz += 0.1) {
1338                                 double ref = (x + dx) * (y + dy) * ((x + dx) * (y + dy) + (z + dz));
1339                                 assertEquals(ref, f.taylor(dx, dy, dz), 2.0e-15);
1340                             }
1341                         }
1342                     }
1343                 }
1344             }
1345         }
1346     }
1347 
1348     @Test
1349     void testTaylorAtan2() {
1350         double[] expected = new double[] { 0.214, 0.0241, 0.00422, 6.48e-4, 8.04e-5 };
1351         double x0 =  0.1;
1352         double y0 = -0.3;
1353         for (int maxOrder = 0; maxOrder < 5; ++maxOrder) {
1354             DSFactory factory = new DSFactory(2, maxOrder);
1355             DerivativeStructure dsX   = factory.variable(0, x0);
1356             DerivativeStructure dsY   = factory.variable(1, y0);
1357             DerivativeStructure atan2 = DerivativeStructure.atan2(dsY, dsX);
1358             double maxError = 0;
1359             for (double dx = -0.05; dx < 0.05; dx += 0.001) {
1360                 for (double dy = -0.05; dy < 0.05; dy += 0.001) {
1361                     double ref = FastMath.atan2(y0 + dy, x0 + dx);
1362                     maxError = FastMath.max(maxError, FastMath.abs(ref - atan2.taylor(dx, dy)));
1363                 }
1364             }
1365             assertEquals(0.0, expected[maxOrder] - maxError, 0.01 * expected[maxOrder]);
1366         }
1367     }
1368 
1369     @Test
1370     public void testAbs() {
1371 
1372         DSFactory factory = new DSFactory(1, 1);
1373         DerivativeStructure minusOne = factory.variable(0, -1.0);
1374         assertEquals(+1.0, FastMath.abs(minusOne).getPartialDerivative(0), 1.0e-15);
1375         assertEquals(-1.0, FastMath.abs(minusOne).getPartialDerivative(1), 1.0e-15);
1376 
1377         DerivativeStructure plusOne = factory.variable(0, +1.0);
1378         assertEquals(+1.0, FastMath.abs(plusOne).getPartialDerivative(0), 1.0e-15);
1379         assertEquals(+1.0, FastMath.abs(plusOne).getPartialDerivative(1), 1.0e-15);
1380 
1381         DerivativeStructure minusZero = factory.variable(0, -0.0);
1382         assertEquals(+0.0, FastMath.abs(minusZero).getPartialDerivative(0), 1.0e-15);
1383         assertEquals(-1.0, FastMath.abs(minusZero).getPartialDerivative(1), 1.0e-15);
1384 
1385         DerivativeStructure plusZero = factory.variable(0, +0.0);
1386         assertEquals(+0.0, FastMath.abs(plusZero).getPartialDerivative(0), 1.0e-15);
1387         assertEquals(+1.0, FastMath.abs(plusZero).getPartialDerivative(1), 1.0e-15);
1388 
1389     }
1390 
1391     @Override
1392     @Test
1393     public void testSign() {
1394 
1395         DSFactory factory = new DSFactory(1, 1);
1396         DerivativeStructure minusOne = factory.variable(0, -1.0);
1397         assertEquals(-1.0, FastMath.sign(minusOne).getPartialDerivative(0), 1.0e-15);
1398         assertEquals( 0.0, FastMath.sign(minusOne).getPartialDerivative(1), 1.0e-15);
1399 
1400         DerivativeStructure plusOne = factory.variable(0, +1.0);
1401         assertEquals(+1.0, FastMath.sign(plusOne).getPartialDerivative(0), 1.0e-15);
1402         assertEquals( 0.0, FastMath.sign(plusOne).getPartialDerivative(1), 1.0e-15);
1403 
1404         DerivativeStructure minusZero = factory.variable(0, -0.0);
1405         assertEquals(-0.0, FastMath.sign(minusZero).getPartialDerivative(0), 1.0e-15);
1406         assertTrue(Double.doubleToLongBits(FastMath.sign(minusZero).getValue()) < 0);
1407         assertEquals( 0.0, FastMath.sign(minusZero).getPartialDerivative(1), 1.0e-15);
1408 
1409         DerivativeStructure plusZero = factory.variable(0, +0.0);
1410         assertEquals(+0.0, FastMath.sign(plusZero).getPartialDerivative(0), 1.0e-15);
1411         assertEquals(0, Double.doubleToLongBits(FastMath.sign(plusZero).getValue()));
1412         assertEquals( 0.0, FastMath.sign(plusZero).getPartialDerivative(1), 1.0e-15);
1413 
1414     }
1415 
1416     @Test
1417     void testCeilFloorRintLong() {
1418 
1419         DSFactory factory = new DSFactory(1, 1);
1420         DerivativeStructure x = factory.variable(0, -1.5);
1421         assertEquals(-1.5, x.getPartialDerivative(0), 1.0e-15);
1422         assertEquals(+1.0, x.getPartialDerivative(1), 1.0e-15);
1423         assertEquals(-1.0, FastMath.ceil(x).getPartialDerivative(0), 1.0e-15);
1424         assertEquals(+0.0, FastMath.ceil(x).getPartialDerivative(1), 1.0e-15);
1425         assertEquals(-2.0, FastMath.floor(x).getPartialDerivative(0), 1.0e-15);
1426         assertEquals(+0.0, FastMath.floor(x).getPartialDerivative(1), 1.0e-15);
1427         assertEquals(-2.0, FastMath.rint(x).getPartialDerivative(0), 1.0e-15);
1428         assertEquals(+0.0, FastMath.rint(x).getPartialDerivative(1), 1.0e-15);
1429         assertEquals(-2.0, x.subtract(x.getField().getOne()).rint().getPartialDerivative(0), 1.0e-15);
1430 
1431     }
1432 
1433     @Test
1434     void testCopySign() {
1435 
1436         DSFactory factory = new DSFactory(1, 1);
1437         DerivativeStructure minusOne = factory.variable(0, -1.0);
1438         assertEquals(+1.0, FastMath.copySign(minusOne, +1.0).getPartialDerivative(0), 1.0e-15);
1439         assertEquals(-1.0, FastMath.copySign(minusOne, +1.0).getPartialDerivative(1), 1.0e-15);
1440         assertEquals(-1.0, FastMath.copySign(minusOne, -1.0).getPartialDerivative(0), 1.0e-15);
1441         assertEquals(+1.0, FastMath.copySign(minusOne, -1.0).getPartialDerivative(1), 1.0e-15);
1442         assertEquals(+1.0, FastMath.copySign(minusOne, +0.0).getPartialDerivative(0), 1.0e-15);
1443         assertEquals(-1.0, FastMath.copySign(minusOne, +0.0).getPartialDerivative(1), 1.0e-15);
1444         assertEquals(-1.0, FastMath.copySign(minusOne, -0.0).getPartialDerivative(0), 1.0e-15);
1445         assertEquals(+1.0, FastMath.copySign(minusOne, -0.0).getPartialDerivative(1), 1.0e-15);
1446         assertEquals(+1.0, FastMath.copySign(minusOne, Double.NaN).getPartialDerivative(0), 1.0e-15);
1447         assertEquals(-1.0, FastMath.copySign(minusOne, Double.NaN).getPartialDerivative(1), 1.0e-15);
1448 
1449         DerivativeStructure plusOne = factory.variable(0, +1.0);
1450         assertEquals(+1.0, FastMath.copySign(plusOne, factory.constant(+1.0)).getPartialDerivative(0), 1.0e-15);
1451         assertEquals(+1.0, FastMath.copySign(plusOne, factory.constant(+1.0)).getPartialDerivative(1), 1.0e-15);
1452         assertEquals(-1.0, FastMath.copySign(plusOne, factory.constant(-1.0)).getPartialDerivative(0), 1.0e-15);
1453         assertEquals(-1.0, FastMath.copySign(plusOne, factory.constant(-1.0)).getPartialDerivative(1), 1.0e-15);
1454         assertEquals(+1.0, FastMath.copySign(plusOne, factory.constant(+0.0)).getPartialDerivative(0), 1.0e-15);
1455         assertEquals(+1.0, FastMath.copySign(plusOne, factory.constant(+0.0)).getPartialDerivative(1), 1.0e-15);
1456         assertEquals(-1.0, FastMath.copySign(plusOne, factory.constant(-0.0)).getPartialDerivative(0), 1.0e-15);
1457         assertEquals(-1.0, FastMath.copySign(plusOne, factory.constant(-0.0)).getPartialDerivative(1), 1.0e-15);
1458         assertEquals(+1.0, FastMath.copySign(plusOne, factory.constant(Double.NaN)).getPartialDerivative(0), 1.0e-15);
1459         assertEquals(+1.0, FastMath.copySign(plusOne, factory.constant(Double.NaN)).getPartialDerivative(1), 1.0e-15);
1460 
1461     }
1462 
1463     @Test
1464     void testToDegreesDefinition() {
1465         double epsilon = 3.0e-16;
1466         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
1467             DSFactory factory = new DSFactory(1, maxOrder);
1468             for (double x = 0.1; x < 1.2; x += 0.001) {
1469                 DerivativeStructure dsX = factory.variable(0, x);
1470                 assertEquals(FastMath.toDegrees(x), dsX.toDegrees().getValue(), epsilon);
1471                 for (int n = 1; n <= maxOrder; ++n) {
1472                     if (n == 1) {
1473                         assertEquals(180 / FastMath.PI, dsX.toDegrees().getPartialDerivative(1), epsilon);
1474                     } else {
1475                         assertEquals(0.0, dsX.toDegrees().getPartialDerivative(n), epsilon);
1476                     }
1477                 }
1478             }
1479         }
1480     }
1481 
1482     @Test
1483     void testToRadiansDefinition() {
1484         double epsilon = 3.0e-16;
1485         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
1486             DSFactory factory = new DSFactory(1, maxOrder);
1487             for (double x = 0.1; x < 1.2; x += 0.001) {
1488                 DerivativeStructure dsX = factory.variable(0, x);
1489                 assertEquals(FastMath.toRadians(x), dsX.toRadians().getValue(), epsilon);
1490                 for (int n = 1; n <= maxOrder; ++n) {
1491                     if (n == 1) {
1492                         assertEquals(FastMath.PI / 180, dsX.toRadians().getPartialDerivative(1), epsilon);
1493                     } else {
1494                         assertEquals(0.0, dsX.toRadians().getPartialDerivative(n), epsilon);
1495                     }
1496                 }
1497             }
1498         }
1499     }
1500 
1501     @Test
1502     void testDegRad() {
1503         double epsilon = 3.0e-16;
1504         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
1505             DSFactory factory = new DSFactory(1, maxOrder);
1506             for (double x = 0.1; x < 1.2; x += 0.001) {
1507                 DerivativeStructure dsX = factory.variable(0, x);
1508                 DerivativeStructure rebuiltX = dsX.toDegrees().toRadians();
1509                 DerivativeStructure zero = rebuiltX.subtract(dsX);
1510                 for (int n = 0; n <= maxOrder; ++n) {
1511                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon);
1512                 }
1513             }
1514         }
1515     }
1516 
1517     @Test
1518     void testComposeMismatchedDimensions() {
1519         assertThrows(MathIllegalArgumentException.class, () -> {
1520             new DSFactory(1, 3).variable(0, 1.2).compose(new double[3]);
1521         });
1522     }
1523 
1524     @Test
1525     void testCompose() {
1526         double[] epsilon = new double[] { 1.0e-20, 5.0e-14, 2.0e-13, 3.0e-13, 2.0e-13, 1.0e-20 };
1527         PolynomialFunction poly =
1528                 new PolynomialFunction(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 });
1529         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
1530             DSFactory factory = new DSFactory(1, maxOrder);
1531             PolynomialFunction[] p = new PolynomialFunction[maxOrder + 1];
1532             p[0] = poly;
1533             for (int i = 1; i <= maxOrder; ++i) {
1534                 p[i] = p[i - 1].polynomialDerivative();
1535             }
1536             for (double x = 0.1; x < 1.2; x += 0.001) {
1537                 DerivativeStructure dsX = factory.variable(0, x);
1538                 DerivativeStructure dsY1 = dsX.getField().getZero();
1539                 for (int i = poly.degree(); i >= 0; --i) {
1540                     dsY1 = dsY1.multiply(dsX).add(poly.getCoefficients()[i]);
1541                 }
1542                 double[] f = new double[maxOrder + 1];
1543                 for (int i = 0; i < f.length; ++i) {
1544                     f[i] = p[i].value(x);
1545                 }
1546                 DerivativeStructure dsY2 = dsX.compose(f);
1547                 DerivativeStructure zero = dsY1.subtract(dsY2);
1548                 for (int n = 0; n <= maxOrder; ++n) {
1549                     assertEquals(0.0, zero.getPartialDerivative(n), epsilon[n]);
1550                 }
1551             }
1552         }
1553     }
1554 
1555     @Test
1556     void testIntegration() {
1557         // check that first-order integration on two variables does not depend on sequence of operations
1558         final RandomGenerator random = new Well19937a(0x87bb96d6e11557bdl);
1559         final DSFactory factory = new DSFactory(3, 7);
1560         final int size = factory.getCompiler().getSize();
1561         for (int count = 0; count < 100; ++count) {
1562             final double[] data = new double[size];
1563             for (int i = 0; i < size; i++) {
1564                 data[i] = random.nextDouble();
1565             }
1566             final DerivativeStructure f       = factory.build(data);
1567             final DerivativeStructure i2fIxIy = f.integrate(0, 1).integrate(1, 1);
1568             final DerivativeStructure i2fIyIx = f.integrate(1, 1).integrate(0, 1);
1569             checkEquals(i2fIxIy, i2fIyIx, 0.);
1570         }
1571     }
1572 
1573     @Test
1574     void testIntegrationGreaterThanOrder() {
1575         // check that integration to a too high order generates zero
1576         // as integration constants are set to zero
1577         final RandomGenerator random = new Well19937a(0x4744a847b11e4c6fl);
1578         final DSFactory factory = new DSFactory(3, 7);
1579         final int size = factory.getCompiler().getSize();
1580         for (int count = 0; count < 100; ++count) {
1581             final double[] data = new double[size];
1582             for (int i = 0; i < size; i++) {
1583                 data[i] = random.nextDouble();
1584             }
1585             final DerivativeStructure f = factory.build(data);
1586             for (int index = 0; index < factory.getCompiler().getFreeParameters(); ++index) {
1587                 final DerivativeStructure integ = f.integrate(index, factory.getCompiler().getOrder() + 1);
1588                 checkEquals(factory.constant(0), integ, 0.);
1589             }
1590         }
1591     }
1592 
1593     @Test
1594     void testIntegrationNoOp() {
1595         // check that integration of order 0 is no-op
1596         final RandomGenerator random = new Well19937a(0x75a35152f30f644bl);
1597         final DSFactory factory = new DSFactory(3, 7);
1598         final int size = factory.getCompiler().getSize();
1599         for (int count = 0; count < 100; ++count) {
1600             final double[] data = new double[size];
1601             for (int i = 0; i < size; i++) {
1602                 data[i] = random.nextDouble();
1603             }
1604             final DerivativeStructure f = factory.build(data);
1605             for (int index = 0; index < factory.getCompiler().getFreeParameters(); ++index) {
1606                 final DerivativeStructure integ = f.integrate(index, 0);
1607                 checkEquals(f, integ, 0.);
1608             }
1609         }
1610     }
1611 
1612     @Test
1613     void testDifferentiationNoOp() {
1614         // check that differentiation of order 0 is no-op
1615         final RandomGenerator random = new Well19937a(0x3b6ae4c2f1282949l);
1616         final DSFactory factory = new DSFactory(3, 7);
1617         final int size = factory.getCompiler().getSize();
1618         for (int count = 0; count < 100; ++count) {
1619             final double[] data = new double[size];
1620             for (int i = 0; i < size; i++) {
1621                 data[i] = random.nextDouble();
1622             }
1623             final DerivativeStructure f = factory.build(data);
1624             for (int index = 0; index < factory.getCompiler().getFreeParameters(); ++index) {
1625                 final DerivativeStructure integ = f.differentiate(index, 0);
1626                 checkEquals(f, integ, 0.);
1627             }
1628         }
1629     }
1630 
1631     @Test
1632     void testIntegrationDifferentiation() {
1633         // check that integration and differentiation for univariate functions are each other inverse except for constant
1634         // term and highest order one
1635         final RandomGenerator random = new Well19937a(0x67fe66c05e5ee222l);
1636         final DSFactory factory = new DSFactory(1, 25);
1637         final int size = factory.getCompiler().getSize();
1638         for (int count = 0; count < 100; ++count) {
1639             final double[] data = new double[size];
1640             for (int i = 1; i < size - 1; i++) {
1641                 data[i] = random.nextDouble();
1642             }
1643             final int indexVar = 0;
1644             final DerivativeStructure f = factory.build(data);
1645             final DerivativeStructure f2 = f.integrate(indexVar, 1).differentiate(indexVar, 1);
1646             final DerivativeStructure f3 = f.differentiate(indexVar, 1).integrate(indexVar, 1);
1647             checkEquals(f2, f, 0.);
1648             checkEquals(f2, f3, 0.);
1649             // check special case when non-positive integration order actually returns differentiation
1650             final DerivativeStructure df = f.integrate(indexVar, -1);
1651             final DerivativeStructure df2 = f.differentiate(indexVar, 1);
1652             checkEquals(df, df2, 0.);
1653             // check special case when non-positive differentiation order actually returns integration
1654             final DerivativeStructure fi  = f.differentiate(indexVar, -1);
1655             final DerivativeStructure fi2 = f.integrate(indexVar, 1);
1656             checkEquals(fi, fi2, 0.);
1657         }
1658     }
1659 
1660     @Test
1661     void testDifferentiation1() {
1662         // check differentiation operator with result obtained manually
1663         final int freeParam = 3;
1664         final int order = 5;
1665         final DSFactory factory = new DSFactory(freeParam, order);
1666         final DerivativeStructure f = factory.variable(0, 1.0);
1667         final int[] orders = new int[freeParam];
1668         orders[0] = 2;
1669         orders[1] = 1;
1670         orders[2] = 1;
1671         final double value = 10.;
1672         f.setDerivativeComponent(factory.getCompiler().getPartialDerivativeIndex(orders), value);
1673         final DerivativeStructure dfDx = f.differentiate(0, 1);
1674         orders[0] -= 1;
1675         assertEquals(1., dfDx.getPartialDerivative(new int[freeParam]), 0.);
1676         assertEquals(value, dfDx.getPartialDerivative(orders), 0.);
1677         checkEquals(factory.constant(0), f.differentiate(0, order + 1), 0.);
1678     }
1679 
1680     @Test
1681     void testDifferentiation2() {
1682         // check that first-order differentiation twice is same as second-order differentiation
1683         final RandomGenerator random = new Well19937a(0xec293aaee352de94l);
1684         final DSFactory factory = new DSFactory(5, 4);
1685         final int size = factory.getCompiler().getSize();
1686         for (int count = 0; count < 100; ++count) {
1687             final double[] data = new double[size];
1688             for (int i = 0; i < size; i++) {
1689                 data[i] = random.nextDouble();
1690             }
1691             final DerivativeStructure f = factory.build(data);
1692             final DerivativeStructure d2fDx2 = f.differentiate(0, 1).differentiate(0, 1);
1693             final DerivativeStructure d2fDx2Bis = f.differentiate(0, 2);
1694             checkEquals(d2fDx2, d2fDx2Bis, 0.);
1695         }
1696     }
1697 
1698     @Test
1699     void testDifferentiation3() {
1700         // check that first-order differentiation on two variables does not depend on sequence of operations
1701         final RandomGenerator random = new Well19937a(0x35409ecc1348e46cl);
1702         final DSFactory factory = new DSFactory(3, 7);
1703         final int size = factory.getCompiler().getSize();
1704         for (int count = 0; count < 100; ++count) {
1705             final double[] data = new double[size];
1706             for (int i = 0; i < size; i++) {
1707                 data[i] = random.nextDouble();
1708             }
1709             final DerivativeStructure f = factory.build(data);
1710             final DerivativeStructure d2fDxDy = f.differentiate(0, 1).differentiate(1, 1);
1711             final DerivativeStructure d2fDyDx = f.differentiate(1, 1).differentiate(0, 1);
1712             checkEquals(d2fDxDy, d2fDyDx, 0.);
1713         }
1714     }
1715 
1716     @Test
1717     void testField() {
1718         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
1719             DSFactory factory = new DSFactory(3, maxOrder);
1720             DerivativeStructure x = factory.variable(0, 1.0);
1721             checkF0F1(x.getField().getZero(), 0.0, 0.0, 0.0, 0.0);
1722             checkF0F1(x.getField().getOne(), 1.0, 0.0, 0.0, 0.0);
1723             assertEquals(maxOrder, x.getField().getZero().getOrder());
1724             assertEquals(3, x.getField().getZero().getFreeParameters());
1725             assertEquals(DerivativeStructure.class, x.getField().getRuntimeClass());
1726         }
1727     }
1728 
1729     @Test
1730     void testOneParameterConstructor() {
1731         double x = 1.2;
1732         double cos = FastMath.cos(x);
1733         double sin = FastMath.sin(x);
1734         DSFactory factory = new DSFactory(1, 4);
1735         DerivativeStructure yRef = factory.variable(0, x).cos();
1736         try {
1737             new DSFactory(1, 4).build(0.0, 0.0);
1738             fail("an exception should have been thrown");
1739         } catch (MathIllegalArgumentException dme) {
1740             // expected
1741         } catch (Exception e) {
1742             fail("wrong exceptionc caught " + e.getClass().getName());
1743         }
1744         double[] derivatives = new double[] { cos, -sin, -cos, sin, cos };
1745         DerivativeStructure y = factory.build(derivatives);
1746         checkEquals(yRef, y, 1.0e-15);
1747         UnitTestUtils.customAssertEquals(derivatives, y.getAllDerivatives(), 1.0e-15);
1748     }
1749 
1750     @Test
1751     void testOneOrderConstructor() {
1752         DSFactory factory = new DSFactory(3, 1);
1753         double x =  1.2;
1754         double y =  2.4;
1755         double z = 12.5;
1756         DerivativeStructure xRef = factory.variable(0, x);
1757         DerivativeStructure yRef = factory.variable(1, y);
1758         DerivativeStructure zRef = factory.variable(2, z);
1759         try {
1760             new DSFactory(3, 1).build(x + y - z, 1.0, 1.0);
1761             fail("an exception should have been thrown");
1762         } catch (MathIllegalArgumentException dme) {
1763             // expected
1764         } catch (Exception e) {
1765             fail("wrong exceptionc caught " + e.getClass().getName());
1766         }
1767         double[] derivatives = new double[] { x + y - z, 1.0, 1.0, -1.0 };
1768         DerivativeStructure t = factory.build(derivatives);
1769         checkEquals(xRef.add(yRef.subtract(zRef)), t, 1.0e-15);
1770         UnitTestUtils.customAssertEquals(derivatives, xRef.add(yRef.subtract(zRef)).getAllDerivatives(), 1.0e-15);
1771     }
1772 
1773     @Test
1774     void testLinearCombination1DSDS() {
1775         DSFactory factory = new DSFactory(6, 1);
1776         final DerivativeStructure[] a = new DerivativeStructure[] {
1777             factory.variable(0, -1321008684645961.0 / 268435456.0),
1778             factory.variable(1, -5774608829631843.0 / 268435456.0),
1779             factory.variable(2, -7645843051051357.0 / 8589934592.0)
1780         };
1781         final DerivativeStructure[] b = new DerivativeStructure[] {
1782             factory.variable(3, -5712344449280879.0 / 2097152.0),
1783             factory.variable(4, -4550117129121957.0 / 2097152.0),
1784             factory.variable(5, 8846951984510141.0 / 131072.0)
1785         };
1786 
1787         final DerivativeStructure abSumInline = a[0].linearCombination(a[0], b[0], a[1], b[1], a[2], b[2]);
1788         final DerivativeStructure abSumArray = a[0].linearCombination(a, b);
1789 
1790         assertEquals(abSumInline.getValue(), abSumArray.getValue(), 0);
1791         assertEquals(-1.8551294182586248737720779899, abSumInline.getValue(), 1.0e-15);
1792         assertEquals(b[0].getValue(), abSumInline.getPartialDerivative(1, 0, 0, 0, 0, 0), 1.0e-15);
1793         assertEquals(b[1].getValue(), abSumInline.getPartialDerivative(0, 1, 0, 0, 0, 0), 1.0e-15);
1794         assertEquals(b[2].getValue(), abSumInline.getPartialDerivative(0, 0, 1, 0, 0, 0), 1.0e-15);
1795         assertEquals(a[0].getValue(), abSumInline.getPartialDerivative(0, 0, 0, 1, 0, 0), 1.0e-15);
1796         assertEquals(a[1].getValue(), abSumInline.getPartialDerivative(0, 0, 0, 0, 1, 0), 1.0e-15);
1797         assertEquals(a[2].getValue(), abSumInline.getPartialDerivative(0, 0, 0, 0, 0, 1), 1.0e-15);
1798 
1799     }
1800 
1801     @Test
1802     void testLinearCombination1DoubleDS() {
1803         DSFactory factory = new DSFactory(3, 1);
1804         final double[] a = new double[] {
1805             -1321008684645961.0 / 268435456.0,
1806             -5774608829631843.0 / 268435456.0,
1807             -7645843051051357.0 / 8589934592.0
1808         };
1809         final DerivativeStructure[] b = new DerivativeStructure[] {
1810             factory.variable(0, -5712344449280879.0 / 2097152.0),
1811             factory.variable(1, -4550117129121957.0 / 2097152.0),
1812             factory.variable(2, 8846951984510141.0 / 131072.0)
1813         };
1814 
1815         final DerivativeStructure abSumInline = b[0].linearCombination(a[0], b[0],
1816                                                                        a[1], b[1],
1817                                                                        a[2], b[2]);
1818         final DerivativeStructure abSumArray = b[0].linearCombination(a, b);
1819 
1820         assertEquals(abSumInline.getValue(), abSumArray.getValue(), 0);
1821         assertEquals(-1.8551294182586248737720779899, abSumInline.getValue(), 1.0e-15);
1822         assertEquals(a[0], abSumInline.getPartialDerivative(1, 0, 0), 1.0e-15);
1823         assertEquals(a[1], abSumInline.getPartialDerivative(0, 1, 0), 1.0e-15);
1824         assertEquals(a[2], abSumInline.getPartialDerivative(0, 0, 1), 1.0e-15);
1825 
1826     }
1827 
1828     @Test
1829     void testLinearCombination2DSDS() {
1830         // we compare accurate versus naive dot product implementations
1831         // on regular vectors (i.e. not extreme cases like in the previous test)
1832         Well1024a random = new Well1024a(0xc6af886975069f11l);
1833 
1834         DSFactory factory = new DSFactory(4, 1);
1835         for (int i = 0; i < 10000; ++i) {
1836             final DerivativeStructure[] u = new DerivativeStructure[factory.getCompiler().getFreeParameters()];
1837             final DerivativeStructure[] v = new DerivativeStructure[factory.getCompiler().getFreeParameters()];
1838             for (int j = 0; j < u.length; ++j) {
1839                 u[j] = factory.variable(j, 1e17 * random.nextDouble());
1840                 v[j] = factory.constant(1e17 * random.nextDouble());
1841             }
1842 
1843             DerivativeStructure lin = u[0].linearCombination(u[0], v[0], u[1], v[1]);
1844             double ref = u[0].getValue() * v[0].getValue() +
1845                          u[1].getValue() * v[1].getValue();
1846             assertEquals(ref, lin.getValue(), 1.0e-15 * FastMath.abs(ref));
1847             assertEquals(v[0].getValue(), lin.getPartialDerivative(1, 0, 0, 0), 1.0e-15 * FastMath.abs(v[0].getValue()));
1848             assertEquals(v[1].getValue(), lin.getPartialDerivative(0, 1, 0, 0), 1.0e-15 * FastMath.abs(v[1].getValue()));
1849 
1850             lin = u[0].linearCombination(u[0], v[0], u[1], v[1], u[2], v[2]);
1851             ref = u[0].getValue() * v[0].getValue() +
1852                   u[1].getValue() * v[1].getValue() +
1853                   u[2].getValue() * v[2].getValue();
1854             assertEquals(ref, lin.getValue(), 1.0e-15 * FastMath.abs(ref));
1855             assertEquals(v[0].getValue(), lin.getPartialDerivative(1, 0, 0, 0), 1.0e-15 * FastMath.abs(v[0].getValue()));
1856             assertEquals(v[1].getValue(), lin.getPartialDerivative(0, 1, 0, 0), 1.0e-15 * FastMath.abs(v[1].getValue()));
1857             assertEquals(v[2].getValue(), lin.getPartialDerivative(0, 0, 1, 0), 1.0e-15 * FastMath.abs(v[2].getValue()));
1858 
1859             lin = u[0].linearCombination(u[0], v[0], u[1], v[1], u[2], v[2], u[3], v[3]);
1860             ref = u[0].getValue() * v[0].getValue() +
1861                   u[1].getValue() * v[1].getValue() +
1862                   u[2].getValue() * v[2].getValue() +
1863                   u[3].getValue() * v[3].getValue();
1864             assertEquals(ref, lin.getValue(), 1.0e-15 * FastMath.abs(ref));
1865             assertEquals(v[0].getValue(), lin.getPartialDerivative(1, 0, 0, 0), 1.0e-15 * FastMath.abs(v[0].getValue()));
1866             assertEquals(v[1].getValue(), lin.getPartialDerivative(0, 1, 0, 0), 1.0e-15 * FastMath.abs(v[1].getValue()));
1867             assertEquals(v[2].getValue(), lin.getPartialDerivative(0, 0, 1, 0), 1.0e-15 * FastMath.abs(v[2].getValue()));
1868             assertEquals(v[3].getValue(), lin.getPartialDerivative(0, 0, 0, 1), 1.0e-15 * FastMath.abs(v[3].getValue()));
1869 
1870         }
1871     }
1872 
1873     @Test
1874     void testLinearCombination2DoubleDS() {
1875         // we compare accurate versus naive dot product implementations
1876         // on regular vectors (i.e. not extreme cases like in the previous test)
1877         Well1024a random = new Well1024a(0xc6af886975069f11l);
1878 
1879         DSFactory factory = new DSFactory(4, 1);
1880         for (int i = 0; i < 10000; ++i) {
1881             final double[] u = new double[4];
1882             final DerivativeStructure[] v = new DerivativeStructure[factory.getCompiler().getFreeParameters()];
1883             for (int j = 0; j < u.length; ++j) {
1884                 u[j] = 1e17 * random.nextDouble();
1885                 v[j] = factory.variable(j, 1e17 * random.nextDouble());
1886             }
1887 
1888             DerivativeStructure lin = v[0].linearCombination(u[0], v[0], u[1], v[1]);
1889             double ref = u[0] * v[0].getValue() +
1890                          u[1] * v[1].getValue();
1891             assertEquals(ref, lin.getValue(), 1.0e-15 * FastMath.abs(ref));
1892             assertEquals(u[0], lin.getPartialDerivative(1, 0, 0, 0), 1.0e-15 * FastMath.abs(v[0].getValue()));
1893             assertEquals(u[1], lin.getPartialDerivative(0, 1, 0, 0), 1.0e-15 * FastMath.abs(v[1].getValue()));
1894 
1895             lin = v[0].linearCombination(u[0], v[0], u[1], v[1], u[2], v[2]);
1896             ref = u[0] * v[0].getValue() +
1897                   u[1] * v[1].getValue() +
1898                   u[2] * v[2].getValue();
1899             assertEquals(ref, lin.getValue(), 1.0e-15 * FastMath.abs(ref));
1900             assertEquals(u[0], lin.getPartialDerivative(1, 0, 0, 0), 1.0e-15 * FastMath.abs(v[0].getValue()));
1901             assertEquals(u[1], lin.getPartialDerivative(0, 1, 0, 0), 1.0e-15 * FastMath.abs(v[1].getValue()));
1902             assertEquals(u[2], lin.getPartialDerivative(0, 0, 1, 0), 1.0e-15 * FastMath.abs(v[2].getValue()));
1903 
1904             lin = v[0].linearCombination(u[0], v[0], u[1], v[1], u[2], v[2], u[3], v[3]);
1905             ref = u[0] * v[0].getValue() +
1906                   u[1] * v[1].getValue() +
1907                   u[2] * v[2].getValue() +
1908                   u[3] * v[3].getValue();
1909             assertEquals(ref, lin.getValue(), 1.0e-15 * FastMath.abs(ref));
1910             assertEquals(u[0], lin.getPartialDerivative(1, 0, 0, 0), 1.0e-15 * FastMath.abs(v[0].getValue()));
1911             assertEquals(u[1], lin.getPartialDerivative(0, 1, 0, 0), 1.0e-15 * FastMath.abs(v[1].getValue()));
1912             assertEquals(u[2], lin.getPartialDerivative(0, 0, 1, 0), 1.0e-15 * FastMath.abs(v[2].getValue()));
1913             assertEquals(u[3], lin.getPartialDerivative(0, 0, 0, 1), 1.0e-15 * FastMath.abs(v[3].getValue()));
1914 
1915         }
1916     }
1917 
1918     @Test
1919     void testSerialization() {
1920         DerivativeStructure a = new DSFactory(3, 2).variable(0, 1.3);
1921         DerivativeStructure b = (DerivativeStructure) UnitTestUtils.serializeAndRecover(a);
1922         assertEquals(a.getFreeParameters(), b.getFreeParameters());
1923         assertEquals(a.getOrder(), b.getOrder());
1924         checkEquals(a, b, 1.0e-15);
1925     }
1926 
1927     @Test
1928     void testZero() {
1929         DerivativeStructure zero = new DSFactory(3, 2).variable(2, 17.0).getField().getZero();
1930         double[] a = zero.getAllDerivatives();
1931         assertEquals(10, a.length);
1932         for (int i = 0; i < a.length; ++i) {
1933             assertEquals(0.0, a[i], 1.0e-15);
1934         }
1935     }
1936 
1937     @Test
1938     void testOne() {
1939         DerivativeStructure one = new DSFactory(3, 2).variable(2, 17.0).getField().getOne();
1940         double[] a = one.getAllDerivatives();
1941         assertEquals(10, a.length);
1942         for (int i = 0; i < a.length; ++i) {
1943             assertEquals(i == 0 ? 1.0 : 0.0, a[i], 1.0e-15);
1944         }
1945     }
1946 
1947     @Test
1948     void testMap() {
1949         List<int[]> pairs = new ArrayList<>();
1950         for (int parameters = 1; parameters < 5; ++parameters) {
1951             for (int order = 0; order < 3; ++order) {
1952                 pairs.add(new int[] { parameters, order });
1953             }
1954         }
1955         Map<Field<?>, Integer> map = new HashMap<>();
1956         for (int i = 0; i < 1000; ++i) {
1957             // create a brand new factory for each derivative
1958             int parameters = pairs.get(i % pairs.size())[0];
1959             int order      = pairs.get(i % pairs.size())[1];
1960             map.put(new DSFactory(parameters, order).constant(17.0).getField(), 0);
1961         }
1962 
1963         // despite we have created numerous factories,
1964         // there should be only one field for each pair parameters/order
1965         assertEquals(pairs.size(), map.size());
1966         @SuppressWarnings("unchecked")
1967         Field<DerivativeStructure> first = (Field<DerivativeStructure>) map.entrySet().iterator().next().getKey();
1968         assertEquals(first, first);
1969         assertNotEquals(first, Binary64Field.getInstance());
1970 
1971     }
1972 
1973     @Test
1974     void testRebaseConditions() {
1975         final DSFactory f32 = new DSFactory(3, 2);
1976         final DSFactory f22 = new DSFactory(2, 2);
1977         final DSFactory f31 = new DSFactory(3, 1);
1978         try {
1979             f32.variable(0, 0).rebase(f22.variable(0, 0), f22.variable(1, 1.0));
1980         } catch (MathIllegalArgumentException miae) {
1981             assertEquals(LocalizedCoreFormats.DIMENSIONS_MISMATCH, miae.getSpecifier());
1982             assertEquals(3, ((Integer) miae.getParts()[0]).intValue());
1983             assertEquals(2, ((Integer) miae.getParts()[1]).intValue());
1984         }
1985         try {
1986             f32.variable(0, 0).rebase(f31.variable(0, 0), f31.variable(1, 1.0), f31.variable(2, 2.0));
1987         } catch (MathIllegalArgumentException miae) {
1988             assertEquals(LocalizedCoreFormats.DIMENSIONS_MISMATCH, miae.getSpecifier());
1989             assertEquals(2, ((Integer) miae.getParts()[0]).intValue());
1990             assertEquals(1, ((Integer) miae.getParts()[1]).intValue());
1991         }
1992     }
1993 
1994     @Test
1995     void testRebaseNoVariables() {
1996         final DerivativeStructure x = new DSFactory(0, 2).constant(1.0);
1997         assertSame(x, x.rebase());
1998     }
1999 
2000     @Test
2001     void testRebaseValueMoreIntermediateThanBase() {
2002         doTestRebaseValue(createBaseVariables(new DSFactory(2, 4), 1.5, -2.0),
2003                           q -> new DerivativeStructure[] {
2004                               q[0].add(q[1].multiply(3)),
2005                               q[0].log(),
2006                               q[1].divide(q[0].sin())
2007                           },
2008                           new DSFactory(3, 4),
2009                           p -> p[0].add(p[1].divide(p[2])),
2010                           1.0e-15);
2011     }
2012 
2013     @Test
2014     void testRebaseValueLessIntermediateThanBase() {
2015         doTestRebaseValue(createBaseVariables(new DSFactory(3, 4), 1.5, -2.0, 0.5),
2016                           q -> new DerivativeStructure[] {
2017                               q[0].add(q[1].multiply(3)),
2018                               q[0].add(q[1]).subtract(q[2])
2019                           },
2020                           new DSFactory(2, 4),
2021                           p -> p[0].multiply(p[1]),
2022                           1.0e-15);
2023     }
2024 
2025     @Test
2026     void testRebaseValueEqualIntermediateAndBase() {
2027         doTestRebaseValue(createBaseVariables(new DSFactory(2, 4), 1.5, -2.0),
2028                           q -> new DerivativeStructure[] {
2029                               q[0].add(q[1].multiply(3)),
2030                               q[0].add(q[1])
2031                           },
2032                           new DSFactory(2, 4),
2033                           p -> p[0].multiply(p[1]),
2034                           1.0e-15);
2035     }
2036 
2037     private void doTestRebaseValue(final DerivativeStructure[] q,
2038                                    final CalculusFieldMultivariateVectorFunction<DerivativeStructure> qToP,
2039                                    final DSFactory factoryP,
2040                                    final CalculusFieldMultivariateFunction<DerivativeStructure> f,
2041                                    final double tol) {
2042 
2043         // intermediate variables as functions of base variables
2044         final DerivativeStructure[] pBase = qToP.value(q);
2045         
2046         // reference function
2047         final DerivativeStructure ref = f.value(pBase);
2048 
2049         // intermediate variables as independent variables
2050         final DerivativeStructure[] pIntermediate = creatIntermediateVariables(factoryP, pBase);
2051 
2052         // function of the intermediate variables
2053         final DerivativeStructure fI = f.value(pIntermediate);
2054 
2055         // function rebased to base variables
2056         final DerivativeStructure rebased = fI.rebase(pBase);
2057 
2058         assertEquals(q[0].getFreeParameters(),                   ref.getFreeParameters());
2059         assertEquals(q[0].getOrder(),                            ref.getOrder());
2060         assertEquals(factoryP.getCompiler().getFreeParameters(), fI.getFreeParameters());
2061         assertEquals(factoryP.getCompiler().getOrder(),          fI.getOrder());
2062         assertEquals(ref.getFreeParameters(),                    rebased.getFreeParameters());
2063         assertEquals(ref.getOrder(),                             rebased.getOrder());
2064 
2065         checkEquals(ref, rebased, tol);
2066 
2067         // compare with Taylor map based implementation
2068         checkEquals(composeWithTaylorMap(fI, pBase), rebased, tol);
2069 
2070     }
2071 
2072     @Test
2073     void testOrdersSum() {
2074         for (int i = 0; i < 6; ++i) {
2075             for (int j = 0; j < 4; ++j) {
2076                 DSCompiler compiler = DSCompiler.getCompiler(i, j);
2077                 for (int k = 0; k < compiler.getSize(); ++k) {
2078                     assertEquals(IntStream.of(compiler.getPartialDerivativeOrders(k)).sum(),
2079                                         compiler.getPartialDerivativeOrdersSum(k));
2080                 }
2081             }
2082         }
2083     }
2084 
2085     @Test
2086     void testDivisionVersusAlternativeImplementation() {
2087         final DSFactory factory = new DSFactory(3, 10);
2088         final DerivativeStructure lhs = FastMath.cos(factory.variable(1, 2.5));
2089         final DerivativeStructure rhs = factory.variable(2, -4).multiply(factory.variable(0, -3.));
2090         compareDivisionToVersionViaPower(lhs, rhs, 1e-12);
2091     }
2092 
2093     @Test
2094     void testReciprocalVersusAlternativeImplementation() {
2095         final DSFactory factory = new DSFactory(2, 15);
2096         final DerivativeStructure operand = factory.variable(0, 1.).
2097                 add(factory.variable(1, 0.).multiply(2.));
2098         compareReciprocalToVersionViaPower(operand, 1e-15);
2099     }
2100 
2101     @Test
2102     void testSqrtVersusRootN() {
2103         final DSFactory factory = new DSFactory(2, 8);
2104         DerivativeStructure ds = factory.variable(1, -2.).multiply(factory.variable(0, 1.));
2105         assertArrayEquals(ds.sqrt().getAllDerivatives(), ds.rootN(2).getAllDerivatives(), 1e-10);
2106     }
2107 
2108     @Test
2109     void testRunTimeClass() {
2110         Field<DerivativeStructure> field = new DSFactory(3, 2).constant(0.0).getField();
2111         assertEquals(DerivativeStructure.class, field.getRuntimeClass());
2112     }
2113 
2114     private void checkF0F1(DerivativeStructure ds, double value, double...derivatives) {
2115 
2116         // check dimension
2117         assertEquals(derivatives.length, ds.getFreeParameters());
2118 
2119         // check value, directly and also as 0th order derivative
2120         assertEquals(value, ds.getValue(), 1.0e-15);
2121         assertEquals(value, ds.getPartialDerivative(new int[ds.getFreeParameters()]), 1.0e-15);
2122 
2123         // check first order derivatives
2124         for (int i = 0; i < derivatives.length; ++i) {
2125             int[] orders = new int[derivatives.length];
2126             orders[i] = 1;
2127             assertEquals(derivatives[i], ds.getPartialDerivative(orders), 1.0e-15);
2128         }
2129 
2130     }
2131 
2132     public static void checkEquals(DerivativeStructure ds1, DerivativeStructure ds2, double epsilon) {
2133 
2134         // check dimension
2135         assertEquals(ds1.getFreeParameters(), ds2.getFreeParameters());
2136         assertEquals(ds1.getOrder(), ds2.getOrder());
2137 
2138         int[] derivatives = new int[ds1.getFreeParameters()];
2139         int sum = 0;
2140         while (true) {
2141 
2142             if (sum <= ds1.getOrder()) {
2143                 assertEquals(ds1.getPartialDerivative(derivatives),
2144                                     ds2.getPartialDerivative(derivatives),
2145                                     epsilon);
2146             }
2147 
2148             boolean increment = true;
2149             sum = 0;
2150             for (int i = derivatives.length - 1; i >= 0; --i) {
2151                 if (increment) {
2152                     if (derivatives[i] == ds1.getOrder()) {
2153                         derivatives[i] = 0;
2154                     } else {
2155                         derivatives[i]++;
2156                         increment = false;
2157                     }
2158                 }
2159                 sum += derivatives[i];
2160             }
2161             if (increment) {
2162                 return;
2163             }
2164 
2165         }
2166 
2167     }
2168 
2169     /** Compose the present derivatives on the right with a compatible so-called Taylor map i.e. an array of other
2170      * partial derivatives. The output has the same number of independent variables than the right-hand side. */
2171     private DerivativeStructure composeWithTaylorMap(final DerivativeStructure lhs, final DerivativeStructure[] rhs) {
2172 
2173         // turn right-hand side of composition into Taylor expansions without constant term
2174         final DSFactory rhsFactory = rhs[0].getFactory();
2175         final TaylorExpansion[] rhsAsExpansions = new TaylorExpansion[rhs.length];
2176         for (int k = 0; k < rhs.length; k++) {
2177             final DerivativeStructure copied = new DerivativeStructure(rhsFactory, rhs[k].getAllDerivatives());
2178             copied.setDerivativeComponent(0, 0.);
2179             rhsAsExpansions[k] = new TaylorExpansion(copied);
2180         }
2181         // turn left-hand side of composition into Taylor expansion
2182         final TaylorExpansion lhsAsExpansion = new TaylorExpansion(lhs);
2183 
2184         // initialize quantities
2185         TaylorExpansion te = new TaylorExpansion(rhsFactory.constant(lhs.getValue()));
2186         TaylorExpansion[][] powers = new TaylorExpansion[rhs.length][lhs.getOrder()];  // for lazy storage of powers
2187 
2188         // compose the Taylor expansions
2189         final double[] coefficients = lhsAsExpansion.coefficients;
2190         for (int j = 1; j < coefficients.length; j++) {
2191             if (coefficients[j] != 0.) {  // filter out null terms
2192                 TaylorExpansion inter = new TaylorExpansion(rhsFactory.constant(coefficients[j]));
2193                 final int[] orders = lhs.getFactory().getCompiler().getPartialDerivativeOrders(j);
2194                 for (int i = 0; i < orders.length; i++) {
2195                     if (orders[i] != 0) {  // only consider non-trivial powers
2196                         if (powers[i][orders[i] - 1] == null) {
2197                             // this power has not been computed yet
2198                             final DerivativeStructure ds = new DerivativeStructure(rhsFactory, rhs[i].getAllDerivatives());
2199                             ds.setDerivativeComponent(0, 0.);
2200                             TaylorExpansion inter2 = new TaylorExpansion(ds);
2201                             for (int k = 1; k < orders[i]; k++) {
2202                                 inter2 = inter2.multiply(rhsAsExpansions[i]);
2203                             }
2204                             powers[i][orders[i] - 1] = inter2;
2205                         }
2206                         inter = inter.multiply(powers[i][orders[i] - 1]);
2207                     }
2208                 }
2209                 te = te.add(inter);
2210             }
2211         }
2212 
2213         // convert into derivatives object
2214         return te.buildDsEquivalent();
2215     }
2216 
2217     /** Class to map partial derivatives to corresponding Taylor expansion. */
2218     private static class TaylorExpansion {
2219 
2220         /** Polynomial coefficients of the Taylor expansion in the local canonical basis. */
2221         final double[] coefficients;
2222 
2223         final double[] factorials;
2224         final DSFactory dsFactory;
2225 
2226        /** Constructor. */
2227         TaylorExpansion(final DerivativeStructure ds) {
2228             final double[] data = ds.getAllDerivatives();
2229             this.dsFactory = ds.getFactory();
2230             this.coefficients = new double[data.length];
2231 
2232             // compute relevant factorials (would be more efficient to compute products of factorials to map Taylor
2233             // expansions and partial derivatives)
2234             this.factorials = new double[ds.getOrder() + 1];
2235             Arrays.fill(this.factorials, 1.);
2236             for (int i = 2; i < this.factorials.length; i++) {
2237                 this.factorials[i] = this.factorials[i - 1] * (double) (i);  // avoid limit of 20! in ArithmeticUtils
2238             }
2239 
2240             // transform partial derivatives into coefficients of Taylor expansion
2241             for (int j = 0; j < data.length; j++) {
2242                 this.coefficients[j] = data[j];
2243                 if (this.coefficients[j] != 0.) {
2244                     int[] orders = ds.getFactory().getCompiler().getPartialDerivativeOrders(j);
2245                     for (int order : orders) {
2246                         this.coefficients[j] /= this.factorials[order];
2247                     }
2248                 }
2249             }
2250         }
2251 
2252         /** Builder for the corresponding {@link DerivativeStructure}. */
2253         public DerivativeStructure buildDsEquivalent() throws MathIllegalArgumentException {
2254             final DSCompiler dsc = this.dsFactory.getCompiler();
2255             final double[] data = new double[this.coefficients.length];
2256             for (int j = 0; j < data.length; j++) {
2257                 data[j] = this.coefficients[j];
2258                 if (data[j] != 0.) {
2259                     int[] orders = dsc.getPartialDerivativeOrders(j);
2260                     for (int order : orders) {
2261                         data[j] *= this.factorials[order];
2262                     }
2263                 }
2264             }
2265             return new DerivativeStructure(this.dsFactory, data);
2266         }
2267 
2268         TaylorExpansion add(final TaylorExpansion te) {
2269             return new TaylorExpansion(this.buildDsEquivalent().add(te.buildDsEquivalent()));
2270         }
2271 
2272         TaylorExpansion multiply(final TaylorExpansion te) {
2273             return new TaylorExpansion(this.buildDsEquivalent().multiply(te.buildDsEquivalent()));
2274         }
2275 
2276     }
2277 
2278     private DerivativeStructure[] createBaseVariables(final DSFactory factory, double... q) {
2279         final DerivativeStructure[] qDS = new DerivativeStructure[q.length];
2280         for (int i = 0; i < q.length; ++i) {
2281             qDS[i] = factory.variable(i, q[i]);
2282         }
2283         return qDS;
2284     }
2285 
2286     private DerivativeStructure[] creatIntermediateVariables(final DSFactory factory, DerivativeStructure... pBase) {
2287         final DerivativeStructure[] pIntermediate = new DerivativeStructure[pBase.length];
2288         for (int i = 0; i < pBase.length; ++i) {
2289             pIntermediate[i] = factory.variable(i, pBase[i].getValue());
2290         }
2291         return pIntermediate;
2292     }
2293 
2294     private void compareDivisionToVersionViaPower(final DerivativeStructure lhs, final DerivativeStructure rhs,
2295                                                   final double tolerance) {
2296         DSCompiler compiler = lhs.getFactory().getCompiler();
2297         final double[] result = new double[compiler.getSize()];
2298         compiler.multiply(lhs.getAllDerivatives(), 0, rhs.reciprocal().getAllDerivatives(), 0, result, 0);
2299         final double[] result2 = result.clone();
2300         compiler.divide(lhs.getAllDerivatives(), 0, rhs.getAllDerivatives(), 0, result2, 0);
2301         assertArrayEquals(result, result2, tolerance);
2302     }
2303 
2304     private void compareReciprocalToVersionViaPower(final DerivativeStructure operand, final double tolerance) {
2305         DSCompiler compiler = operand.getFactory().getCompiler();
2306         final double[] result = new double[compiler.getSize()];
2307         compiler.pow(operand.getAllDerivatives(), 0, -1, result, 0);
2308         final double[] result2 = result.clone();
2309         compiler.reciprocal(operand.getAllDerivatives(), 0, result2, 0);
2310         assertArrayEquals(result, result2, tolerance);
2311     }
2312 
2313 }