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  package org.hipparchus.analysis.differentiation;
23  
24  import java.io.Serializable;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import org.hipparchus.Field;
30  import org.hipparchus.exception.MathIllegalArgumentException;
31  import org.hipparchus.util.FastMath;
32  import org.hipparchus.util.MathArrays;
33  import org.hipparchus.util.MathUtils;
34  import org.hipparchus.util.Precision;
35  
36  /**
37   * First derivative computation with large number of variables.
38   * <p>
39   * This class plays a similar role to {@link DerivativeStructure}, with
40   * a focus on efficiency when dealing with large number of independent variables
41   * and most computation depend only on a few of them, and when only first derivative
42   * is desired. When these conditions are met, this class should be much faster than
43   * {@link DerivativeStructure} and use less memory.
44   * </p>
45   *
46   */
47  public class SparseGradient implements Derivative1<SparseGradient>, Serializable {
48  
49      /** Serializable UID. */
50      private static final long serialVersionUID = 20131025L;
51  
52      /** Value of the calculation. */
53      private double value;
54  
55      /** Stored derivative, each key representing a different independent variable. */
56      private final Map<Integer, Double> derivatives;
57  
58      /** Internal constructor.
59       * @param value value of the function
60       * @param derivatives derivatives map, a deep copy will be performed,
61       * so the map given here will remain safe from changes in the new instance,
62       * may be null to create an empty derivatives map, i.e. a constant value
63       */
64      private SparseGradient(final double value, final Map<Integer, Double> derivatives) {
65          this.value = value;
66          this.derivatives = new HashMap<>();
67          if (derivatives != null) {
68              this.derivatives.putAll(derivatives);
69          }
70      }
71  
72      /** Internal constructor.
73       * @param value value of the function
74       * @param scale scaling factor to apply to all derivatives
75       * @param derivatives derivatives map, a deep copy will be performed,
76       * so the map given here will remain safe from changes in the new instance,
77       * may be null to create an empty derivatives map, i.e. a constant value
78       */
79      private SparseGradient(final double value, final double scale,
80                               final Map<Integer, Double> derivatives) {
81          this.value = value;
82          this.derivatives = new HashMap<>();
83          if (derivatives != null) {
84              for (final Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
85                  this.derivatives.put(entry.getKey(), scale * entry.getValue());
86              }
87          }
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      public int getFreeParameters() {
93          return derivatives.size();
94      }
95  
96      /** {@inheritDoc} */
97      @Override
98      public double getPartialDerivative(int... orders) throws MathIllegalArgumentException {
99          return getDerivative(orders[0]);
100     }
101 
102     /** {@inheritDoc} */
103     @Override
104     public SparseGradient newInstance(final double v) {
105         return createConstant(v);
106     }
107 
108     /** {@inheritDoc} */
109     @Override
110     public SparseGradient withValue(final double v) {
111         return new SparseGradient(v, derivatives);
112     }
113 
114     /** Factory method creating a constant.
115      * @param value value of the constant
116      * @return a new instance
117      */
118     public static SparseGradient createConstant(final double value) {
119         return new SparseGradient(value, Collections.emptyMap());
120     }
121 
122     /** Factory method creating an independent variable.
123      * @param idx index of the variable
124      * @param value value of the variable
125      * @return a new instance
126      */
127     public static SparseGradient createVariable(final int idx, final double value) {
128         return new SparseGradient(value, Collections.singletonMap(idx, 1.0));
129     }
130 
131     /**
132      * Find the number of variables.
133      * @return number of variables
134      */
135     @Deprecated
136     public int numVars() {
137         return getFreeParameters();
138     }
139 
140     /**
141      * Get the derivative with respect to a particular index variable.
142      *
143      * @param index index to differentiate with.
144      * @return derivative with respect to a particular index variable
145      */
146     public double getDerivative(final int index) {
147         final Double out = derivatives.get(index);
148         return (out == null) ? 0.0 : out;
149     }
150 
151     /**
152      * Get the value of the function.
153      * @return value of the function.
154      */
155     @Override
156     public double getValue() {
157         return value;
158     }
159 
160     /** {@inheritDoc} */
161     @Override
162     public SparseGradient add(final SparseGradient a) {
163         final SparseGradient out = new SparseGradient(value + a.value, derivatives);
164         for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
165             final int id = entry.getKey();
166             final Double old = out.derivatives.get(id);
167             if (old == null) {
168                 out.derivatives.put(id, entry.getValue());
169             } else {
170                 out.derivatives.put(id, old + entry.getValue());
171             }
172         }
173 
174         return out;
175     }
176 
177     /**
178      * Add in place.
179      * <p>
180      * This method is designed to be faster when used multiple times in a loop.
181      * </p>
182      * <p>
183      * The instance is changed here, in order to not change the
184      * instance the {@link #add(SparseGradient)} method should
185      * be used.
186      * </p>
187      * @param a instance to add
188      */
189     public void addInPlace(final SparseGradient a) {
190         value += a.value;
191         for (final Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
192             final int id = entry.getKey();
193             final Double old = derivatives.get(id);
194             if (old == null) {
195                 derivatives.put(id, entry.getValue());
196             } else {
197                 derivatives.put(id, old + entry.getValue());
198             }
199         }
200     }
201 
202     /** {@inheritDoc} */
203     @Override
204     public SparseGradient subtract(final SparseGradient a) {
205         final SparseGradient out = new SparseGradient(value - a.value, derivatives);
206         for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
207             final int id = entry.getKey();
208             final Double old = out.derivatives.get(id);
209             if (old == null) {
210                 out.derivatives.put(id, -entry.getValue());
211             } else {
212                 out.derivatives.put(id, old - entry.getValue());
213             }
214         }
215         return out;
216     }
217 
218     /** {@inheritDoc} */
219     @Override
220     public SparseGradient multiply(final SparseGradient a) {
221         final SparseGradient out =
222             new SparseGradient(value * a.value, Collections.emptyMap());
223 
224         // Derivatives.
225         for (Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
226             out.derivatives.put(entry.getKey(), a.value * entry.getValue());
227         }
228         for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
229             final int id = entry.getKey();
230             final Double old = out.derivatives.get(id);
231             if (old == null) {
232                 out.derivatives.put(id, value * entry.getValue());
233             } else {
234                 out.derivatives.put(id, old + value * entry.getValue());
235             }
236         }
237         return out;
238     }
239 
240     /**
241      * Multiply in place.
242      * <p>
243      * This method is designed to be faster when used multiple times in a loop.
244      * </p>
245      * <p>
246      * The instance is changed here, in order to not change the
247      * instance the {@link #add(SparseGradient)} method should
248      * be used.
249      * </p>
250      * @param a instance to multiply
251      */
252     public void multiplyInPlace(final SparseGradient a) {
253         // Derivatives.
254         for (Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
255             derivatives.put(entry.getKey(), a.value * entry.getValue());
256         }
257         for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
258             final int id = entry.getKey();
259             final Double old = derivatives.get(id);
260             if (old == null) {
261                 derivatives.put(id, value * entry.getValue());
262             } else {
263                 derivatives.put(id, old + value * entry.getValue());
264             }
265         }
266         value *= a.value;
267     }
268 
269     /** {@inheritDoc} */
270     @Override
271     public SparseGradient multiply(final double c) {
272         return new SparseGradient(value * c, c, derivatives);
273     }
274 
275     /** {@inheritDoc} */
276     @Override
277     public SparseGradient multiply(final int n) {
278         return new SparseGradient(value * n, n, derivatives);
279     }
280 
281     /** {@inheritDoc} */
282     @Override
283     public SparseGradient divide(final SparseGradient a) {
284         final SparseGradient out = new SparseGradient(value / a.value, Collections.emptyMap());
285 
286         // Derivatives.
287         for (Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
288             out.derivatives.put(entry.getKey(), entry.getValue() / a.value);
289         }
290         for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
291             final int id = entry.getKey();
292             final Double old = out.derivatives.get(id);
293             if (old == null) {
294                 out.derivatives.put(id, -out.value / a.value * entry.getValue());
295             } else {
296                 out.derivatives.put(id, old - out.value / a.value * entry.getValue());
297             }
298         }
299         return out;
300     }
301 
302     /** {@inheritDoc} */
303     @Override
304     public SparseGradient divide(final double c) {
305         return new SparseGradient(value / c, 1.0 / c, derivatives);
306     }
307 
308     /** {@inheritDoc} */
309     @Override
310     public SparseGradient negate() {
311         return new SparseGradient(-value, -1.0, derivatives);
312     }
313 
314     /** {@inheritDoc} */
315     @Override
316     public Field<SparseGradient> getField() {
317         return SparseGradientField.getInstance();
318     }
319 
320     /** Local field for sparse gradient. */
321     private static class SparseGradientField implements Field<SparseGradient> {
322 
323         /** Zero constant. */
324         private final SparseGradient zero;
325 
326         /** One constant. */
327         private final SparseGradient one;
328 
329         /** Private constructor for the singleton.
330          */
331         private SparseGradientField() {
332             zero = createConstant(0);
333             one  = createConstant(1);
334         }
335 
336         /** Get the unique instance.
337          * @return the unique instance
338          */
339         public static SparseGradientField getInstance() {
340             return LazyHolder.INSTANCE;
341         }
342 
343         /** {@inheritDoc} */
344         @Override
345         public SparseGradient getZero() {
346             return zero;
347         }
348 
349         /** {@inheritDoc} */
350         @Override
351         public SparseGradient getOne() {
352             return one;
353         }
354 
355         /** {@inheritDoc} */
356         @Override
357         public Class<SparseGradient> getRuntimeClass() {
358             return SparseGradient.class;
359         }
360 
361         /** {@inheritDoc} */
362         @Override
363         public boolean equals(final Object other) {
364             return this == other;
365         }
366 
367         /** {@inheritDoc} */
368         @Override
369         public int hashCode() {
370             return 0x142aeff7;
371         }
372 
373         // CHECKSTYLE: stop HideUtilityClassConstructor
374         /** Holder for the instance.
375          * <p>We use here the Initialization On Demand Holder Idiom.</p>
376          */
377         private static class LazyHolder {
378             /** Cached field instance. */
379             private static final SparseGradientField INSTANCE = new SparseGradientField();
380         }
381         // CHECKSTYLE: resume HideUtilityClassConstructor
382 
383     }
384 
385     /** {@inheritDoc} */
386     @Override
387     public SparseGradient remainder(final double a) {
388         return new SparseGradient(FastMath.IEEEremainder(value, a), derivatives);
389     }
390 
391     /** {@inheritDoc} */
392     @Override
393     public SparseGradient remainder(final SparseGradient a) {
394 
395         // compute k such that lhs % rhs = lhs - k rhs
396         final double rem = FastMath.IEEEremainder(value, a.value);
397         final double k   = FastMath.rint((value - rem) / a.value);
398 
399         return subtract(a.multiply(k));
400 
401     }
402 
403     /** {@inheritDoc} */
404     @Override
405     public SparseGradient abs() {
406         if (Double.doubleToLongBits(value) < 0) {
407             // we use the bits representation to also handle -0.0
408             return negate();
409         } else {
410             return this;
411         }
412     }
413 
414     /** {@inheritDoc} */
415     @Override
416     public SparseGradient copySign(final SparseGradient sign) {
417         final long m = Double.doubleToLongBits(value);
418         final long s = Double.doubleToLongBits(sign.value);
419         if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK
420             return this;
421         }
422         return negate(); // flip sign
423     }
424 
425     /** {@inheritDoc} */
426     @Override
427     public SparseGradient copySign(final double sign) {
428         final long m = Double.doubleToLongBits(value);
429         final long s = Double.doubleToLongBits(sign);
430         if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK
431             return this;
432         }
433         return negate(); // flip sign
434     }
435 
436     /** {@inheritDoc} */
437     @Override
438     public SparseGradient scalb(final int n) {
439         final SparseGradient out = new SparseGradient(FastMath.scalb(value, n), Collections.emptyMap());
440         for (Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
441             out.derivatives.put(entry.getKey(), FastMath.scalb(entry.getValue(), n));
442         }
443         return out;
444     }
445 
446     /** {@inheritDoc} */
447     @Override
448     public SparseGradient hypot(final SparseGradient y) {
449         if (Double.isInfinite(value) || Double.isInfinite(y.value)) {
450             return createConstant(Double.POSITIVE_INFINITY);
451         } else if (Double.isNaN(value) || Double.isNaN(y.value)) {
452             return createConstant(Double.NaN);
453         } else {
454 
455             final int expX = FastMath.getExponent(value);
456             final int expY = FastMath.getExponent(y.value);
457             if (expX > expY + 27) {
458                 // y is negligible with respect to x
459                 return abs();
460             } else if (expY > expX + 27) {
461                 // x is negligible with respect to y
462                 return y.abs();
463             } else {
464 
465                 // find an intermediate scale to avoid both overflow and underflow
466                 final int middleExp = (expX + expY) / 2;
467 
468                 // scale parameters without losing precision
469                 final SparseGradient scaledX = scalb(-middleExp);
470                 final SparseGradient scaledY = y.scalb(-middleExp);
471 
472                 // compute scaled hypotenuse
473                 final SparseGradient scaledH =
474                         scaledX.multiply(scaledX).add(scaledY.multiply(scaledY)).sqrt();
475 
476                 // remove scaling
477                 return scaledH.scalb(middleExp);
478 
479             }
480 
481         }
482     }
483 
484     /**
485      * Returns the hypotenuse of a triangle with sides {@code x} and {@code y}
486      * - sqrt(<i>x</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
487      * avoiding intermediate overflow or underflow.
488      *
489      * <ul>
490      * <li> If either argument is infinite, then the result is positive infinity.</li>
491      * <li> else, if either argument is NaN then the result is NaN.</li>
492      * </ul>
493      *
494      * @param x a value
495      * @param y a value
496      * @return sqrt(<i>x</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
497      */
498     public static SparseGradient hypot(final SparseGradient x, final SparseGradient y) {
499         return x.hypot(y);
500     }
501 
502     /** {@inheritDoc} */
503     @Override
504     public SparseGradient sqrt() {
505         final double sqrt = FastMath.sqrt(value);
506         return new SparseGradient(sqrt, 0.5 / sqrt, derivatives);
507     }
508 
509     /** {@inheritDoc} */
510     @Override
511     public SparseGradient pow(final double p) {
512         return new SparseGradient(FastMath.pow(value,  p), p * FastMath.pow(value,  p - 1), derivatives);
513     }
514 
515     /** {@inheritDoc} */
516     @Override
517     public SparseGradient pow(final int n) {
518         if (n == 0) {
519             return getField().getOne();
520         } else {
521             final double valueNm1 = FastMath.pow(value,  n - 1);
522             return new SparseGradient(value * valueNm1, n * valueNm1, derivatives);
523         }
524     }
525 
526     /** Compute a<sup>x</sup> where a is a double and x a {@link SparseGradient}
527      * @param a number to exponentiate
528      * @param x power to apply
529      * @return a<sup>x</sup>
530      */
531     public static SparseGradient pow(final double a, final SparseGradient x) {
532         if (a == 0) {
533             if (x.value == 0) {
534                 return x.compose(1.0, Double.NEGATIVE_INFINITY);
535             } else if (x.value < 0) {
536                 return x.compose(Double.NaN, Double.NaN);
537             } else {
538                 return x.getField().getZero();
539             }
540         } else {
541             final double ax = FastMath.pow(a, x.value);
542             return new SparseGradient(ax, ax * FastMath.log(a), x.derivatives);
543         }
544     }
545 
546     /** {@inheritDoc} */
547     @Override
548     public SparseGradient atan2(final SparseGradient x) {
549 
550         // compute r = sqrt(x^2+y^2)
551         final SparseGradient r = square().add(x.square()).sqrt();
552 
553         final SparseGradient a;
554         if (x.value >= 0) {
555 
556             // compute atan2(y, x) = 2 atan(y / (r + x))
557             a = divide(r.add(x)).atan().multiply(2);
558 
559         } else {
560 
561             // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x))
562             final SparseGradient tmp = divide(r.subtract(x)).atan().multiply(-2);
563             a = tmp.add(tmp.value <= 0 ? -FastMath.PI : FastMath.PI);
564 
565         }
566 
567         // fix value to take special cases (+0/+0, +0/-0, -0/+0, -0/-0, +/-infinity) correctly
568         a.value = FastMath.atan2(value, x.value);
569 
570         return a;
571 
572     }
573 
574     /** Two arguments arc tangent operation.
575      * @param y first argument of the arc tangent
576      * @param x second argument of the arc tangent
577      * @return atan2(y, x)
578      */
579     public static SparseGradient atan2(final SparseGradient y, final SparseGradient x) {
580         return y.atan2(x);
581     }
582 
583     /** {@inheritDoc} */
584     @Override
585     public SparseGradient toDegrees() {
586         return new SparseGradient(FastMath.toDegrees(value), FastMath.toDegrees(1.0), derivatives);
587     }
588 
589     /** {@inheritDoc} */
590     @Override
591     public SparseGradient toRadians() {
592         return new SparseGradient(FastMath.toRadians(value), FastMath.toRadians(1.0), derivatives);
593     }
594 
595     /** Evaluate Taylor expansion of a sparse gradient.
596      * @param delta parameters offsets (&Delta;x, &Delta;y, ...)
597      * @return value of the Taylor expansion at x + &Delta;x, y + &Delta;y, ...
598      */
599     public double taylor(final double ... delta) {
600         double y = value;
601         for (int i = 0; i < delta.length; ++i) {
602             y += delta[i] * getDerivative(i);
603         }
604         return y;
605     }
606 
607     /** Compute composition of the instance by a univariate function.
608      * @param f array of value and derivatives of the function at
609      * the current point (i.e. [f({@link #getValue()}),
610      * f'({@link #getValue()}), f''({@link #getValue()})...]).
611      * @return f(this)
612      * @exception MathIllegalArgumentException if the number of elements
613      * in the array is not equal to 2 (i.e. value and first derivative)
614      */
615     @Override
616     public SparseGradient compose(final double... f) {
617         MathUtils.checkDimension(f.length, 2);
618         return compose(f[0], f[1]);
619     }
620 
621     /** {@inheritDoc} */
622     @Override
623     public SparseGradient compose(final double f0, final double f1) {
624         return new SparseGradient(f0, f1, derivatives);
625     }
626 
627     /** {@inheritDoc} */
628     @Override
629     public SparseGradient linearCombination(final SparseGradient[] a,
630                                             final SparseGradient[] b)
631         throws MathIllegalArgumentException {
632 
633         // compute a simple value, with all l derivatives
634         SparseGradient out = a[0].getField().getZero();
635         for (int i = 0; i < a.length; ++i) {
636             out = out.add(a[i].multiply(b[i]));
637         }
638 
639         // recompute an accurate value, taking care of cancellations
640         final double[] aDouble = new double[a.length];
641         for (int i = 0; i < a.length; ++i) {
642             aDouble[i] = a[i].getValue();
643         }
644         final double[] bDouble = new double[b.length];
645         for (int i = 0; i < b.length; ++i) {
646             bDouble[i] = b[i].getValue();
647         }
648         out.value = MathArrays.linearCombination(aDouble, bDouble);
649 
650         return out;
651 
652     }
653 
654     /** {@inheritDoc} */
655     @Override
656     public SparseGradient linearCombination(final double[] a, final SparseGradient[] b) {
657 
658         // compute a simple value, with all partial derivatives
659         SparseGradient out = b[0].getField().getZero();
660         for (int i = 0; i < a.length; ++i) {
661             out = out.add(b[i].multiply(a[i]));
662         }
663 
664         // recompute an accurate value, taking care of cancellations
665         final double[] bDouble = new double[b.length];
666         for (int i = 0; i < b.length; ++i) {
667             bDouble[i] = b[i].getValue();
668         }
669         out.value = MathArrays.linearCombination(a, bDouble);
670 
671         return out;
672 
673     }
674 
675     /** {@inheritDoc} */
676     @Override
677     public SparseGradient linearCombination(final SparseGradient a1, final SparseGradient b1,
678                                               final SparseGradient a2, final SparseGradient b2) {
679 
680         // compute a simple value, with all partial derivatives
681         SparseGradient out = a1.multiply(b1).add(a2.multiply(b2));
682 
683         // recompute an accurate value, taking care of cancellations
684         out.value = MathArrays.linearCombination(a1.value, b1.value, a2.value, b2.value);
685 
686         return out;
687 
688     }
689 
690     /** {@inheritDoc} */
691     @Override
692     public SparseGradient linearCombination(final double a1, final SparseGradient b1,
693                                               final double a2, final SparseGradient b2) {
694 
695         // compute a simple value, with all partial derivatives
696         SparseGradient out = b1.multiply(a1).add(b2.multiply(a2));
697 
698         // recompute an accurate value, taking care of cancellations
699         out.value = MathArrays.linearCombination(a1, b1.value, a2, b2.value);
700 
701         return out;
702 
703     }
704 
705     /** {@inheritDoc} */
706     @Override
707     public SparseGradient linearCombination(final SparseGradient a1, final SparseGradient b1,
708                                               final SparseGradient a2, final SparseGradient b2,
709                                               final SparseGradient a3, final SparseGradient b3) {
710 
711         // compute a simple value, with all partial derivatives
712         SparseGradient out = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3));
713 
714         // recompute an accurate value, taking care of cancellations
715         out.value = MathArrays.linearCombination(a1.value, b1.value,
716                                                  a2.value, b2.value,
717                                                  a3.value, b3.value);
718 
719         return out;
720 
721     }
722 
723     /** {@inheritDoc} */
724     @Override
725     public SparseGradient linearCombination(final double a1, final SparseGradient b1,
726                                               final double a2, final SparseGradient b2,
727                                               final double a3, final SparseGradient b3) {
728 
729         // compute a simple value, with all partial derivatives
730         SparseGradient out = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3));
731 
732         // recompute an accurate value, taking care of cancellations
733         out.value = MathArrays.linearCombination(a1, b1.value,
734                                                  a2, b2.value,
735                                                  a3, b3.value);
736 
737         return out;
738 
739     }
740 
741     /** {@inheritDoc} */
742     @Override
743     public SparseGradient linearCombination(final SparseGradient a1, final SparseGradient b1,
744                                               final SparseGradient a2, final SparseGradient b2,
745                                               final SparseGradient a3, final SparseGradient b3,
746                                               final SparseGradient a4, final SparseGradient b4) {
747 
748         // compute a simple value, with all partial derivatives
749         SparseGradient out = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)).add(a4.multiply(b4));
750 
751         // recompute an accurate value, taking care of cancellations
752         out.value = MathArrays.linearCombination(a1.value, b1.value,
753                                                  a2.value, b2.value,
754                                                  a3.value, b3.value,
755                                                  a4.value, b4.value);
756 
757         return out;
758 
759     }
760 
761     /** {@inheritDoc} */
762     @Override
763     public SparseGradient linearCombination(final double a1, final SparseGradient b1,
764                                               final double a2, final SparseGradient b2,
765                                               final double a3, final SparseGradient b3,
766                                               final double a4, final SparseGradient b4) {
767 
768         // compute a simple value, with all partial derivatives
769         SparseGradient out = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)).add(b4.multiply(a4));
770 
771         // recompute an accurate value, taking care of cancellations
772         out.value = MathArrays.linearCombination(a1, b1.value,
773                                                  a2, b2.value,
774                                                  a3, b3.value,
775                                                  a4, b4.value);
776 
777         return out;
778 
779     }
780 
781     /** {@inheritDoc} */
782     @Override
783     public SparseGradient getPi() {
784         return new SparseGradient(FastMath.PI, null);
785     }
786 
787     /**
788      * Test for the equality of two sparse gradients.
789      * <p>
790      * Sparse gradients are considered equal if they have the same value
791      * and the same derivatives.
792      * </p>
793      * @param other Object to test for equality to this
794      * @return true if two sparse gradients are equal
795      */
796     @Override
797     public boolean equals(Object other) {
798 
799         if (this == other) {
800             return true;
801         }
802 
803         if (other instanceof SparseGradient) {
804             final SparseGradient rhs = (SparseGradient)other;
805             if (!Precision.equals(value, rhs.value, 1)) {
806                 return false;
807             }
808             if (derivatives.size() != rhs.derivatives.size()) {
809                 return false;
810             }
811             for (final Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
812                 if (!rhs.derivatives.containsKey(entry.getKey())) {
813                     return false;
814                 }
815                 if (!Precision.equals(entry.getValue(), rhs.derivatives.get(entry.getKey()), 1)) {
816                     return false;
817                 }
818             }
819             return true;
820         }
821 
822         return false;
823 
824     }
825 
826     /**
827      * Get a hashCode for the derivative structure.
828      * @return a hash code value for this object
829      */
830     @Override
831     public int hashCode() {
832         return 743 + 809 * MathUtils.hash(value) + 167 * derivatives.hashCode();
833     }
834 
835 }