View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.math4.legacy.analysis.differentiation;
18  
19  import java.util.Arrays;
20  import java.util.List;
21  
22  import org.apache.commons.math4.legacy.field.ExtendedFieldElementAbstractTest;
23  import org.apache.commons.math4.legacy.analysis.polynomials.PolynomialFunction;
24  import org.apache.commons.rng.UniformRandomProvider;
25  import org.apache.commons.rng.simple.RandomSource;
26  import org.apache.commons.math4.core.jdkmath.JdkMath;
27  import org.junit.Assert;
28  import org.junit.Test;
29  
30  public class SparseGradientTest extends ExtendedFieldElementAbstractTest<SparseGradient> {
31  
32      @Override
33      protected SparseGradient build(final double x) {
34          return SparseGradient.createVariable(0, x);
35      }
36  
37      @Test
38      public void testConstant() {
39          double c = 1.0;
40          SparseGradient grad = SparseGradient.createConstant(c);
41          Assert.assertEquals(c, grad.getValue(), 1.0e-15); // returns the value
42          Assert.assertEquals(0, grad.numVars(), 1.0e-15); // has no variables
43      }
44  
45      @Test
46      public void testVariable() {
47          double v = 1.0;
48          int id = 0;
49          SparseGradient grad = SparseGradient.createVariable(id, v);
50          Assert.assertEquals(v, grad.getValue(), 1.0e-15); // returns the value
51          Assert.assertEquals(1, grad.numVars(), 1.0e-15); // has one variable
52          Assert.assertEquals(1.0, grad.getDerivative(id), 1.0e-15); // derivative wr.t itself is 1
53      }
54  
55      @Test
56      public void testVarAddition() {
57          final double v1 = 1.0;
58          final double v2 = 2.0;
59          final int id1 = -1;
60          final int id2 = 3;
61          final SparseGradient var1 = SparseGradient.createVariable(id1, v1);
62          final SparseGradient var2 = SparseGradient.createVariable(id2, v2);
63          final SparseGradient sum = var1.add(var2);
64  
65          Assert.assertEquals(v1 + v2, sum.getValue(), 1.0e-15); // returns the value
66          Assert.assertEquals(2, sum.numVars());
67          Assert.assertEquals(1.0, sum.getDerivative(id1), 1.0e-15);
68          Assert.assertEquals(1.0, sum.getDerivative(id2), 1.0e-15);
69      }
70  
71      @Test
72      public void testSubtraction() {
73          final double v1 = 1.0;
74          final double v2 = 2.0;
75          final int id1 = -1;
76          final int id2 = 3;
77          final SparseGradient var1 = SparseGradient.createVariable(id1, v1);
78          final SparseGradient var2 = SparseGradient.createVariable(id2, v2);
79          final SparseGradient sum = var1.subtract(var2);
80  
81          Assert.assertEquals(v1 - v2, sum.getValue(), 1.0e-15); // returns the value
82          Assert.assertEquals(2, sum.numVars());
83          Assert.assertEquals(1.0, sum.getDerivative(id1), 1.0e-15);
84          Assert.assertEquals(-1.0, sum.getDerivative(id2), 1.0e-15);
85      }
86  
87      @Test
88      public void testDivision() {
89          final double v1 = 1.0;
90          final double v2 = 2.0;
91          final int id1 = -1;
92          final int id2 = 3;
93          final SparseGradient var1 = SparseGradient.createVariable(id1, v1);
94          final SparseGradient var2 = SparseGradient.createVariable(id2, v2);
95          final SparseGradient out = var1.divide(var2);
96          Assert.assertEquals(v1 / v2, out.getValue(), 1.0e-15); // returns the value
97          Assert.assertEquals(2, out.numVars());
98          Assert.assertEquals(1 / v2, out.getDerivative(id1), 1.0e-15);
99          Assert.assertEquals(-1 / (v2 * v2), out.getDerivative(id2), 1.0e-15);
100     }
101 
102     @Test
103     public void testMult() {
104         final double v1 = 1.0;
105         final double c1 = 0.5;
106         final double v2 = 2.0;
107         final int id1 = -1;
108         final int id2 = 3;
109         final SparseGradient var1 = SparseGradient.createVariable(id1, v1);
110         final SparseGradient unit1 = var1.multiply(c1);
111         final SparseGradient unit2 = SparseGradient.createVariable(id2, v2).multiply(var1);
112         final SparseGradient sum = unit1.add(unit2);
113         Assert.assertEquals(v1 * c1 + v2 * v1, sum.getValue(), 1.0e-15); // returns the value
114         Assert.assertEquals(2, sum.numVars());
115         Assert.assertEquals(c1 + v2, sum.getDerivative(id1), 1.0e-15);
116         Assert.assertEquals(v1, sum.getDerivative(id2), 1.0e-15);
117     }
118 
119     @Test
120     public void testVarMultInPlace() {
121         final double v1 = 1.0;
122         final double c1 = 0.5;
123         final double v2 = 2.0;
124         final int id1 = -1;
125         final int id2 = 3;
126         final SparseGradient var1 = SparseGradient.createVariable(id1, v1);
127         final SparseGradient sum = var1.multiply(c1);
128         final SparseGradient mult = SparseGradient.createVariable(id2, v2);
129         mult.multiplyInPlace(var1);
130         sum.addInPlace(mult);
131         Assert.assertEquals(v1 * c1 + v2 * v1, sum.getValue(), 1.0e-15); // returns the value
132         Assert.assertEquals(2, sum.numVars());
133         Assert.assertEquals(c1 + v2, sum.getDerivative(id1), 1.0e-15);
134         Assert.assertEquals(v1, sum.getDerivative(id2), 1.0e-15);
135     }
136 
137     @Test
138     public void testPrimitiveAdd() {
139         checkF0F1(SparseGradient.createVariable(0, 1.0).add(5), 6.0, 1.0, 0.0, 0.0);
140         checkF0F1(SparseGradient.createVariable(1, 2.0).add(5), 7.0, 0.0, 1.0, 0.0);
141         checkF0F1(SparseGradient.createVariable(2, 3.0).add(5), 8.0, 0.0, 0.0, 1.0);
142     }
143 
144     @Test
145     public void testAdd() {
146         SparseGradient x = SparseGradient.createVariable(0, 1.0);
147         SparseGradient y = SparseGradient.createVariable(1, 2.0);
148         SparseGradient z = SparseGradient.createVariable(2, 3.0);
149         SparseGradient xyz = x.add(y.add(z));
150         checkF0F1(xyz, x.getValue() + y.getValue() + z.getValue(), 1.0, 1.0, 1.0);
151     }
152 
153     @Test
154     public void testPrimitiveSubtract() {
155         checkF0F1(SparseGradient.createVariable(0, 1.0).subtract(5), -4.0, 1.0, 0.0, 0.0);
156         checkF0F1(SparseGradient.createVariable(1, 2.0).subtract(5), -3.0, 0.0, 1.0, 0.0);
157         checkF0F1(SparseGradient.createVariable(2, 3.0).subtract(5), -2.0, 0.0, 0.0, 1.0);
158     }
159 
160     @Test
161     public void testSubtract() {
162         SparseGradient x = SparseGradient.createVariable(0, 1.0);
163         SparseGradient y = SparseGradient.createVariable(1, 2.0);
164         SparseGradient z = SparseGradient.createVariable(2, 3.0);
165         SparseGradient xyz = x.subtract(y.subtract(z));
166         checkF0F1(xyz, x.getValue() - (y.getValue() - z.getValue()), 1.0, -1.0, 1.0);
167     }
168 
169     @Test
170     public void testPrimitiveMultiply() {
171         checkF0F1(SparseGradient.createVariable(0, 1.0).multiply(5),  5.0, 5.0, 0.0, 0.0);
172         checkF0F1(SparseGradient.createVariable(1, 2.0).multiply(5), 10.0, 0.0, 5.0, 0.0);
173         checkF0F1(SparseGradient.createVariable(2, 3.0).multiply(5), 15.0, 0.0, 0.0, 5.0);
174     }
175 
176     @Test
177     public void testMultiply() {
178         SparseGradient x = SparseGradient.createVariable(0, 1.0);
179         SparseGradient y = SparseGradient.createVariable(1, 2.0);
180         SparseGradient z = SparseGradient.createVariable(2, 3.0);
181         SparseGradient xyz = x.multiply(y.multiply(z));
182         checkF0F1(xyz, 6.0, 6.0, 3.0, 2.0);
183     }
184 
185     @Test
186     public void testNegate() {
187         checkF0F1(SparseGradient.createVariable(0, 1.0).negate(), -1.0, -1.0, 0.0, 0.0);
188         checkF0F1(SparseGradient.createVariable(1, 2.0).negate(), -2.0, 0.0, -1.0, 0.0);
189         checkF0F1(SparseGradient.createVariable(2, 3.0).negate(), -3.0, 0.0, 0.0, -1.0);
190     }
191 
192     @Test
193     public void testReciprocal() {
194         for (double x = 0.1; x < 1.2; x += 0.1) {
195             SparseGradient r = SparseGradient.createVariable(0, x).reciprocal();
196             Assert.assertEquals(1 / x, r.getValue(), 1.0e-15);
197             final double expected = -1 / (x * x);
198             Assert.assertEquals(expected, r.getDerivative(0), 1.0e-15 * JdkMath.abs(expected));
199         }
200     }
201 
202     @Test
203     public void testPow() {
204         for (int n = 0; n < 10; ++n) {
205 
206             SparseGradient x = SparseGradient.createVariable(0, 1.0);
207             SparseGradient y = SparseGradient.createVariable(1, 2.0);
208             SparseGradient z = SparseGradient.createVariable(2, 3.0);
209             List<SparseGradient> list = Arrays.asList(x, y, z,
210                                                       x.add(y).add(z),
211                                                       x.multiply(y).multiply(z));
212 
213             if (n == 0) {
214                 for (SparseGradient sg : list) {
215                     Assert.assertEquals(sg.getField().getOne(), sg.pow(n));
216                 }
217             } else if (n == 1) {
218                 for (SparseGradient sg : list) {
219                     Assert.assertEquals(sg, sg.pow(n));
220                 }
221             } else {
222                 for (SparseGradient sg : list) {
223                     SparseGradient p = sg.getField().getOne();
224                     for (int i = 0; i < n; ++i) {
225                         p = p.multiply(sg);
226                     }
227                     Assert.assertEquals(p, sg.pow(n));
228                 }
229             }
230         }
231     }
232 
233     @Test
234     public void testPowDoubleDS() {
235         for (int maxOrder = 1; maxOrder < 5; ++maxOrder) {
236 
237             SparseGradient x = SparseGradient.createVariable(0, 0.1);
238             SparseGradient y = SparseGradient.createVariable(1, 0.2);
239             SparseGradient z = SparseGradient.createVariable(2, 0.3);
240             List<SparseGradient> list = Arrays.asList(x, y, z,
241                                                       x.add(y).add(z),
242                                                       x.multiply(y).multiply(z));
243 
244             for (SparseGradient sg : list) {
245                 // the special case a = 0 is included here
246                 for (double a : new double[] { 0.0, 0.1, 1.0, 2.0, 5.0 }) {
247                     SparseGradient reference = (a == 0) ?
248                                                x.getField().getZero() :
249                                                SparseGradient.createConstant(a).pow(sg);
250                     SparseGradient result = SparseGradient.pow(a, sg);
251                     Assert.assertEquals(reference, result);
252                 }
253             }
254 
255             // negative base: -1^x can be evaluated for integers only, so value is sometimes OK, derivatives are always NaN
256             SparseGradient negEvenInteger = SparseGradient.pow(-2.0, SparseGradient.createVariable(0, 2.0));
257             Assert.assertEquals(4.0, negEvenInteger.getValue(), 1.0e-15);
258             Assert.assertTrue(Double.isNaN(negEvenInteger.getDerivative(0)));
259             SparseGradient negOddInteger = SparseGradient.pow(-2.0, SparseGradient.createVariable(0, 3.0));
260             Assert.assertEquals(-8.0, negOddInteger.getValue(), 1.0e-15);
261             Assert.assertTrue(Double.isNaN(negOddInteger.getDerivative(0)));
262             SparseGradient negNonInteger = SparseGradient.pow(-2.0, SparseGradient.createVariable(0, 2.001));
263             Assert.assertTrue(Double.isNaN(negNonInteger.getValue()));
264             Assert.assertTrue(Double.isNaN(negNonInteger.getDerivative(0)));
265 
266             SparseGradient zeroNeg = SparseGradient.pow(0.0, SparseGradient.createVariable(0, -1.0));
267             Assert.assertTrue(Double.isNaN(zeroNeg.getValue()));
268             Assert.assertTrue(Double.isNaN(zeroNeg.getDerivative(0)));
269             SparseGradient posNeg = SparseGradient.pow(2.0, SparseGradient.createVariable(0, -2.0));
270             Assert.assertEquals(1.0 / 4.0, posNeg.getValue(), 1.0e-15);
271             Assert.assertEquals(JdkMath.log(2.0) / 4.0, posNeg.getDerivative(0), 1.0e-15);
272 
273             // very special case: a = 0 and power = 0
274             SparseGradient zeroZero = SparseGradient.pow(0.0, SparseGradient.createVariable(0, 0.0));
275 
276             // this should be OK for simple first derivative with one variable only ...
277             Assert.assertEquals(1.0, zeroZero.getValue(), 1.0e-15);
278             Assert.assertEquals(Double.NEGATIVE_INFINITY, zeroZero.getDerivative(0), 1.0e-15);
279             Assert.assertEquals(0.0, zeroZero.getDerivative(1), 1.0e-15);
280             Assert.assertEquals(0.0, zeroZero.getDerivative(2), 1.0e-15);
281         }
282     }
283 
284     @Test
285     public void testExpression() {
286         double epsilon = 2.5e-13;
287         for (double x = 0; x < 2; x += 0.2) {
288             SparseGradient sgX = SparseGradient.createVariable(0, x);
289             for (double y = 0; y < 2; y += 0.2) {
290                 SparseGradient sgY = SparseGradient.createVariable(1, y);
291                 for (double z = 0; z >- 2; z -= 0.2) {
292                     SparseGradient sgZ = SparseGradient.createVariable(2, z);
293 
294                     // f(x, y, z) = x + 5 x y - 2 z + (8 z x - y)^3
295                     SparseGradient sg =
296                             sgZ.linearCombination(1, sgX,
297                                                   5, sgX.multiply(sgY),
298                                                  -2, sgZ,
299                                                  1, sgZ.linearCombination(8, sgZ.multiply(sgX), -1, sgY).pow(3));
300                     double f = x + 5 * x * y - 2 * z + JdkMath.pow(8 * z * x - y, 3);
301                     Assert.assertEquals(f, sg.getValue(), JdkMath.abs(epsilon * f));
302 
303                     // df/dx = 1 + 5 y + 24 (8 z x - y)^2 z
304                     double dfdx = 1 + 5 * y + 24 * z * JdkMath.pow(8 * z * x - y, 2);
305                     Assert.assertEquals(dfdx, sg.getDerivative(0), JdkMath.abs(epsilon * dfdx));
306                 }
307             }
308         }
309     }
310 
311     @Test
312     public void testCompositionOneVariableX() {
313         double epsilon = 1.0e-13;
314         for (double x = 0.1; x < 1.2; x += 0.1) {
315             SparseGradient sgX = SparseGradient.createVariable(0, x);
316             for (double y = 0.1; y < 1.2; y += 0.1) {
317                 SparseGradient sgY = SparseGradient.createConstant(y);
318                 SparseGradient f = sgX.divide(sgY).sqrt();
319                 double f0 = JdkMath.sqrt(x / y);
320                 Assert.assertEquals(f0, f.getValue(), JdkMath.abs(epsilon * f0));
321                 double f1 = 1 / (2 * JdkMath.sqrt(x * y));
322                 Assert.assertEquals(f1, f.getDerivative(0), JdkMath.abs(epsilon * f1));
323             }
324         }
325     }
326 
327     @Test
328     public void testTrigo() {
329         double epsilon = 2.0e-12;
330             for (double x = 0.1; x < 1.2; x += 0.1) {
331                 SparseGradient sgX = SparseGradient.createVariable(0, x);
332                 for (double y = 0.1; y < 1.2; y += 0.1) {
333                     SparseGradient sgY = SparseGradient.createVariable(1, y);
334                     for (double z = 0.1; z < 1.2; z += 0.1) {
335                         SparseGradient sgZ = SparseGradient.createVariable(2, z);
336                         SparseGradient f = sgX.divide(sgY.cos().add(sgZ.tan())).sin();
337                         double a = JdkMath.cos(y) + JdkMath.tan(z);
338                         double f0 = JdkMath.sin(x / a);
339                         Assert.assertEquals(f0, f.getValue(), JdkMath.abs(epsilon * f0));
340                         double dfdx = JdkMath.cos(x / a) / a;
341                         Assert.assertEquals(dfdx, f.getDerivative(0), JdkMath.abs(epsilon * dfdx));
342                         double dfdy =  x * JdkMath.sin(y) * dfdx / a;
343                         Assert.assertEquals(dfdy, f.getDerivative(1), JdkMath.abs(epsilon * dfdy));
344                         double cz = JdkMath.cos(z);
345                         double cz2 = cz * cz;
346                         double dfdz = -x * dfdx / (a * cz2);
347                         Assert.assertEquals(dfdz, f.getDerivative(2), JdkMath.abs(epsilon * dfdz));
348                     }
349                 }
350             }
351     }
352 
353     @Test
354     public void testSqrtDefinition() {
355         for (double x = 0.1; x < 1.2; x += 0.001) {
356             SparseGradient sgX = SparseGradient.createVariable(0, x);
357             SparseGradient sqrt1 = sgX.pow(0.5);
358             SparseGradient sqrt2 = sgX.sqrt();
359             SparseGradient zero = sqrt1.subtract(sqrt2);
360             checkF0F1(zero, 0.0, 0.0);
361         }
362     }
363 
364     @Test
365     public void testRootNSingularity() {
366         for (int n = 2; n < 10; ++n) {
367             SparseGradient sgZero = SparseGradient.createVariable(0, 0.0);
368             SparseGradient rootN  = sgZero.rootN(n);
369             Assert.assertEquals(0.0, rootN.getValue(), 1.0e-5);
370             Assert.assertTrue(Double.isInfinite(rootN.getDerivative(0)));
371             Assert.assertTrue(rootN.getDerivative(0) > 0);
372         }
373     }
374 
375     @Test
376     public void testSqrtPow2() {
377         for (double x = 0.1; x < 1.2; x += 0.001) {
378             SparseGradient sgX = SparseGradient.createVariable(0, x);
379             SparseGradient rebuiltX = sgX.multiply(sgX).sqrt();
380             SparseGradient zero = rebuiltX.subtract(sgX);
381             checkF0F1(zero, 0.0, 0.0);
382         }
383     }
384 
385     @Test
386     public void testCbrtDefinition() {
387         for (double x = 0.1; x < 1.2; x += 0.001) {
388             SparseGradient sgX = SparseGradient.createVariable(0, x);
389             SparseGradient cbrt1 = sgX.pow(1.0 / 3.0);
390             SparseGradient cbrt2 = sgX.cbrt();
391             SparseGradient zero = cbrt1.subtract(cbrt2);
392             checkF0F1(zero, 0.0, 0.0);
393         }
394     }
395 
396     @Test
397     public void testCbrtPow3() {
398         for (double x = 0.1; x < 1.2; x += 0.001) {
399             SparseGradient sgX = SparseGradient.createVariable(0, x);
400             SparseGradient rebuiltX = sgX.multiply(sgX.multiply(sgX)).cbrt();
401             SparseGradient zero = rebuiltX.subtract(sgX);
402             checkF0F1(zero, 0.0, 0.0);
403         }
404     }
405 
406     @Test
407     public void testPowReciprocalPow() {
408         for (double x = 0.1; x < 1.2; x += 0.01) {
409             SparseGradient sgX = SparseGradient.createVariable(0, x);
410             for (double y = 0.1; y < 1.2; y += 0.01) {
411                 SparseGradient sgY = SparseGradient.createVariable(1, y);
412                 SparseGradient rebuiltX = sgX.pow(sgY).pow(sgY.reciprocal());
413                 SparseGradient zero = rebuiltX.subtract(sgX);
414                 checkF0F1(zero, 0.0, 0.0, 0.0);
415             }
416         }
417     }
418 
419     @Test
420     public void testHypotDefinition() {
421         for (double x = -1.7; x < 2; x += 0.2) {
422             SparseGradient sgX = SparseGradient.createVariable(0, x);
423             for (double y = -1.7; y < 2; y += 0.2) {
424                 SparseGradient sgY = SparseGradient.createVariable(1, y);
425                 SparseGradient hypot = SparseGradient.hypot(sgY, sgX);
426                 SparseGradient ref = sgX.multiply(sgX).add(sgY.multiply(sgY)).sqrt();
427                 SparseGradient zero = hypot.subtract(ref);
428                 checkF0F1(zero, 0.0, 0.0, 0.0);
429             }
430         }
431     }
432 
433     @Test
434     public void testHypotNoOverflow() {
435 
436         SparseGradient sgX = SparseGradient.createVariable(0, +3.0e250);
437         SparseGradient sgY = SparseGradient.createVariable(1, -4.0e250);
438         SparseGradient hypot = SparseGradient.hypot(sgX, sgY);
439         Assert.assertEquals(5.0e250, hypot.getValue(), 1.0e235);
440         Assert.assertEquals(sgX.getValue() / hypot.getValue(), hypot.getDerivative(0), 1.0e-10);
441         Assert.assertEquals(sgY.getValue() / hypot.getValue(), hypot.getDerivative(1), 1.0e-10);
442 
443         SparseGradient sqrt  = sgX.multiply(sgX).add(sgY.multiply(sgY)).sqrt();
444         Assert.assertTrue(Double.isInfinite(sqrt.getValue()));
445     }
446 
447     @Test
448     public void testHypotNeglectible() {
449 
450         SparseGradient sgSmall = SparseGradient.createVariable(0, +3.0e-10);
451         SparseGradient sgLarge = SparseGradient.createVariable(1, -4.0e25);
452 
453         Assert.assertEquals(sgLarge.abs().getValue(),
454                             SparseGradient.hypot(sgSmall, sgLarge).getValue(),
455                             1.0e-10);
456         Assert.assertEquals(0,
457                             SparseGradient.hypot(sgSmall, sgLarge).getDerivative(0),
458                             1.0e-10);
459         Assert.assertEquals(-1,
460                             SparseGradient.hypot(sgSmall, sgLarge).getDerivative(1),
461                             1.0e-10);
462 
463         Assert.assertEquals(sgLarge.abs().getValue(),
464                             SparseGradient.hypot(sgLarge, sgSmall).getValue(),
465                             1.0e-10);
466         Assert.assertEquals(0,
467                             SparseGradient.hypot(sgLarge, sgSmall).getDerivative(0),
468                             1.0e-10);
469         Assert.assertEquals(-1,
470                             SparseGradient.hypot(sgLarge, sgSmall).getDerivative(1),
471                             1.0e-10);
472     }
473 
474     @Test
475     public void testHypotSpecial() {
476         Assert.assertTrue(Double.isNaN(SparseGradient.hypot(SparseGradient.createVariable(0, Double.NaN),
477                                                                  SparseGradient.createVariable(0, +3.0e250)).getValue()));
478         Assert.assertTrue(Double.isNaN(SparseGradient.hypot(SparseGradient.createVariable(0, +3.0e250),
479                                                                  SparseGradient.createVariable(0, Double.NaN)).getValue()));
480         Assert.assertTrue(Double.isInfinite(SparseGradient.hypot(SparseGradient.createVariable(0, Double.POSITIVE_INFINITY),
481                                                                       SparseGradient.createVariable(0, +3.0e250)).getValue()));
482         Assert.assertTrue(Double.isInfinite(SparseGradient.hypot(SparseGradient.createVariable(0, +3.0e250),
483                                                                       SparseGradient.createVariable(0, Double.POSITIVE_INFINITY)).getValue()));
484     }
485 
486     @Test
487     public void testPrimitiveRemainder() {
488         for (double x = -1.7; x < 2; x += 0.2) {
489             SparseGradient sgX = SparseGradient.createVariable(0, x);
490             for (double y = -1.7; y < 2; y += 0.2) {
491                 SparseGradient remainder = sgX.remainder(y);
492                 SparseGradient ref = sgX.subtract(x - JdkMath.IEEEremainder(x, y));
493                 SparseGradient zero = remainder.subtract(ref);
494                 checkF0F1(zero, 0.0, 0.0, 0.0);
495             }
496         }
497     }
498 
499     @Test
500     public void testRemainder() {
501         for (double x = -1.7; x < 2; x += 0.2) {
502             SparseGradient sgX = SparseGradient.createVariable(0, x);
503             for (double y = -1.7; y < 2; y += 0.2) {
504                 SparseGradient sgY = SparseGradient.createVariable(1, y);
505                 SparseGradient remainder = sgX.remainder(sgY);
506                 SparseGradient ref = sgX.subtract(sgY.multiply((x - JdkMath.IEEEremainder(x, y)) / y));
507                 SparseGradient zero = remainder.subtract(ref);
508                 checkF0F1(zero, 0.0, 0.0, 0.0);
509             }
510         }
511     }
512 
513     @Override
514     @Test
515     public void testExp() {
516         for (double x = 0.1; x < 1.2; x += 0.001) {
517             double refExp = JdkMath.exp(x);
518             checkF0F1(SparseGradient.createVariable(0, x).exp(), refExp, refExp);
519         }
520     }
521 
522     @Test
523     public void testExpm1Definition() {
524         for (double x = 0.1; x < 1.2; x += 0.001) {
525             SparseGradient sgX = SparseGradient.createVariable(0, x);
526             SparseGradient expm11 = sgX.expm1();
527             SparseGradient expm12 = sgX.exp().subtract(sgX.getField().getOne());
528             SparseGradient zero = expm11.subtract(expm12);
529             checkF0F1(zero, 0.0, 0.0);
530         }
531     }
532 
533     @Override
534     @Test
535     public void testLog() {
536         for (double x = 0.1; x < 1.2; x += 0.001) {
537             checkF0F1(SparseGradient.createVariable(0, x).log(), JdkMath.log(x), 1.0 / x);
538         }
539     }
540 
541     @Test
542     public void testLog1pDefinition() {
543         for (double x = 0.1; x < 1.2; x += 0.001) {
544             SparseGradient sgX = SparseGradient.createVariable(0, x);
545             SparseGradient log1p1 = sgX.log1p();
546             SparseGradient log1p2 = sgX.add(sgX.getField().getOne()).log();
547             SparseGradient zero = log1p1.subtract(log1p2);
548             checkF0F1(zero, 0.0, 0.0);
549         }
550     }
551 
552     @Test
553     public void testLog10Definition() {
554         for (double x = 0.1; x < 1.2; x += 0.001) {
555             SparseGradient sgX = SparseGradient.createVariable(0, x);
556             SparseGradient log101 = sgX.log10();
557             SparseGradient log102 = sgX.log().divide(JdkMath.log(10.0));
558             SparseGradient zero = log101.subtract(log102);
559             checkF0F1(zero, 0.0, 0.0);
560         }
561     }
562 
563     @Test
564     public void testLogExp() {
565         for (double x = 0.1; x < 1.2; x += 0.001) {
566             SparseGradient sgX = SparseGradient.createVariable(0, x);
567             SparseGradient rebuiltX = sgX.exp().log();
568             SparseGradient zero = rebuiltX.subtract(sgX);
569             checkF0F1(zero, 0.0, 0.0);
570         }
571     }
572 
573     @Test
574     public void testLog1pExpm1() {
575         for (double x = 0.1; x < 1.2; x += 0.001) {
576             SparseGradient sgX = SparseGradient.createVariable(0, x);
577             SparseGradient rebuiltX = sgX.expm1().log1p();
578             SparseGradient zero = rebuiltX.subtract(sgX);
579             checkF0F1(zero, 0.0, 0.0);
580         }
581     }
582 
583     @Test
584     public void testLog10Power() {
585         for (double x = 0.1; x < 1.2; x += 0.001) {
586             SparseGradient sgX = SparseGradient.createVariable(0, x);
587             SparseGradient rebuiltX = SparseGradient.pow(10.0, sgX).log10();
588             SparseGradient zero = rebuiltX.subtract(sgX);
589             checkF0F1(zero, 0.0, 0.0);
590         }
591     }
592 
593     @Test
594     public void testSinCos() {
595         for (double x = 0.1; x < 1.2; x += 0.001) {
596             SparseGradient sgX = SparseGradient.createVariable(0, x);
597             SparseGradient sin = sgX.sin();
598             SparseGradient cos = sgX.cos();
599             double s = JdkMath.sin(x);
600             double c = JdkMath.cos(x);
601             checkF0F1(sin, s, c);
602             checkF0F1(cos, c, -s);
603         }
604     }
605 
606     @Test
607     public void testSinAsin() {
608         for (double x = 0.1; x < 1.2; x += 0.001) {
609             SparseGradient sgX = SparseGradient.createVariable(0, x);
610             SparseGradient rebuiltX = sgX.sin().asin();
611             SparseGradient zero = rebuiltX.subtract(sgX);
612             checkF0F1(zero, 0.0, 0.0);
613         }
614     }
615 
616     @Test
617     public void testCosAcos() {
618         for (double x = 0.1; x < 1.2; x += 0.001) {
619             SparseGradient sgX = SparseGradient.createVariable(0, x);
620             SparseGradient rebuiltX = sgX.cos().acos();
621             SparseGradient zero = rebuiltX.subtract(sgX);
622             checkF0F1(zero, 0.0, 0.0);
623         }
624     }
625 
626     @Test
627     public void testTanAtan() {
628         for (double x = 0.1; x < 1.2; x += 0.001) {
629             SparseGradient sgX = SparseGradient.createVariable(0, x);
630             SparseGradient rebuiltX = sgX.tan().atan();
631             SparseGradient zero = rebuiltX.subtract(sgX);
632             checkF0F1(zero, 0.0, 0.0);
633         }
634     }
635 
636     @Test
637     public void testTangentDefinition() {
638         for (double x = 0.1; x < 1.2; x += 0.001) {
639             SparseGradient sgX = SparseGradient.createVariable(0, x);
640             SparseGradient tan1 = sgX.sin().divide(sgX.cos());
641             SparseGradient tan2 = sgX.tan();
642             SparseGradient zero = tan1.subtract(tan2);
643             checkF0F1(zero, 0.0, 0.0);
644         }
645     }
646 
647     @Override
648     @Test
649     public void testAtan2() {
650         for (double x = -1.7; x < 2; x += 0.2) {
651             SparseGradient sgX = SparseGradient.createVariable(0, x);
652             for (double y = -1.7; y < 2; y += 0.2) {
653                 SparseGradient sgY = SparseGradient.createVariable(1, y);
654                 SparseGradient atan2 = SparseGradient.atan2(sgY, sgX);
655                 SparseGradient ref = sgY.divide(sgX).atan();
656                 if (x < 0) {
657                     ref = (y < 0) ? ref.subtract(JdkMath.PI) : ref.add(JdkMath.PI);
658                 }
659                 SparseGradient zero = atan2.subtract(ref);
660                 checkF0F1(zero, 0.0, 0.0);
661             }
662         }
663     }
664 
665     @Test
666     public void testAtan2SpecialCases() {
667 
668         SparseGradient pp =
669                 SparseGradient.atan2(SparseGradient.createVariable(1, +0.0),
670                                           SparseGradient.createVariable(1, +0.0));
671         Assert.assertEquals(0, pp.getValue(), 1.0e-15);
672         Assert.assertEquals(+1, JdkMath.copySign(1, pp.getValue()), 1.0e-15);
673 
674         SparseGradient pn =
675                 SparseGradient.atan2(SparseGradient.createVariable(1, +0.0),
676                                           SparseGradient.createVariable(1, -0.0));
677         Assert.assertEquals(JdkMath.PI, pn.getValue(), 1.0e-15);
678 
679         SparseGradient np =
680                 SparseGradient.atan2(SparseGradient.createVariable(1, -0.0),
681                                           SparseGradient.createVariable(1, +0.0));
682         Assert.assertEquals(0, np.getValue(), 1.0e-15);
683         Assert.assertEquals(-1, JdkMath.copySign(1, np.getValue()), 1.0e-15);
684 
685         SparseGradient nn =
686                 SparseGradient.atan2(SparseGradient.createVariable(1, -0.0),
687                                           SparseGradient.createVariable(1, -0.0));
688         Assert.assertEquals(-JdkMath.PI, nn.getValue(), 1.0e-15);
689     }
690 
691     @Test
692     public void testSinhDefinition() {
693         for (double x = 0.1; x < 1.2; x += 0.001) {
694             SparseGradient sgX = SparseGradient.createVariable(0, x);
695             SparseGradient sinh1 = sgX.exp().subtract(sgX.exp().reciprocal()).multiply(0.5);
696             SparseGradient sinh2 = sgX.sinh();
697             SparseGradient zero = sinh1.subtract(sinh2);
698             checkF0F1(zero, 0.0, 0.0);
699         }
700     }
701 
702     @Test
703     public void testCoshDefinition() {
704         for (double x = 0.1; x < 1.2; x += 0.001) {
705             SparseGradient sgX = SparseGradient.createVariable(0, x);
706             SparseGradient cosh1 = sgX.exp().add(sgX.exp().reciprocal()).multiply(0.5);
707             SparseGradient cosh2 = sgX.cosh();
708             SparseGradient zero = cosh1.subtract(cosh2);
709             checkF0F1(zero, 0.0, 0.0);
710         }
711     }
712 
713     @Test
714     public void testTanhDefinition() {
715         for (double x = 0.1; x < 1.2; x += 0.001) {
716             SparseGradient sgX = SparseGradient.createVariable(0, x);
717             SparseGradient tanh1 = sgX.exp().subtract(sgX.exp().reciprocal()).divide(sgX.exp().add(sgX.exp().reciprocal()));
718             SparseGradient tanh2 = sgX.tanh();
719             SparseGradient zero = tanh1.subtract(tanh2);
720             checkF0F1(zero, 0.0, 0.0);
721         }
722     }
723 
724     @Test
725     public void testSinhAsinh() {
726         for (double x = 0.1; x < 1.2; x += 0.001) {
727             SparseGradient sgX = SparseGradient.createVariable(0, x);
728             SparseGradient rebuiltX = sgX.sinh().asinh();
729             SparseGradient zero = rebuiltX.subtract(sgX);
730             checkF0F1(zero, 0.0, 0.0);
731         }
732     }
733 
734     @Test
735     public void testCoshAcosh() {
736         for (double x = 0.1; x < 1.2; x += 0.001) {
737             SparseGradient sgX = SparseGradient.createVariable(0, x);
738             SparseGradient rebuiltX = sgX.cosh().acosh();
739             SparseGradient zero = rebuiltX.subtract(sgX);
740             checkF0F1(zero, 0.0, 0.0);
741         }
742     }
743 
744     @Test
745     public void testTanhAtanh() {
746         for (double x = 0.1; x < 1.2; x += 0.001) {
747             SparseGradient sgX = SparseGradient.createVariable(0, x);
748             SparseGradient rebuiltX = sgX.tanh().atanh();
749             SparseGradient zero = rebuiltX.subtract(sgX);
750             checkF0F1(zero, 0.0, 0.0);
751         }
752     }
753 
754     @Test
755     public void testCompositionOneVariableY() {
756         for (double x = 0.1; x < 1.2; x += 0.1) {
757             SparseGradient sgX = SparseGradient.createConstant(x);
758             for (double y = 0.1; y < 1.2; y += 0.1) {
759                 SparseGradient sgY = SparseGradient.createVariable(0, y);
760                 SparseGradient f = sgX.divide(sgY).sqrt();
761                 double f0 = JdkMath.sqrt(x / y);
762                 double f1 = -x / (2 * y * y * f0);
763                 checkF0F1(f, f0, f1);
764             }
765         }
766     }
767 
768     @Test
769     public void testTaylorPolynomial() {
770         for (double x = 0; x < 1.2; x += 0.1) {
771             SparseGradient sgX = SparseGradient.createVariable(0, x);
772             for (double y = 0; y < 1.2; y += 0.2) {
773                 SparseGradient sgY = SparseGradient.createVariable(1, y);
774                 for (double z = 0; z < 1.2; z += 0.2) {
775                     SparseGradient sgZ = SparseGradient.createVariable(2, z);
776                     SparseGradient f = sgX.multiply(3).add(sgZ.multiply(-2)).add(sgY.multiply(5));
777                     for (double dx = -0.2; dx < 0.2; dx += 0.2) {
778                         for (double dy = -0.2; dy < 0.2; dy += 0.1) {
779                             for (double dz = -0.2; dz < 0.2; dz += 0.1) {
780                                 double ref = 3 * (x + dx) + 5 * (y + dy) -2 * (z + dz);
781                                 Assert.assertEquals(ref, f.taylor(dx, dy, dz), 3.0e-15);
782                             }
783                         }
784                     }
785                 }
786             }
787         }
788     }
789 
790     @Test
791     public void testTaylorAtan2() {
792         double x0 =  0.1;
793         double y0 = -0.3;
794             SparseGradient sgX   = SparseGradient.createVariable(0, x0);
795             SparseGradient sgY   = SparseGradient.createVariable(1, y0);
796             SparseGradient atan2 = SparseGradient.atan2(sgY, sgX);
797             double maxError = 0;
798             for (double dx = -0.05; dx < 0.05; dx += 0.001) {
799                 for (double dy = -0.05; dy < 0.05; dy += 0.001) {
800                     double ref = JdkMath.atan2(y0 + dy, x0 + dx);
801                     maxError = JdkMath.max(maxError, JdkMath.abs(ref - atan2.taylor(dx, dy)));
802                 }
803             }
804             double expectedError = 0.0241;
805             Assert.assertEquals(expectedError, maxError, 0.01 * expectedError);
806     }
807 
808     @Override
809     @Test
810     public void testAbs() {
811 
812         SparseGradient minusOne = SparseGradient.createVariable(0, -1.0);
813         Assert.assertEquals(+1.0, minusOne.abs().getValue(), 1.0e-15);
814         Assert.assertEquals(-1.0, minusOne.abs().getDerivative(0), 1.0e-15);
815 
816         SparseGradient plusOne = SparseGradient.createVariable(0, +1.0);
817         Assert.assertEquals(+1.0, plusOne.abs().getValue(), 1.0e-15);
818         Assert.assertEquals(+1.0, plusOne.abs().getDerivative(0), 1.0e-15);
819 
820         SparseGradient minusZero = SparseGradient.createVariable(0, -0.0);
821         Assert.assertEquals(+0.0, minusZero.abs().getValue(), 1.0e-15);
822         Assert.assertEquals(-1.0, minusZero.abs().getDerivative(0), 1.0e-15);
823 
824         SparseGradient plusZero = SparseGradient.createVariable(0, +0.0);
825         Assert.assertEquals(+0.0, plusZero.abs().getValue(), 1.0e-15);
826         Assert.assertEquals(+1.0, plusZero.abs().getDerivative(0), 1.0e-15);
827     }
828 
829     @Override
830     @Test
831     public void testSignum() {
832 
833         SparseGradient minusOne = SparseGradient.createVariable(0, -1.0);
834         Assert.assertEquals(-1.0, minusOne.signum().getValue(), 1.0e-15);
835         Assert.assertEquals( 0.0, minusOne.signum().getDerivative(0), 1.0e-15);
836 
837         SparseGradient plusOne = SparseGradient.createVariable(0, +1.0);
838         Assert.assertEquals(+1.0, plusOne.signum().getValue(), 1.0e-15);
839         Assert.assertEquals( 0.0, plusOne.signum().getDerivative(0), 1.0e-15);
840 
841         SparseGradient minusZero = SparseGradient.createVariable(0, -0.0);
842         Assert.assertEquals(-0.0, minusZero.signum().getValue(), 1.0e-15);
843         Assert.assertTrue(Double.doubleToLongBits(minusZero.signum().getValue()) < 0);
844         Assert.assertEquals( 0.0, minusZero.signum().getDerivative(0), 1.0e-15);
845 
846         SparseGradient plusZero = SparseGradient.createVariable(0, +0.0);
847         Assert.assertEquals(+0.0, plusZero.signum().getValue(), 1.0e-15);
848         Assert.assertEquals(0, Double.doubleToLongBits(plusZero.signum().getValue()));
849         Assert.assertEquals( 0.0, plusZero.signum().getDerivative(0), 1.0e-15);
850     }
851 
852     @Test
853     public void testCeilFloorRintLong() {
854 
855         SparseGradient x = SparseGradient.createVariable(0, -1.5);
856         Assert.assertEquals(-1.5, x.getValue(), 1.0e-15);
857         Assert.assertEquals(+1.0, x.getDerivative(0), 1.0e-15);
858         Assert.assertEquals(-1.0, x.ceil().getValue(), 1.0e-15);
859         Assert.assertEquals(+0.0, x.ceil().getDerivative(0), 1.0e-15);
860         Assert.assertEquals(-2.0, x.floor().getValue(), 1.0e-15);
861         Assert.assertEquals(+0.0, x.floor().getDerivative(0), 1.0e-15);
862         Assert.assertEquals(-2.0, x.rint().getValue(), 1.0e-15);
863         Assert.assertEquals(+0.0, x.rint().getDerivative(0), 1.0e-15);
864         Assert.assertEquals(-2.0, x.subtract(x.getField().getOne()).rint().getValue(), 1.0e-15);
865         Assert.assertEquals(-1L, x.round(), 1.0e-15);
866     }
867 
868     @Test
869     public void testCopySign() {
870 
871         SparseGradient minusOne = SparseGradient.createVariable(0, -1.0);
872         Assert.assertEquals(+1.0, minusOne.copySign(+1.0).getValue(), 1.0e-15);
873         Assert.assertEquals(-1.0, minusOne.copySign(+1.0).getDerivative(0), 1.0e-15);
874         Assert.assertEquals(-1.0, minusOne.copySign(-1.0).getValue(), 1.0e-15);
875         Assert.assertEquals(+1.0, minusOne.copySign(-1.0).getDerivative(0), 1.0e-15);
876         Assert.assertEquals(+1.0, minusOne.copySign(+0.0).getValue(), 1.0e-15);
877         Assert.assertEquals(-1.0, minusOne.copySign(+0.0).getDerivative(0), 1.0e-15);
878         Assert.assertEquals(-1.0, minusOne.copySign(-0.0).getValue(), 1.0e-15);
879         Assert.assertEquals(+1.0, minusOne.copySign(-0.0).getDerivative(0), 1.0e-15);
880         Assert.assertEquals(+1.0, minusOne.copySign(Double.NaN).getValue(), 1.0e-15);
881         Assert.assertEquals(-1.0, minusOne.copySign(Double.NaN).getDerivative(0), 1.0e-15);
882 
883         SparseGradient plusOne = SparseGradient.createVariable(0, +1.0);
884         Assert.assertEquals(+1.0, plusOne.copySign(+1.0).getValue(), 1.0e-15);
885         Assert.assertEquals(+1.0, plusOne.copySign(+1.0).getDerivative(0), 1.0e-15);
886         Assert.assertEquals(-1.0, plusOne.copySign(-1.0).getValue(), 1.0e-15);
887         Assert.assertEquals(-1.0, plusOne.copySign(-1.0).getDerivative(0), 1.0e-15);
888         Assert.assertEquals(+1.0, plusOne.copySign(+0.0).getValue(), 1.0e-15);
889         Assert.assertEquals(+1.0, plusOne.copySign(+0.0).getDerivative(0), 1.0e-15);
890         Assert.assertEquals(-1.0, plusOne.copySign(-0.0).getValue(), 1.0e-15);
891         Assert.assertEquals(-1.0, plusOne.copySign(-0.0).getDerivative(0), 1.0e-15);
892         Assert.assertEquals(+1.0, plusOne.copySign(Double.NaN).getValue(), 1.0e-15);
893         Assert.assertEquals(+1.0, plusOne.copySign(Double.NaN).getDerivative(0), 1.0e-15);
894     }
895 
896     @Test
897     public void testToDegreesDefinition() {
898         double epsilon = 3.0e-16;
899         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
900             for (double x = 0.1; x < 1.2; x += 0.001) {
901                 SparseGradient sgX = SparseGradient.createVariable(0, x);
902                 Assert.assertEquals(JdkMath.toDegrees(x), sgX.toDegrees().getValue(), epsilon);
903                 Assert.assertEquals(180 / JdkMath.PI, sgX.toDegrees().getDerivative(0), epsilon);
904             }
905         }
906     }
907 
908     @Test
909     public void testToRadiansDefinition() {
910         double epsilon = 3.0e-16;
911         for (int maxOrder = 0; maxOrder < 6; ++maxOrder) {
912             for (double x = 0.1; x < 1.2; x += 0.001) {
913                 SparseGradient sgX = SparseGradient.createVariable(0, x);
914                 Assert.assertEquals(JdkMath.toRadians(x), sgX.toRadians().getValue(), epsilon);
915                 Assert.assertEquals(JdkMath.PI / 180, sgX.toRadians().getDerivative(0), epsilon);
916             }
917         }
918     }
919 
920     @Test
921     public void testDegRad() {
922         for (double x = 0.1; x < 1.2; x += 0.001) {
923             SparseGradient sgX = SparseGradient.createVariable(0, x);
924             SparseGradient rebuiltX = sgX.toDegrees().toRadians();
925             SparseGradient zero = rebuiltX.subtract(sgX);
926             checkF0F1(zero, 0, 0);
927         }
928     }
929 
930     @Test
931     public void testCompose() {
932         PolynomialFunction poly =
933                 new PolynomialFunction(new double[] { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 });
934         for (double x = 0.1; x < 1.2; x += 0.001) {
935             SparseGradient sgX = SparseGradient.createVariable(0, x);
936             SparseGradient sgY1 = sgX.getField().getZero();
937             for (int i = poly.degree(); i >= 0; --i) {
938                 sgY1 = sgY1.multiply(sgX).add(poly.getCoefficients()[i]);
939             }
940             SparseGradient sgY2 = sgX.compose(poly.value(x), poly.polynomialDerivative().value(x));
941             SparseGradient zero = sgY1.subtract(sgY2);
942             checkF0F1(zero, 0.0, 0.0);
943         }
944     }
945 
946     @Test
947     public void testField() {
948             SparseGradient x = SparseGradient.createVariable(0, 1.0);
949             checkF0F1(x.getField().getZero(), 0.0, 0.0, 0.0, 0.0);
950             checkF0F1(x.getField().getOne(), 1.0, 0.0, 0.0, 0.0);
951             Assert.assertEquals(SparseGradient.class, x.getField().getRuntimeClass());
952     }
953 
954     @Test
955     public void testLinearCombination1DSDS() {
956         final SparseGradient[] a = new SparseGradient[] {
957             SparseGradient.createVariable(0, -1321008684645961.0 / 268435456.0),
958             SparseGradient.createVariable(1, -5774608829631843.0 / 268435456.0),
959             SparseGradient.createVariable(2, -7645843051051357.0 / 8589934592.0)
960         };
961         final SparseGradient[] b = new SparseGradient[] {
962             SparseGradient.createVariable(3, -5712344449280879.0 / 2097152.0),
963             SparseGradient.createVariable(4, -4550117129121957.0 / 2097152.0),
964             SparseGradient.createVariable(5, 8846951984510141.0 / 131072.0)
965         };
966 
967         final SparseGradient abSumInline = a[0].linearCombination(a[0], b[0], a[1], b[1], a[2], b[2]);
968         final SparseGradient abSumArray = a[0].linearCombination(a, b);
969 
970         Assert.assertEquals(abSumInline.getValue(), abSumArray.getValue(), 1.0e-15);
971         Assert.assertEquals(-1.8551294182586248737720779899, abSumInline.getValue(), 1.0e-15);
972         Assert.assertEquals(b[0].getValue(), abSumInline.getDerivative(0), 1.0e-15);
973         Assert.assertEquals(b[1].getValue(), abSumInline.getDerivative(1), 1.0e-15);
974         Assert.assertEquals(b[2].getValue(), abSumInline.getDerivative(2), 1.0e-15);
975         Assert.assertEquals(a[0].getValue(), abSumInline.getDerivative(3), 1.0e-15);
976         Assert.assertEquals(a[1].getValue(), abSumInline.getDerivative(4), 1.0e-15);
977         Assert.assertEquals(a[2].getValue(), abSumInline.getDerivative(5), 1.0e-15);
978     }
979 
980     @Test
981     public void testLinearCombination1DoubleDS() {
982         final double[] a = new double[] {
983             -1321008684645961.0 / 268435456.0,
984             -5774608829631843.0 / 268435456.0,
985             -7645843051051357.0 / 8589934592.0
986         };
987         final SparseGradient[] b = new SparseGradient[] {
988             SparseGradient.createVariable(0, -5712344449280879.0 / 2097152.0),
989             SparseGradient.createVariable(1, -4550117129121957.0 / 2097152.0),
990             SparseGradient.createVariable(2, 8846951984510141.0 / 131072.0)
991         };
992 
993         final SparseGradient abSumInline = b[0].linearCombination(a[0], b[0],
994                                                                        a[1], b[1],
995                                                                        a[2], b[2]);
996         final SparseGradient abSumArray = b[0].linearCombination(a, b);
997 
998         Assert.assertEquals(abSumInline.getValue(), abSumArray.getValue(), 1.0e-15);
999         Assert.assertEquals(-1.8551294182586248737720779899, abSumInline.getValue(), 1.0e-15);
1000         Assert.assertEquals(a[0], abSumInline.getDerivative(0), 1.0e-15);
1001         Assert.assertEquals(a[1], abSumInline.getDerivative(1), 1.0e-15);
1002         Assert.assertEquals(a[2], abSumInline.getDerivative(2), 1.0e-15);
1003     }
1004 
1005     @Test
1006     public void testLinearCombination2DSDS() {
1007         // we compare accurate versus naive dot product implementations
1008         // on regular vectors (i.e. not extreme cases like in the previous test)
1009         UniformRandomProvider random = RandomSource.WELL_1024_A.create(0xc6af886975069f11L);
1010 
1011         for (int i = 0; i < 10000; ++i) {
1012             final SparseGradient[] u = new SparseGradient[4];
1013             final SparseGradient[] v = new SparseGradient[4];
1014             for (int j = 0; j < u.length; ++j) {
1015                 u[j] = SparseGradient.createVariable(j, 1e17 * random.nextDouble());
1016                 v[j] = SparseGradient.createConstant(1e17 * random.nextDouble());
1017             }
1018 
1019             SparseGradient lin = u[0].linearCombination(u[0], v[0], u[1], v[1]);
1020             double ref = u[0].getValue() * v[0].getValue() +
1021                          u[1].getValue() * v[1].getValue();
1022             Assert.assertEquals(ref, lin.getValue(), 1.0e-15 * JdkMath.abs(ref));
1023             Assert.assertEquals(v[0].getValue(), lin.getDerivative(0), 1.0e-15 * JdkMath.abs(v[0].getValue()));
1024             Assert.assertEquals(v[1].getValue(), lin.getDerivative(1), 1.0e-15 * JdkMath.abs(v[1].getValue()));
1025 
1026             lin = u[0].linearCombination(u[0], v[0], u[1], v[1], u[2], v[2]);
1027             ref = u[0].getValue() * v[0].getValue() +
1028                   u[1].getValue() * v[1].getValue() +
1029                   u[2].getValue() * v[2].getValue();
1030             Assert.assertEquals(ref, lin.getValue(), 1.0e-15 * JdkMath.abs(ref));
1031             Assert.assertEquals(v[0].getValue(), lin.getDerivative(0), 1.0e-15 * JdkMath.abs(v[0].getValue()));
1032             Assert.assertEquals(v[1].getValue(), lin.getDerivative(1), 1.0e-15 * JdkMath.abs(v[1].getValue()));
1033             Assert.assertEquals(v[2].getValue(), lin.getDerivative(2), 1.0e-15 * JdkMath.abs(v[2].getValue()));
1034 
1035             lin = u[0].linearCombination(u[0], v[0], u[1], v[1], u[2], v[2], u[3], v[3]);
1036             ref = u[0].getValue() * v[0].getValue() +
1037                   u[1].getValue() * v[1].getValue() +
1038                   u[2].getValue() * v[2].getValue() +
1039                   u[3].getValue() * v[3].getValue();
1040             Assert.assertEquals(ref, lin.getValue(), 1.0e-15 * JdkMath.abs(ref));
1041             Assert.assertEquals(v[0].getValue(), lin.getDerivative(0), 1.0e-15 * JdkMath.abs(v[0].getValue()));
1042             Assert.assertEquals(v[1].getValue(), lin.getDerivative(1), 1.0e-15 * JdkMath.abs(v[1].getValue()));
1043             Assert.assertEquals(v[2].getValue(), lin.getDerivative(2), 1.0e-15 * JdkMath.abs(v[2].getValue()));
1044             Assert.assertEquals(v[3].getValue(), lin.getDerivative(3), 1.0e-15 * JdkMath.abs(v[3].getValue()));
1045         }
1046     }
1047 
1048     @Test
1049     public void testLinearCombination2DoubleDS() {
1050         // we compare accurate versus naive dot product implementations
1051         // on regular vectors (i.e. not extreme cases like in the previous test)
1052         UniformRandomProvider random = RandomSource.WELL_1024_A.create(0xc6af886975069f11L);
1053 
1054         for (int i = 0; i < 10000; ++i) {
1055             final double[] u = new double[4];
1056             final SparseGradient[] v = new SparseGradient[4];
1057             for (int j = 0; j < u.length; ++j) {
1058                 u[j] = 1e17 * random.nextDouble();
1059                 v[j] = SparseGradient.createVariable(j, 1e17 * random.nextDouble());
1060             }
1061 
1062             SparseGradient lin = v[0].linearCombination(u[0], v[0], u[1], v[1]);
1063             double ref = u[0] * v[0].getValue() +
1064                          u[1] * v[1].getValue();
1065             Assert.assertEquals(ref, lin.getValue(), 1.0e-15 * JdkMath.abs(ref));
1066             Assert.assertEquals(u[0], lin.getDerivative(0), 1.0e-15 * JdkMath.abs(v[0].getValue()));
1067             Assert.assertEquals(u[1], lin.getDerivative(1), 1.0e-15 * JdkMath.abs(v[1].getValue()));
1068 
1069             lin = v[0].linearCombination(u[0], v[0], u[1], v[1], u[2], v[2]);
1070             ref = u[0] * v[0].getValue() +
1071                   u[1] * v[1].getValue() +
1072                   u[2] * v[2].getValue();
1073             Assert.assertEquals(ref, lin.getValue(), 1.0e-15 * JdkMath.abs(ref));
1074             Assert.assertEquals(u[0], lin.getDerivative(0), 1.0e-15 * JdkMath.abs(v[0].getValue()));
1075             Assert.assertEquals(u[1], lin.getDerivative(1), 1.0e-15 * JdkMath.abs(v[1].getValue()));
1076             Assert.assertEquals(u[2], lin.getDerivative(2), 1.0e-15 * JdkMath.abs(v[2].getValue()));
1077 
1078             lin = v[0].linearCombination(u[0], v[0], u[1], v[1], u[2], v[2], u[3], v[3]);
1079             ref = u[0] * v[0].getValue() +
1080                   u[1] * v[1].getValue() +
1081                   u[2] * v[2].getValue() +
1082                   u[3] * v[3].getValue();
1083             Assert.assertEquals(ref, lin.getValue(), 1.0e-15 * JdkMath.abs(ref));
1084             Assert.assertEquals(u[0], lin.getDerivative(0), 1.0e-15 * JdkMath.abs(v[0].getValue()));
1085             Assert.assertEquals(u[1], lin.getDerivative(1), 1.0e-15 * JdkMath.abs(v[1].getValue()));
1086             Assert.assertEquals(u[2], lin.getDerivative(2), 1.0e-15 * JdkMath.abs(v[2].getValue()));
1087             Assert.assertEquals(u[3], lin.getDerivative(3), 1.0e-15 * JdkMath.abs(v[3].getValue()));
1088         }
1089     }
1090 
1091     private void checkF0F1(SparseGradient sg, double value, double...derivatives) {
1092 
1093         // check value
1094         Assert.assertEquals(value, sg.getValue(), 1.0e-13);
1095 
1096         // check first order derivatives
1097         for (int i = 0; i < derivatives.length; ++i) {
1098             Assert.assertEquals(derivatives[i], sg.getDerivative(i), 1.0e-13);
1099         }
1100     }
1101 }