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.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  import java.util.concurrent.atomic.AtomicReference;
23  
24  import org.apache.commons.numbers.core.Sum;
25  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
26  import org.apache.commons.math4.legacy.exception.MathArithmeticException;
27  import org.apache.commons.math4.legacy.exception.MathInternalError;
28  import org.apache.commons.math4.legacy.exception.NotPositiveException;
29  import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
30  import org.apache.commons.numbers.combinatorics.Factorial;
31  import org.apache.commons.math4.core.jdkmath.JdkMath;
32  
33  /** Class holding "compiled" computation rules for derivative structures.
34   * <p>This class implements the computation rules described in Dan Kalman's paper <a
35   * href="http://www1.american.edu/cas/mathstat/People/kalman/pdffiles/mmgautodiff.pdf">Doubly
36   * Recursive Multivariate Automatic Differentiation</a>, Mathematics Magazine, vol. 75,
37   * no. 3, June 2002. However, in order to avoid performances bottlenecks, the recursive
38   * rules are "compiled" once in an unfold form. This class does this recursion unrolling
39   * and stores the computation rules as simple loops with pre-computed indirection arrays.</p>
40   * <p>
41   * This class maps all derivative computation into single dimension arrays that hold the
42   * value and partial derivatives. The class does not hold these arrays, which remains under
43   * the responsibility of the caller. For each combination of number of free parameters and
44   * derivation order, only one compiler is necessary, and this compiler will be used to
45   * perform computations on all arrays provided to it, which can represent hundreds or
46   * thousands of different parameters kept together with all their partial derivatives.
47   * </p>
48   * <p>
49   * The arrays on which compilers operate contain only the partial derivatives together
50   * with the 0<sup>th</sup> derivative, i.e. the value. The partial derivatives are stored in
51   * a compiler-specific order, which can be retrieved using methods {@link
52   * #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} and {@link
53   * #getPartialDerivativeOrders(int)}. The value is guaranteed to be stored as the first element
54   * (i.e. the {@link #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} method returns
55   * 0 when called with 0 for all derivation orders and {@link #getPartialDerivativeOrders(int)
56   * getPartialDerivativeOrders} returns an array filled with 0 when called with 0 as the index).
57   * </p>
58   * <p>
59   * Note that the ordering changes with number of parameters and derivation order. For example
60   * given 2 parameters x and y, df/dy is stored at index 2 when derivation order is set to 1 (in
61   * this case the array has three elements: f, df/dx and df/dy). If derivation order is set to
62   * 2, then df/dy will be stored at index 3 (in this case the array has six elements: f, df/dx,
63   * df/dxdx, df/dy, df/dxdy and df/dydy).
64   * </p>
65   * <p>
66   * Given this structure, users can perform some simple operations like adding, subtracting
67   * or multiplying constants and negating the elements by themselves, knowing if they want to
68   * mutate their array or create a new array. These simple operations are not provided by
69   * the compiler. The compiler provides only the more complex operations between several arrays.
70   * </p>
71   * <p>This class is mainly used as the engine for scalar variable {@link DerivativeStructure}.
72   * It can also be used directly to hold several variables in arrays for more complex data
73   * structures. User can for example store a vector of n variables depending on three x, y
74   * and z free parameters in one array as follows:</p> <pre>
75   *   // parameter 0 is x, parameter 1 is y, parameter 2 is z
76   *   int parameters = 3;
77   *   DSCompiler compiler = DSCompiler.getCompiler(parameters, order);
78   *   int size = compiler.getSize();
79   *
80   *   // pack all elements in a single array
81   *   double[] array = new double[n * size];
82   *   for (int i = 0; i &lt; n; ++i) {
83   *
84   *     // we know value is guaranteed to be the first element
85   *     array[i * size] = v[i];
86   *
87   *     // we don't know where first derivatives are stored, so we ask the compiler
88   *     array[i * size + compiler.getPartialDerivativeIndex(1, 0, 0) = dvOnDx[i][0];
89   *     array[i * size + compiler.getPartialDerivativeIndex(0, 1, 0) = dvOnDy[i][0];
90   *     array[i * size + compiler.getPartialDerivativeIndex(0, 0, 1) = dvOnDz[i][0];
91   *
92   *     // we let all higher order derivatives set to 0
93   *
94   *   }
95   * </pre>
96   * <p>Then in another function, user can perform some operations on all elements stored
97   * in the single array, such as a simple product of all variables:</p> <pre>
98   *   // compute the product of all elements
99   *   double[] product = new double[size];
100  *   prod[0] = 1.0;
101  *   for (int i = 0; i &lt; n; ++i) {
102  *     double[] tmp = product.clone();
103  *     compiler.multiply(tmp, 0, array, i * size, product, 0);
104  *   }
105  *
106  *   // value
107  *   double p = product[0];
108  *
109  *   // first derivatives
110  *   double dPdX = product[compiler.getPartialDerivativeIndex(1, 0, 0)];
111  *   double dPdY = product[compiler.getPartialDerivativeIndex(0, 1, 0)];
112  *   double dPdZ = product[compiler.getPartialDerivativeIndex(0, 0, 1)];
113  *
114  *   // cross derivatives (assuming order was at least 2)
115  *   double dPdXdX = product[compiler.getPartialDerivativeIndex(2, 0, 0)];
116  *   double dPdXdY = product[compiler.getPartialDerivativeIndex(1, 1, 0)];
117  *   double dPdXdZ = product[compiler.getPartialDerivativeIndex(1, 0, 1)];
118  *   double dPdYdY = product[compiler.getPartialDerivativeIndex(0, 2, 0)];
119  *   double dPdYdZ = product[compiler.getPartialDerivativeIndex(0, 1, 1)];
120  *   double dPdZdZ = product[compiler.getPartialDerivativeIndex(0, 0, 2)];
121  * </pre>
122  * @see DerivativeStructure
123  * @since 3.1
124  */
125 public final class DSCompiler {
126     /** Array of all compilers created so far. */
127     private static AtomicReference<DSCompiler[][]> compilers =
128             new AtomicReference<>(null);
129 
130     /** Number of free parameters. */
131     private final int parameters;
132 
133     /** Derivation order. */
134     private final int order;
135 
136     /** Number of partial derivatives (including the single 0 order derivative element). */
137     private final int[][] sizes;
138 
139     /** Indirection array for partial derivatives. */
140     private final int[][] derivativesIndirection;
141 
142     /** Indirection array of the lower derivative elements. */
143     private final int[] lowerIndirection;
144 
145     /** Indirection arrays for multiplication. */
146     private final int[][][] multIndirection;
147 
148     /** Indirection arrays for function composition. */
149     private final int[][][] compIndirection;
150 
151     /** Private constructor, reserved for the factory method {@link #getCompiler(int, int)}.
152      * @param parameters number of free parameters
153      * @param order derivation order
154      * @param valueCompiler compiler for the value part
155      * @param derivativeCompiler compiler for the derivative part
156      * @throws NumberIsTooLargeException if order is too large
157      */
158     private DSCompiler(final int parameters, final int order,
159                        final DSCompiler valueCompiler, final DSCompiler derivativeCompiler)
160         throws NumberIsTooLargeException {
161 
162         this.parameters = parameters;
163         this.order      = order;
164         this.sizes      = compileSizes(parameters, order, valueCompiler);
165         this.derivativesIndirection =
166                 compileDerivativesIndirection(parameters, order,
167                                               valueCompiler, derivativeCompiler);
168         this.lowerIndirection =
169                 compileLowerIndirection(parameters, order,
170                                         valueCompiler, derivativeCompiler);
171         this.multIndirection =
172                 compileMultiplicationIndirection(parameters, order,
173                                                  valueCompiler, derivativeCompiler, lowerIndirection);
174         this.compIndirection =
175                 compileCompositionIndirection(parameters, order,
176                                               valueCompiler, derivativeCompiler,
177                                               sizes, derivativesIndirection);
178     }
179 
180     /** Get the compiler for number of free parameters and order.
181      * @param parameters number of free parameters
182      * @param order derivation order
183      * @return cached rules set
184      * @throws NumberIsTooLargeException if order is too large
185      */
186     public static DSCompiler getCompiler(int parameters, int order)
187         throws NumberIsTooLargeException {
188 
189         // get the cached compilers
190         final DSCompiler[][] cache = compilers.get();
191         if (cache != null && cache.length > parameters &&
192             cache[parameters].length > order && cache[parameters][order] != null) {
193             // the compiler has already been created
194             return cache[parameters][order];
195         }
196 
197         // we need to create more compilers
198         final int maxParameters = JdkMath.max(parameters, cache == null ? 0 : cache.length);
199         final int maxOrder      = JdkMath.max(order,     cache == null ? 0 : cache[0].length);
200         final DSCompiler[][] newCache = new DSCompiler[maxParameters + 1][maxOrder + 1];
201 
202         if (cache != null) {
203             // preserve the already created compilers
204             for (int i = 0; i < cache.length; ++i) {
205                 System.arraycopy(cache[i], 0, newCache[i], 0, cache[i].length);
206             }
207         }
208 
209         // create the array in increasing diagonal order
210         for (int diag = 0; diag <= parameters + order; ++diag) {
211             for (int o = JdkMath.max(0, diag - parameters); o <= JdkMath.min(order, diag); ++o) {
212                 final int p = diag - o;
213                 if (newCache[p][o] == null) {
214                     final DSCompiler valueCompiler      = (p == 0) ? null : newCache[p - 1][o];
215                     final DSCompiler derivativeCompiler = (o == 0) ? null : newCache[p][o - 1];
216                     newCache[p][o] = new DSCompiler(p, o, valueCompiler, derivativeCompiler);
217                 }
218             }
219         }
220 
221         // atomically reset the cached compilers array
222         compilers.compareAndSet(cache, newCache);
223 
224         return newCache[parameters][order];
225     }
226 
227     /** Compile the sizes array.
228      * @param parameters number of free parameters
229      * @param order derivation order
230      * @param valueCompiler compiler for the value part
231      * @return sizes array
232      */
233     private static int[][] compileSizes(final int parameters, final int order,
234                                         final DSCompiler valueCompiler) {
235 
236         final int[][] sizes = new int[parameters + 1][order + 1];
237         if (parameters == 0) {
238             Arrays.fill(sizes[0], 1);
239         } else {
240             System.arraycopy(valueCompiler.sizes, 0, sizes, 0, parameters);
241             sizes[parameters][0] = 1;
242             for (int i = 0; i < order; ++i) {
243                 sizes[parameters][i + 1] = sizes[parameters][i] + sizes[parameters - 1][i + 1];
244             }
245         }
246 
247         return sizes;
248     }
249 
250     /** Compile the derivatives indirection array.
251      * @param parameters number of free parameters
252      * @param order derivation order
253      * @param valueCompiler compiler for the value part
254      * @param derivativeCompiler compiler for the derivative part
255      * @return derivatives indirection array
256      */
257     private static int[][] compileDerivativesIndirection(final int parameters, final int order,
258                                                       final DSCompiler valueCompiler,
259                                                       final DSCompiler derivativeCompiler) {
260 
261         if (parameters == 0 || order == 0) {
262             return new int[1][parameters];
263         }
264 
265         final int vSize = valueCompiler.derivativesIndirection.length;
266         final int dSize = derivativeCompiler.derivativesIndirection.length;
267         final int[][] derivativesIndirection = new int[vSize + dSize][parameters];
268 
269         // set up the indices for the value part
270         for (int i = 0; i < vSize; ++i) {
271             // copy the first indices, the last one remaining set to 0
272             System.arraycopy(valueCompiler.derivativesIndirection[i], 0,
273                              derivativesIndirection[i], 0,
274                              parameters - 1);
275         }
276 
277         // set up the indices for the derivative part
278         for (int i = 0; i < dSize; ++i) {
279 
280             // copy the indices
281             System.arraycopy(derivativeCompiler.derivativesIndirection[i], 0,
282                              derivativesIndirection[vSize + i], 0,
283                              parameters);
284 
285             // increment the derivation order for the last parameter
286             derivativesIndirection[vSize + i][parameters - 1]++;
287         }
288 
289         return derivativesIndirection;
290     }
291 
292     /** Compile the lower derivatives indirection array.
293      * <p>
294      * This indirection array contains the indices of all elements
295      * except derivatives for last derivation order.
296      * </p>
297      * @param parameters number of free parameters
298      * @param order derivation order
299      * @param valueCompiler compiler for the value part
300      * @param derivativeCompiler compiler for the derivative part
301      * @return lower derivatives indirection array
302      */
303     private static int[] compileLowerIndirection(final int parameters, final int order,
304                                               final DSCompiler valueCompiler,
305                                               final DSCompiler derivativeCompiler) {
306 
307         if (parameters == 0 || order <= 1) {
308             return new int[] { 0 };
309         }
310 
311         // this is an implementation of definition 6 in Dan Kalman's paper.
312         final int vSize = valueCompiler.lowerIndirection.length;
313         final int dSize = derivativeCompiler.lowerIndirection.length;
314         final int[] lowerIndirection = new int[vSize + dSize];
315         System.arraycopy(valueCompiler.lowerIndirection, 0, lowerIndirection, 0, vSize);
316         for (int i = 0; i < dSize; ++i) {
317             lowerIndirection[vSize + i] = valueCompiler.getSize() + derivativeCompiler.lowerIndirection[i];
318         }
319 
320         return lowerIndirection;
321     }
322 
323     /** Compile the multiplication indirection array.
324      * <p>
325      * This indirection array contains the indices of all pairs of elements
326      * involved when computing a multiplication. This allows a straightforward
327      * loop-based multiplication (see {@link #multiply(double[], int, double[], int, double[], int)}).
328      * </p>
329      * @param parameters number of free parameters
330      * @param order derivation order
331      * @param valueCompiler compiler for the value part
332      * @param derivativeCompiler compiler for the derivative part
333      * @param lowerIndirection lower derivatives indirection array
334      * @return multiplication indirection array
335      */
336     private static int[][][] compileMultiplicationIndirection(final int parameters, final int order,
337                                                            final DSCompiler valueCompiler,
338                                                            final DSCompiler derivativeCompiler,
339                                                            final int[] lowerIndirection) {
340 
341         if (parameters == 0 || order == 0) {
342             return new int[][][] { { { 1, 0, 0 } } };
343         }
344 
345         // this is an implementation of definition 3 in Dan Kalman's paper.
346         final int vSize = valueCompiler.multIndirection.length;
347         final int dSize = derivativeCompiler.multIndirection.length;
348         final int[][][] multIndirection = new int[vSize + dSize][][];
349 
350         System.arraycopy(valueCompiler.multIndirection, 0, multIndirection, 0, vSize);
351 
352         for (int i = 0; i < dSize; ++i) {
353             final int[][] dRow = derivativeCompiler.multIndirection[i];
354             List<int[]> row = new ArrayList<>(dRow.length * 2);
355             for (int j = 0; j < dRow.length; ++j) {
356                 row.add(new int[] { dRow[j][0], lowerIndirection[dRow[j][1]], vSize + dRow[j][2] });
357                 row.add(new int[] { dRow[j][0], vSize + dRow[j][1], lowerIndirection[dRow[j][2]] });
358             }
359 
360             // combine terms with similar derivation orders
361             final List<int[]> combined = new ArrayList<>(row.size());
362             for (int j = 0; j < row.size(); ++j) {
363                 final int[] termJ = row.get(j);
364                 if (termJ[0] > 0) {
365                     for (int k = j + 1; k < row.size(); ++k) {
366                         final int[] termK = row.get(k);
367                         if (termJ[1] == termK[1] && termJ[2] == termK[2]) {
368                             // combine termJ and termK
369                             termJ[0] += termK[0];
370                             // make sure we will skip termK later on in the outer loop
371                             termK[0] = 0;
372                         }
373                     }
374                     combined.add(termJ);
375                 }
376             }
377 
378             multIndirection[vSize + i] = combined.toArray(new int[combined.size()][]);
379         }
380 
381         return multIndirection;
382     }
383 
384     /** Compile the function composition indirection array.
385      * <p>
386      * This indirection array contains the indices of all sets of elements
387      * involved when computing a composition. This allows a straightforward
388      * loop-based composition (see {@link #compose(double[], int, double[], double[], int)}).
389      * </p>
390      * @param parameters number of free parameters
391      * @param order derivation order
392      * @param valueCompiler compiler for the value part
393      * @param derivativeCompiler compiler for the derivative part
394      * @param sizes sizes array
395      * @param derivativesIndirection derivatives indirection array
396      * @return multiplication indirection array
397      * @throws NumberIsTooLargeException if order is too large
398      */
399     private static int[][][] compileCompositionIndirection(final int parameters, final int order,
400                                                            final DSCompiler valueCompiler,
401                                                            final DSCompiler derivativeCompiler,
402                                                            final int[][] sizes,
403                                                            final int[][] derivativesIndirection)
404        throws NumberIsTooLargeException {
405 
406         if (parameters == 0 || order == 0) {
407             return new int[][][] { { { 1, 0 } } };
408         }
409 
410         final int vSize = valueCompiler.compIndirection.length;
411         final int dSize = derivativeCompiler.compIndirection.length;
412         final int[][][] compIndirection = new int[vSize + dSize][][];
413 
414         // the composition rules from the value part can be reused as is
415         System.arraycopy(valueCompiler.compIndirection, 0, compIndirection, 0, vSize);
416 
417         // the composition rules for the derivative part are deduced by
418         // differentiation the rules from the underlying compiler once
419         // with respect to the parameter this compiler handles and the
420         // underlying one did not handle
421         for (int i = 0; i < dSize; ++i) {
422             List<int[]> row = new ArrayList<>();
423             for (int[] term : derivativeCompiler.compIndirection[i]) {
424 
425                 // handle term p * f_k(g(x)) * g_l1(x) * g_l2(x) * ... * g_lp(x)
426 
427                 // derive the first factor in the term: f_k with respect to new parameter
428                 int[] derivedTermF = new int[term.length + 1];
429                 derivedTermF[0] = term[0];     // p
430                 derivedTermF[1] = term[1] + 1; // f_(k+1)
431                 int[] orders = new int[parameters];
432                 orders[parameters - 1] = 1;
433                 derivedTermF[term.length] = getPartialDerivativeIndex(parameters, order, sizes, orders);  // g_1
434                 for (int j = 2; j < term.length; ++j) {
435                     // convert the indices as the mapping for the current order
436                     // is different from the mapping with one less order
437                     derivedTermF[j] = convertIndex(term[j], parameters,
438                                                    derivativeCompiler.derivativesIndirection,
439                                                    parameters, order, sizes);
440                 }
441                 Arrays.sort(derivedTermF, 2, derivedTermF.length);
442                 row.add(derivedTermF);
443 
444                 // derive the various g_l
445                 for (int l = 2; l < term.length; ++l) {
446                     int[] derivedTermG = new int[term.length];
447                     derivedTermG[0] = term[0];
448                     derivedTermG[1] = term[1];
449                     for (int j = 2; j < term.length; ++j) {
450                         // convert the indices as the mapping for the current order
451                         // is different from the mapping with one less order
452                         derivedTermG[j] = convertIndex(term[j], parameters,
453                                                        derivativeCompiler.derivativesIndirection,
454                                                        parameters, order, sizes);
455                         if (j == l) {
456                             // derive this term
457                             System.arraycopy(derivativesIndirection[derivedTermG[j]], 0, orders, 0, parameters);
458                             orders[parameters - 1]++;
459                             derivedTermG[j] = getPartialDerivativeIndex(parameters, order, sizes, orders);
460                         }
461                     }
462                     Arrays.sort(derivedTermG, 2, derivedTermG.length);
463                     row.add(derivedTermG);
464                 }
465             }
466 
467             // combine terms with similar derivation orders
468             final List<int[]> combined = new ArrayList<>(row.size());
469             for (int j = 0; j < row.size(); ++j) {
470                 final int[] termJ = row.get(j);
471                 if (termJ[0] > 0) {
472                     for (int k = j + 1; k < row.size(); ++k) {
473                         final int[] termK = row.get(k);
474                         boolean equals = termJ.length == termK.length;
475                         for (int l = 1; equals && l < termJ.length; ++l) {
476                             equals &= termJ[l] == termK[l];
477                         }
478                         if (equals) {
479                             // combine termJ and termK
480                             termJ[0] += termK[0];
481                             // make sure we will skip termK later on in the outer loop
482                             termK[0] = 0;
483                         }
484                     }
485                     combined.add(termJ);
486                 }
487             }
488 
489             compIndirection[vSize + i] = combined.toArray(new int[combined.size()][]);
490         }
491 
492         return compIndirection;
493     }
494 
495     /** Get the index of a partial derivative in the array.
496      * <p>
497      * If all orders are set to 0, then the 0<sup>th</sup> order derivative
498      * is returned, which is the value of the function.
499      * </p>
500      * <p>The indices of derivatives are between 0 and {@link #getSize() getSize()} - 1.
501      * Their specific order is fixed for a given compiler, but otherwise not
502      * publicly specified. There are however some simple cases which have guaranteed
503      * indices:
504      * </p>
505      * <ul>
506      *   <li>the index of 0<sup>th</sup> order derivative is always 0</li>
507      *   <li>if there is only 1 {@link #getFreeParameters() free parameter}, then the
508      *   derivatives are sorted in increasing derivation order (i.e. f at index 0, df/dp
509      *   at index 1, d<sup>2</sup>f/dp<sup>2</sup> at index 2 ...
510      *   d<sup>k</sup>f/dp<sup>k</sup> at index k),</li>
511      *   <li>if the {@link #getOrder() derivation order} is 1, then the derivatives
512      *   are sorted in increasing free parameter order (i.e. f at index 0, df/dx<sub>1</sub>
513      *   at index 1, df/dx<sub>2</sub> at index 2 ... df/dx<sub>k</sub> at index k),</li>
514      *   <li>all other cases are not publicly specified</li>
515      * </ul>
516      * <p>
517      * This method is the inverse of method {@link #getPartialDerivativeOrders(int)}
518      * </p>
519      * @param orders derivation orders with respect to each parameter
520      * @return index of the partial derivative
521      * @exception DimensionMismatchException if the numbers of parameters does not
522      * match the instance
523      * @exception NumberIsTooLargeException if sum of derivation orders is larger
524      * than the instance limits
525      * @see #getPartialDerivativeOrders(int)
526      */
527     public int getPartialDerivativeIndex(final int ... orders)
528             throws DimensionMismatchException, NumberIsTooLargeException {
529 
530         // safety check
531         if (orders.length != getFreeParameters()) {
532             throw new DimensionMismatchException(orders.length, getFreeParameters());
533         }
534 
535         return getPartialDerivativeIndex(parameters, order, sizes, orders);
536     }
537 
538     /** Get the index of a partial derivative in an array.
539      * @param parameters number of free parameters
540      * @param order derivation order
541      * @param sizes sizes array
542      * @param orders derivation orders with respect to each parameter
543      * (the length of this array must match the number of parameters)
544      * @return index of the partial derivative
545      * @exception NumberIsTooLargeException if sum of derivation orders is larger
546      * than the instance limits
547      */
548     private static int getPartialDerivativeIndex(final int parameters, final int order,
549                                                  final int[][] sizes, final int ... orders)
550         throws NumberIsTooLargeException {
551 
552         // the value is obtained by diving into the recursive Dan Kalman's structure
553         // this is theorem 2 of his paper, with recursion replaced by iteration
554         int index     = 0;
555         int m         = order;
556         int ordersSum = 0;
557         for (int i = parameters - 1; i >= 0; --i) {
558 
559             // derivative order for current free parameter
560             int derivativeOrder = orders[i];
561 
562             // safety check
563             ordersSum += derivativeOrder;
564             if (ordersSum > order) {
565                 throw new NumberIsTooLargeException(ordersSum, order, true);
566             }
567 
568             while (derivativeOrder-- > 0) {
569                 // as long as we differentiate according to current free parameter,
570                 // we have to skip the value part and dive into the derivative part
571                 // so we add the size of the value part to the base index
572                 index += sizes[i][m--];
573             }
574         }
575 
576         return index;
577     }
578 
579     /** Convert an index from one (parameters, order) structure to another.
580      * @param index index of a partial derivative in source derivative structure
581      * @param srcP number of free parameters in source derivative structure
582      * @param srcDerivativesIndirection derivatives indirection array for the source
583      * derivative structure
584      * @param destP number of free parameters in destination derivative structure
585      * @param destO derivation order in destination derivative structure
586      * @param destSizes sizes array for the destination derivative structure
587      * @return index of the partial derivative with the <em>same</em> characteristics
588      * in destination derivative structure
589      * @throws NumberIsTooLargeException if order is too large
590      */
591     private static int convertIndex(final int index,
592                                     final int srcP, final int[][] srcDerivativesIndirection,
593                                     final int destP, final int destO, final int[][] destSizes)
594         throws NumberIsTooLargeException {
595         int[] orders = new int[destP];
596         System.arraycopy(srcDerivativesIndirection[index], 0, orders, 0, JdkMath.min(srcP, destP));
597         return getPartialDerivativeIndex(destP, destO, destSizes, orders);
598     }
599 
600     /** Get the derivation orders for a specific index in the array.
601      * <p>
602      * This method is the inverse of {@link #getPartialDerivativeIndex(int...)}.
603      * </p>
604      * @param index of the partial derivative
605      * @return orders derivation orders with respect to each parameter
606      * @see #getPartialDerivativeIndex(int...)
607      */
608     public int[] getPartialDerivativeOrders(final int index) {
609         return derivativesIndirection[index];
610     }
611 
612     /** Get the number of free parameters.
613      * @return number of free parameters
614      */
615     public int getFreeParameters() {
616         return parameters;
617     }
618 
619     /** Get the derivation order.
620      * @return derivation order
621      */
622     public int getOrder() {
623         return order;
624     }
625 
626     /** Get the array size required for holding partial derivatives data.
627      * <p>
628      * This number includes the single 0 order derivative element, which is
629      * guaranteed to be stored in the first element of the array.
630      * </p>
631      * @return array size required for holding partial derivatives data
632      */
633     public int getSize() {
634         return sizes[parameters][order];
635     }
636 
637     /** Compute linear combination.
638      * The derivative structure built will be a1 * ds1 + a2 * ds2
639      * @param a1 first scale factor
640      * @param c1 first base (unscaled) component
641      * @param offset1 offset of first operand in its array
642      * @param a2 second scale factor
643      * @param c2 second base (unscaled) component
644      * @param offset2 offset of second operand in its array
645      * @param result array where result must be stored (it may be
646      * one of the input arrays)
647      * @param resultOffset offset of the result in its array
648      */
649     public void linearCombination(final double a1, final double[] c1, final int offset1,
650                                   final double a2, final double[] c2, final int offset2,
651                                   final double[] result, final int resultOffset) {
652         for (int i = 0; i < getSize(); ++i) {
653             result[resultOffset + i] = Sum.create()
654                 .addProduct(a1, c1[offset1 + i])
655                 .addProduct(a2, c2[offset2 + i]).getAsDouble();
656         }
657     }
658 
659     /** Compute linear combination.
660      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
661      * @param a1 first scale factor
662      * @param c1 first base (unscaled) component
663      * @param offset1 offset of first operand in its array
664      * @param a2 second scale factor
665      * @param c2 second base (unscaled) component
666      * @param offset2 offset of second operand in its array
667      * @param a3 third scale factor
668      * @param c3 third base (unscaled) component
669      * @param offset3 offset of third operand in its array
670      * @param result array where result must be stored (it may be
671      * one of the input arrays)
672      * @param resultOffset offset of the result in its array
673      */
674     public void linearCombination(final double a1, final double[] c1, final int offset1,
675                                   final double a2, final double[] c2, final int offset2,
676                                   final double a3, final double[] c3, final int offset3,
677                                   final double[] result, final int resultOffset) {
678         for (int i = 0; i < getSize(); ++i) {
679             result[resultOffset + i] = Sum.create()
680                 .addProduct(a1, c1[offset1 + i])
681                 .addProduct(a2, c2[offset2 + i])
682                 .addProduct(a3, c3[offset3 + i]).getAsDouble();
683         }
684     }
685 
686     /** Compute linear combination.
687      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
688      * @param a1 first scale factor
689      * @param c1 first base (unscaled) component
690      * @param offset1 offset of first operand in its array
691      * @param a2 second scale factor
692      * @param c2 second base (unscaled) component
693      * @param offset2 offset of second operand in its array
694      * @param a3 third scale factor
695      * @param c3 third base (unscaled) component
696      * @param offset3 offset of third operand in its array
697      * @param a4 fourth scale factor
698      * @param c4 fourth base (unscaled) component
699      * @param offset4 offset of fourth operand in its array
700      * @param result array where result must be stored (it may be
701      * one of the input arrays)
702      * @param resultOffset offset of the result in its array
703      */
704     public void linearCombination(final double a1, final double[] c1, final int offset1,
705                                   final double a2, final double[] c2, final int offset2,
706                                   final double a3, final double[] c3, final int offset3,
707                                   final double a4, final double[] c4, final int offset4,
708                                   final double[] result, final int resultOffset) {
709         for (int i = 0; i < getSize(); ++i) {
710             result[resultOffset + i] = Sum.create()
711                 .addProduct(a1, c1[offset1 + i])
712                 .addProduct(a2, c2[offset2 + i])
713                 .addProduct(a3, c3[offset3 + i])
714                 .addProduct(a4, c4[offset4 + i]).getAsDouble();
715         }
716     }
717 
718     /** Perform addition of two derivative structures.
719      * @param lhs array holding left hand side of addition
720      * @param lhsOffset offset of the left hand side in its array
721      * @param rhs array right hand side of addition
722      * @param rhsOffset offset of the right hand side in its array
723      * @param result array where result must be stored (it may be
724      * one of the input arrays)
725      * @param resultOffset offset of the result in its array
726      */
727     public void add(final double[] lhs, final int lhsOffset,
728                     final double[] rhs, final int rhsOffset,
729                     final double[] result, final int resultOffset) {
730         for (int i = 0; i < getSize(); ++i) {
731             result[resultOffset + i] = lhs[lhsOffset + i] + rhs[rhsOffset + i];
732         }
733     }
734     /** Perform subtraction of two derivative structures.
735      * @param lhs array holding left hand side of subtraction
736      * @param lhsOffset offset of the left hand side in its array
737      * @param rhs array right hand side of subtraction
738      * @param rhsOffset offset of the right hand side in its array
739      * @param result array where result must be stored (it may be
740      * one of the input arrays)
741      * @param resultOffset offset of the result in its array
742      */
743     public void subtract(final double[] lhs, final int lhsOffset,
744                          final double[] rhs, final int rhsOffset,
745                          final double[] result, final int resultOffset) {
746         for (int i = 0; i < getSize(); ++i) {
747             result[resultOffset + i] = lhs[lhsOffset + i] - rhs[rhsOffset + i];
748         }
749     }
750 
751     /** Perform multiplication of two derivative structures.
752      * @param lhs array holding left hand side of multiplication
753      * @param lhsOffset offset of the left hand side in its array
754      * @param rhs array right hand side of multiplication
755      * @param rhsOffset offset of the right hand side in its array
756      * @param result array where result must be stored (for
757      * multiplication the result array <em>cannot</em> be one of
758      * the input arrays)
759      * @param resultOffset offset of the result in its array
760      */
761     public void multiply(final double[] lhs, final int lhsOffset,
762                          final double[] rhs, final int rhsOffset,
763                          final double[] result, final int resultOffset) {
764         for (int i = 0; i < multIndirection.length; ++i) {
765             final int[][] mappingI = multIndirection[i];
766             double r = 0;
767             for (int j = 0; j < mappingI.length; ++j) {
768                 r += mappingI[j][0] *
769                      lhs[lhsOffset + mappingI[j][1]] *
770                      rhs[rhsOffset + mappingI[j][2]];
771             }
772             result[resultOffset + i] = r;
773         }
774     }
775 
776     /** Perform division of two derivative structures.
777      * @param lhs array holding left hand side of division
778      * @param lhsOffset offset of the left hand side in its array
779      * @param rhs array right hand side of division
780      * @param rhsOffset offset of the right hand side in its array
781      * @param result array where result must be stored (for
782      * division the result array <em>cannot</em> be one of
783      * the input arrays)
784      * @param resultOffset offset of the result in its array
785      */
786     public void divide(final double[] lhs, final int lhsOffset,
787                        final double[] rhs, final int rhsOffset,
788                        final double[] result, final int resultOffset) {
789         final double[] reciprocal = new double[getSize()];
790         pow(rhs, lhsOffset, -1, reciprocal, 0);
791         multiply(lhs, lhsOffset, reciprocal, 0, result, resultOffset);
792     }
793 
794     /** Perform remainder of two derivative structures.
795      * @param lhs array holding left hand side of remainder
796      * @param lhsOffset offset of the left hand side in its array
797      * @param rhs array right hand side of remainder
798      * @param rhsOffset offset of the right hand side in its array
799      * @param result array where result must be stored (it may be
800      * one of the input arrays)
801      * @param resultOffset offset of the result in its array
802      */
803     public void remainder(final double[] lhs, final int lhsOffset,
804                           final double[] rhs, final int rhsOffset,
805                           final double[] result, final int resultOffset) {
806 
807         // compute k such that lhs % rhs = lhs - k rhs
808         final double rem = JdkMath.IEEEremainder(lhs[lhsOffset], rhs[rhsOffset]);
809         final double k   = JdkMath.rint((lhs[lhsOffset] - rem) / rhs[rhsOffset]);
810 
811         // set up value
812         result[resultOffset] = rem;
813 
814         // set up partial derivatives
815         for (int i = 1; i < getSize(); ++i) {
816             result[resultOffset + i] = lhs[lhsOffset + i] - k * rhs[rhsOffset + i];
817         }
818     }
819 
820     /** Compute power of a double to a derivative structure.
821      * @param a number to exponentiate
822      * @param operand array holding the power
823      * @param operandOffset offset of the power in its array
824      * @param result array where result must be stored (for
825      * power the result array <em>cannot</em> be the input
826      * array)
827      * @param resultOffset offset of the result in its array
828      * @since 3.3
829      */
830     public void pow(final double a,
831                     final double[] operand, final int operandOffset,
832                     final double[] result, final int resultOffset) {
833 
834         // create the function value and derivatives
835         // [a^x, ln(a) a^x, ln(a)^2 a^x,, ln(a)^3 a^x, ... ]
836         final double[] function = new double[1 + order];
837         if (a == 0) {
838             if (operand[operandOffset] == 0) {
839                 function[0] = 1;
840                 double infinity = Double.POSITIVE_INFINITY;
841                 for (int i = 1; i < function.length; ++i) {
842                     infinity = -infinity;
843                     function[i] = infinity;
844                 }
845             } else if (operand[operandOffset] < 0) {
846                 Arrays.fill(function, Double.NaN);
847             }
848         } else {
849             function[0] = JdkMath.pow(a, operand[operandOffset]);
850             final double lnA = JdkMath.log(a);
851             for (int i = 1; i < function.length; ++i) {
852                 function[i] = lnA * function[i - 1];
853             }
854         }
855 
856 
857         // apply function composition
858         compose(operand, operandOffset, function, result, resultOffset);
859     }
860 
861     /** Compute power of a derivative structure.
862      * @param operand array holding the operand
863      * @param operandOffset offset of the operand in its array
864      * @param p power to apply
865      * @param result array where result must be stored (for
866      * power the result array <em>cannot</em> be the input
867      * array)
868      * @param resultOffset offset of the result in its array
869      */
870     public void pow(final double[] operand, final int operandOffset, final double p,
871                     final double[] result, final int resultOffset) {
872 
873         if (p == 0) {
874             // special case, x^0 = 1 for all x
875             result[resultOffset] = 1.0;
876             Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), 0);
877             return;
878         }
879 
880         if (operand[operandOffset] == 0) {
881             // special case, 0^p = 0 for all p
882             Arrays.fill(result, resultOffset, resultOffset + getSize(), 0);
883             return;
884         }
885 
886         // create the function value and derivatives
887         // [x^p, px^(p-1), p(p-1)x^(p-2), ... ]
888         double[] function = new double[1 + order];
889         double xk = JdkMath.pow(operand[operandOffset], p - order);
890         for (int i = order; i > 0; --i) {
891             function[i] = xk;
892             xk *= operand[operandOffset];
893         }
894         function[0] = xk;
895         double coefficient = p;
896         for (int i = 1; i <= order; ++i) {
897             function[i] *= coefficient;
898             coefficient *= p - i;
899         }
900 
901         // apply function composition
902         compose(operand, operandOffset, function, result, resultOffset);
903     }
904 
905     /** Compute integer power of a derivative structure.
906      * @param operand array holding the operand
907      * @param operandOffset offset of the operand in its array
908      * @param n power to apply
909      * @param result array where result must be stored (for
910      * power the result array <em>cannot</em> be the input
911      * array)
912      * @param resultOffset offset of the result in its array
913      */
914     public void pow(final double[] operand, final int operandOffset, final int n,
915                     final double[] result, final int resultOffset) {
916 
917         if (n == 0) {
918             // special case, x^0 = 1 for all x
919             result[resultOffset] = 1.0;
920             Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), 0);
921             return;
922         }
923 
924         // create the power function value and derivatives
925         // [x^n, nx^(n-1), n(n-1)x^(n-2), ... ]
926         double[] function = new double[1 + order];
927 
928         if (n > 0) {
929             // strictly positive power
930             final int maxOrder = JdkMath.min(order, n);
931             double xk = JdkMath.pow(operand[operandOffset], n - maxOrder);
932             for (int i = maxOrder; i > 0; --i) {
933                 function[i] = xk;
934                 xk *= operand[operandOffset];
935             }
936             function[0] = xk;
937         } else {
938             // strictly negative power
939             final double inv = 1.0 / operand[operandOffset];
940             double xk = JdkMath.pow(inv, -n);
941             for (int i = 0; i <= order; ++i) {
942                 function[i] = xk;
943                 xk *= inv;
944             }
945         }
946 
947         double coefficient = n;
948         for (int i = 1; i <= order; ++i) {
949             function[i] *= coefficient;
950             coefficient *= n - i;
951         }
952 
953         // apply function composition
954         compose(operand, operandOffset, function, result, resultOffset);
955     }
956 
957     /** Compute power of a derivative structure.
958      * @param x array holding the base
959      * @param xOffset offset of the base in its array
960      * @param y array holding the exponent
961      * @param yOffset offset of the exponent in its array
962      * @param result array where result must be stored (for
963      * power the result array <em>cannot</em> be the input
964      * array)
965      * @param resultOffset offset of the result in its array
966      */
967     public void pow(final double[] x, final int xOffset,
968                     final double[] y, final int yOffset,
969                     final double[] result, final int resultOffset) {
970         final double[] logX = new double[getSize()];
971         log(x, xOffset, logX, 0);
972         final double[] yLogX = new double[getSize()];
973         multiply(logX, 0, y, yOffset, yLogX, 0);
974         exp(yLogX, 0, result, resultOffset);
975     }
976 
977     /** Compute n<sup>th</sup> root of a derivative structure.
978      * @param operand array holding the operand
979      * @param operandOffset offset of the operand in its array
980      * @param n order of the root
981      * @param result array where result must be stored (for
982      * n<sup>th</sup> root the result array <em>cannot</em> be the input
983      * array)
984      * @param resultOffset offset of the result in its array
985      */
986     public void rootN(final double[] operand, final int operandOffset, final int n,
987                       final double[] result, final int resultOffset) {
988 
989         // create the function value and derivatives
990         // [x^(1/n), (1/n)x^((1/n)-1), (1-n)/n^2x^((1/n)-2), ... ]
991         double[] function = new double[1 + order];
992         double xk;
993         if (n == 2) {
994             function[0] = JdkMath.sqrt(operand[operandOffset]);
995             xk          = 0.5 / function[0];
996         } else if (n == 3) {
997             function[0] = JdkMath.cbrt(operand[operandOffset]);
998             xk          = 1.0 / (3.0 * function[0] * function[0]);
999         } else {
1000             function[0] = JdkMath.pow(operand[operandOffset], 1.0 / n);
1001             xk          = 1.0 / (n * JdkMath.pow(function[0], n - 1));
1002         }
1003         final double nReciprocal = 1.0 / n;
1004         final double xReciprocal = 1.0 / operand[operandOffset];
1005         for (int i = 1; i <= order; ++i) {
1006             function[i] = xk;
1007             xk *= xReciprocal * (nReciprocal - i);
1008         }
1009 
1010         // apply function composition
1011         compose(operand, operandOffset, function, result, resultOffset);
1012     }
1013 
1014     /** Compute exponential of a derivative structure.
1015      * @param operand array holding the operand
1016      * @param operandOffset offset of the operand in its array
1017      * @param result array where result must be stored (for
1018      * exponential the result array <em>cannot</em> be the input
1019      * array)
1020      * @param resultOffset offset of the result in its array
1021      */
1022     public void exp(final double[] operand, final int operandOffset,
1023                     final double[] result, final int resultOffset) {
1024 
1025         // create the function value and derivatives
1026         double[] function = new double[1 + order];
1027         Arrays.fill(function, JdkMath.exp(operand[operandOffset]));
1028 
1029         // apply function composition
1030         compose(operand, operandOffset, function, result, resultOffset);
1031     }
1032 
1033     /** Compute exp(x) - 1 of a derivative structure.
1034      * @param operand array holding the operand
1035      * @param operandOffset offset of the operand in its array
1036      * @param result array where result must be stored (for
1037      * exponential the result array <em>cannot</em> be the input
1038      * array)
1039      * @param resultOffset offset of the result in its array
1040      */
1041     public void expm1(final double[] operand, final int operandOffset,
1042                       final double[] result, final int resultOffset) {
1043 
1044         // create the function value and derivatives
1045         double[] function = new double[1 + order];
1046         function[0] = JdkMath.expm1(operand[operandOffset]);
1047         Arrays.fill(function, 1, 1 + order, JdkMath.exp(operand[operandOffset]));
1048 
1049         // apply function composition
1050         compose(operand, operandOffset, function, result, resultOffset);
1051     }
1052 
1053     /** Compute natural logarithm of a derivative structure.
1054      * @param operand array holding the operand
1055      * @param operandOffset offset of the operand in its array
1056      * @param result array where result must be stored (for
1057      * logarithm the result array <em>cannot</em> be the input
1058      * array)
1059      * @param resultOffset offset of the result in its array
1060      */
1061     public void log(final double[] operand, final int operandOffset,
1062                     final double[] result, final int resultOffset) {
1063 
1064         // create the function value and derivatives
1065         double[] function = new double[1 + order];
1066         function[0] = JdkMath.log(operand[operandOffset]);
1067         if (order > 0) {
1068             double inv = 1.0 / operand[operandOffset];
1069             double xk  = inv;
1070             for (int i = 1; i <= order; ++i) {
1071                 function[i] = xk;
1072                 xk *= -i * inv;
1073             }
1074         }
1075 
1076         // apply function composition
1077         compose(operand, operandOffset, function, result, resultOffset);
1078     }
1079 
1080     /** Computes shifted logarithm of a derivative structure.
1081      * @param operand array holding the operand
1082      * @param operandOffset offset of the operand in its array
1083      * @param result array where result must be stored (for
1084      * shifted logarithm the result array <em>cannot</em> be the input array)
1085      * @param resultOffset offset of the result in its array
1086      */
1087     public void log1p(final double[] operand, final int operandOffset,
1088                       final double[] result, final int resultOffset) {
1089 
1090         // create the function value and derivatives
1091         double[] function = new double[1 + order];
1092         function[0] = JdkMath.log1p(operand[operandOffset]);
1093         if (order > 0) {
1094             double inv = 1.0 / (1.0 + operand[operandOffset]);
1095             double xk  = inv;
1096             for (int i = 1; i <= order; ++i) {
1097                 function[i] = xk;
1098                 xk *= -i * inv;
1099             }
1100         }
1101 
1102         // apply function composition
1103         compose(operand, operandOffset, function, result, resultOffset);
1104     }
1105 
1106     /** Computes base 10 logarithm of a derivative structure.
1107      * @param operand array holding the operand
1108      * @param operandOffset offset of the operand in its array
1109      * @param result array where result must be stored (for
1110      * base 10 logarithm the result array <em>cannot</em> be the input array)
1111      * @param resultOffset offset of the result in its array
1112      */
1113     public void log10(final double[] operand, final int operandOffset,
1114                       final double[] result, final int resultOffset) {
1115 
1116         // create the function value and derivatives
1117         double[] function = new double[1 + order];
1118         function[0] = JdkMath.log10(operand[operandOffset]);
1119         if (order > 0) {
1120             double inv = 1.0 / operand[operandOffset];
1121             double xk  = inv / JdkMath.log(10.0);
1122             for (int i = 1; i <= order; ++i) {
1123                 function[i] = xk;
1124                 xk *= -i * inv;
1125             }
1126         }
1127 
1128         // apply function composition
1129         compose(operand, operandOffset, function, result, resultOffset);
1130     }
1131 
1132     /** Compute cosine of a derivative structure.
1133      * @param operand array holding the operand
1134      * @param operandOffset offset of the operand in its array
1135      * @param result array where result must be stored (for
1136      * cosine the result array <em>cannot</em> be the input
1137      * array)
1138      * @param resultOffset offset of the result in its array
1139      */
1140     public void cos(final double[] operand, final int operandOffset,
1141                     final double[] result, final int resultOffset) {
1142 
1143         // create the function value and derivatives
1144         double[] function = new double[1 + order];
1145         function[0] = JdkMath.cos(operand[operandOffset]);
1146         if (order > 0) {
1147             function[1] = -JdkMath.sin(operand[operandOffset]);
1148             for (int i = 2; i <= order; ++i) {
1149                 function[i] = -function[i - 2];
1150             }
1151         }
1152 
1153         // apply function composition
1154         compose(operand, operandOffset, function, result, resultOffset);
1155     }
1156 
1157     /** Compute sine of a derivative structure.
1158      * @param operand array holding the operand
1159      * @param operandOffset offset of the operand in its array
1160      * @param result array where result must be stored (for
1161      * sine the result array <em>cannot</em> be the input
1162      * array)
1163      * @param resultOffset offset of the result in its array
1164      */
1165     public void sin(final double[] operand, final int operandOffset,
1166                     final double[] result, final int resultOffset) {
1167 
1168         // create the function value and derivatives
1169         double[] function = new double[1 + order];
1170         function[0] = JdkMath.sin(operand[operandOffset]);
1171         if (order > 0) {
1172             function[1] = JdkMath.cos(operand[operandOffset]);
1173             for (int i = 2; i <= order; ++i) {
1174                 function[i] = -function[i - 2];
1175             }
1176         }
1177 
1178         // apply function composition
1179         compose(operand, operandOffset, function, result, resultOffset);
1180     }
1181 
1182     /** Compute tangent of a derivative structure.
1183      * @param operand array holding the operand
1184      * @param operandOffset offset of the operand in its array
1185      * @param result array where result must be stored (for
1186      * tangent the result array <em>cannot</em> be the input
1187      * array)
1188      * @param resultOffset offset of the result in its array
1189      */
1190     public void tan(final double[] operand, final int operandOffset,
1191                     final double[] result, final int resultOffset) {
1192 
1193         // create the function value and derivatives
1194         final double[] function = new double[1 + order];
1195         final double t = JdkMath.tan(operand[operandOffset]);
1196         function[0] = t;
1197 
1198         if (order > 0) {
1199 
1200             // the nth order derivative of tan has the form:
1201             // dn(tan(x)/dxn = P_n(tan(x))
1202             // where P_n(t) is a degree n+1 polynomial with same parity as n+1
1203             // P_0(t) = t, P_1(t) = 1 + t^2, P_2(t) = 2 t (1 + t^2) ...
1204             // the general recurrence relation for P_n is:
1205             // P_n(x) = (1+t^2) P_(n-1)'(t)
1206             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
1207             final double[] p = new double[order + 2];
1208             p[1] = 1;
1209             final double t2 = t * t;
1210             for (int n = 1; n <= order; ++n) {
1211 
1212                 // update and evaluate polynomial P_n(t)
1213                 double v = 0;
1214                 p[n + 1] = n * p[n];
1215                 for (int k = n + 1; k >= 0; k -= 2) {
1216                     v = v * t2 + p[k];
1217                     if (k > 2) {
1218                         p[k - 2] = (k - 1) * p[k - 1] + (k - 3) * p[k - 3];
1219                     } else if (k == 2) {
1220                         p[0] = p[1];
1221                     }
1222                 }
1223                 if ((n & 0x1) == 0) {
1224                     v *= t;
1225                 }
1226 
1227                 function[n] = v;
1228             }
1229         }
1230 
1231         // apply function composition
1232         compose(operand, operandOffset, function, result, resultOffset);
1233     }
1234 
1235     /** Compute arc cosine of a derivative structure.
1236      * @param operand array holding the operand
1237      * @param operandOffset offset of the operand in its array
1238      * @param result array where result must be stored (for
1239      * arc cosine the result array <em>cannot</em> be the input
1240      * array)
1241      * @param resultOffset offset of the result in its array
1242      */
1243     public void acos(final double[] operand, final int operandOffset,
1244                     final double[] result, final int resultOffset) {
1245 
1246         // create the function value and derivatives
1247         double[] function = new double[1 + order];
1248         final double x = operand[operandOffset];
1249         function[0] = JdkMath.acos(x);
1250         if (order > 0) {
1251             // the nth order derivative of acos has the form:
1252             // dn(acos(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
1253             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
1254             // P_1(x) = -1, P_2(x) = -x, P_3(x) = -2x^2 - 1 ...
1255             // the general recurrence relation for P_n is:
1256             // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
1257             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
1258             final double[] p = new double[order];
1259             p[0] = -1;
1260             final double x2    = x * x;
1261             final double f     = 1.0 / (1 - x2);
1262             double coeff = JdkMath.sqrt(f);
1263             function[1] = coeff * p[0];
1264             for (int n = 2; n <= order; ++n) {
1265 
1266                 // update and evaluate polynomial P_n(x)
1267                 double v = 0;
1268                 p[n - 1] = (n - 1) * p[n - 2];
1269                 for (int k = n - 1; k >= 0; k -= 2) {
1270                     v = v * x2 + p[k];
1271                     if (k > 2) {
1272                         p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3];
1273                     } else if (k == 2) {
1274                         p[0] = p[1];
1275                     }
1276                 }
1277                 if ((n & 0x1) == 0) {
1278                     v *= x;
1279                 }
1280 
1281                 coeff *= f;
1282                 function[n] = coeff * v;
1283             }
1284         }
1285 
1286         // apply function composition
1287         compose(operand, operandOffset, function, result, resultOffset);
1288     }
1289 
1290     /** Compute arc sine of a derivative structure.
1291      * @param operand array holding the operand
1292      * @param operandOffset offset of the operand in its array
1293      * @param result array where result must be stored (for
1294      * arc sine the result array <em>cannot</em> be the input
1295      * array)
1296      * @param resultOffset offset of the result in its array
1297      */
1298     public void asin(final double[] operand, final int operandOffset,
1299                     final double[] result, final int resultOffset) {
1300 
1301         // create the function value and derivatives
1302         double[] function = new double[1 + order];
1303         final double x = operand[operandOffset];
1304         function[0] = JdkMath.asin(x);
1305         if (order > 0) {
1306             // the nth order derivative of asin has the form:
1307             // dn(asin(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
1308             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
1309             // P_1(x) = 1, P_2(x) = x, P_3(x) = 2x^2 + 1 ...
1310             // the general recurrence relation for P_n is:
1311             // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
1312             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
1313             final double[] p = new double[order];
1314             p[0] = 1;
1315             final double x2    = x * x;
1316             final double f     = 1.0 / (1 - x2);
1317             double coeff = JdkMath.sqrt(f);
1318             function[1] = coeff * p[0];
1319             for (int n = 2; n <= order; ++n) {
1320 
1321                 // update and evaluate polynomial P_n(x)
1322                 double v = 0;
1323                 p[n - 1] = (n - 1) * p[n - 2];
1324                 for (int k = n - 1; k >= 0; k -= 2) {
1325                     v = v * x2 + p[k];
1326                     if (k > 2) {
1327                         p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3];
1328                     } else if (k == 2) {
1329                         p[0] = p[1];
1330                     }
1331                 }
1332                 if ((n & 0x1) == 0) {
1333                     v *= x;
1334                 }
1335 
1336                 coeff *= f;
1337                 function[n] = coeff * v;
1338             }
1339         }
1340 
1341         // apply function composition
1342         compose(operand, operandOffset, function, result, resultOffset);
1343     }
1344 
1345     /** Compute arc tangent of a derivative structure.
1346      * @param operand array holding the operand
1347      * @param operandOffset offset of the operand in its array
1348      * @param result array where result must be stored (for
1349      * arc tangent the result array <em>cannot</em> be the input
1350      * array)
1351      * @param resultOffset offset of the result in its array
1352      */
1353     public void atan(final double[] operand, final int operandOffset,
1354                      final double[] result, final int resultOffset) {
1355 
1356         // create the function value and derivatives
1357         double[] function = new double[1 + order];
1358         final double x = operand[operandOffset];
1359         function[0] = JdkMath.atan(x);
1360         if (order > 0) {
1361             // the nth order derivative of atan has the form:
1362             // dn(atan(x)/dxn = Q_n(x) / (1 + x^2)^n
1363             // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
1364             // Q_1(x) = 1, Q_2(x) = -2x, Q_3(x) = 6x^2 - 2 ...
1365             // the general recurrence relation for Q_n is:
1366             // Q_n(x) = (1+x^2) Q_(n-1)'(x) - 2(n-1) x Q_(n-1)(x)
1367             // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
1368             final double[] q = new double[order];
1369             q[0] = 1;
1370             final double x2    = x * x;
1371             final double f     = 1.0 / (1 + x2);
1372             double coeff = f;
1373             function[1] = coeff * q[0];
1374             for (int n = 2; n <= order; ++n) {
1375 
1376                 // update and evaluate polynomial Q_n(x)
1377                 double v = 0;
1378                 q[n - 1] = -n * q[n - 2];
1379                 for (int k = n - 1; k >= 0; k -= 2) {
1380                     v = v * x2 + q[k];
1381                     if (k > 2) {
1382                         q[k - 2] = (k - 1) * q[k - 1] + (k - 1 - 2 * n) * q[k - 3];
1383                     } else if (k == 2) {
1384                         q[0] = q[1];
1385                     }
1386                 }
1387                 if ((n & 0x1) == 0) {
1388                     v *= x;
1389                 }
1390 
1391                 coeff *= f;
1392                 function[n] = coeff * v;
1393             }
1394         }
1395 
1396         // apply function composition
1397         compose(operand, operandOffset, function, result, resultOffset);
1398     }
1399 
1400     /** Compute two arguments arc tangent of a derivative structure.
1401      * @param y array holding the first operand
1402      * @param yOffset offset of the first operand in its array
1403      * @param x array holding the second operand
1404      * @param xOffset offset of the second operand in its array
1405      * @param result array where result must be stored (for
1406      * two arguments arc tangent the result array <em>cannot</em>
1407      * be the input array)
1408      * @param resultOffset offset of the result in its array
1409      */
1410     public void atan2(final double[] y, final int yOffset,
1411                       final double[] x, final int xOffset,
1412                       final double[] result, final int resultOffset) {
1413 
1414         // compute r = sqrt(x^2+y^2)
1415         double[] tmp1 = new double[getSize()];
1416         multiply(x, xOffset, x, xOffset, tmp1, 0);      // x^2
1417         double[] tmp2 = new double[getSize()];
1418         multiply(y, yOffset, y, yOffset, tmp2, 0);      // y^2
1419         add(tmp1, 0, tmp2, 0, tmp2, 0);                 // x^2 + y^2
1420         rootN(tmp2, 0, 2, tmp1, 0);                     // r = sqrt(x^2 + y^2)
1421 
1422         if (x[xOffset] >= 0) {
1423 
1424             // compute atan2(y, x) = 2 atan(y / (r + x))
1425             add(tmp1, 0, x, xOffset, tmp2, 0);          // r + x
1426             divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r + x)
1427             atan(tmp1, 0, tmp2, 0);                     // atan(y / (r + x))
1428             for (int i = 0; i < tmp2.length; ++i) {
1429                 result[resultOffset + i] = 2 * tmp2[i]; // 2 * atan(y / (r + x))
1430             }
1431         } else {
1432 
1433             // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x))
1434             subtract(tmp1, 0, x, xOffset, tmp2, 0);     // r - x
1435             divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r - x)
1436             atan(tmp1, 0, tmp2, 0);                     // atan(y / (r - x))
1437             result[resultOffset] =
1438                     ((tmp2[0] <= 0) ? -JdkMath.PI : JdkMath.PI) - 2 * tmp2[0]; // +/-pi - 2 * atan(y / (r - x))
1439             for (int i = 1; i < tmp2.length; ++i) {
1440                 result[resultOffset + i] = -2 * tmp2[i]; // +/-pi - 2 * atan(y / (r - x))
1441             }
1442         }
1443 
1444         // fix value to take special cases (+0/+0, +0/-0, -0/+0, -0/-0, +/-infinity) correctly
1445         result[resultOffset] = JdkMath.atan2(y[yOffset], x[xOffset]);
1446     }
1447 
1448     /** Compute hyperbolic cosine of a derivative structure.
1449      * @param operand array holding the operand
1450      * @param operandOffset offset of the operand in its array
1451      * @param result array where result must be stored (for
1452      * hyperbolic cosine the result array <em>cannot</em> be the input
1453      * array)
1454      * @param resultOffset offset of the result in its array
1455      */
1456     public void cosh(final double[] operand, final int operandOffset,
1457                      final double[] result, final int resultOffset) {
1458 
1459         // create the function value and derivatives
1460         double[] function = new double[1 + order];
1461         function[0] = JdkMath.cosh(operand[operandOffset]);
1462         if (order > 0) {
1463             function[1] = JdkMath.sinh(operand[operandOffset]);
1464             for (int i = 2; i <= order; ++i) {
1465                 function[i] = function[i - 2];
1466             }
1467         }
1468 
1469         // apply function composition
1470         compose(operand, operandOffset, function, result, resultOffset);
1471     }
1472 
1473     /** Compute hyperbolic sine of a derivative structure.
1474      * @param operand array holding the operand
1475      * @param operandOffset offset of the operand in its array
1476      * @param result array where result must be stored (for
1477      * hyperbolic sine the result array <em>cannot</em> be the input
1478      * array)
1479      * @param resultOffset offset of the result in its array
1480      */
1481     public void sinh(final double[] operand, final int operandOffset,
1482                      final double[] result, final int resultOffset) {
1483 
1484         // create the function value and derivatives
1485         double[] function = new double[1 + order];
1486         function[0] = JdkMath.sinh(operand[operandOffset]);
1487         if (order > 0) {
1488             function[1] = JdkMath.cosh(operand[operandOffset]);
1489             for (int i = 2; i <= order; ++i) {
1490                 function[i] = function[i - 2];
1491             }
1492         }
1493 
1494         // apply function composition
1495         compose(operand, operandOffset, function, result, resultOffset);
1496     }
1497 
1498     /** Compute hyperbolic tangent of a derivative structure.
1499      * @param operand array holding the operand
1500      * @param operandOffset offset of the operand in its array
1501      * @param result array where result must be stored (for
1502      * hyperbolic tangent the result array <em>cannot</em> be the input
1503      * array)
1504      * @param resultOffset offset of the result in its array
1505      */
1506     public void tanh(final double[] operand, final int operandOffset,
1507                      final double[] result, final int resultOffset) {
1508 
1509         // create the function value and derivatives
1510         final double[] function = new double[1 + order];
1511         final double t = JdkMath.tanh(operand[operandOffset]);
1512         function[0] = t;
1513 
1514         if (order > 0) {
1515 
1516             // the nth order derivative of tanh has the form:
1517             // dn(tanh(x)/dxn = P_n(tanh(x))
1518             // where P_n(t) is a degree n+1 polynomial with same parity as n+1
1519             // P_0(t) = t, P_1(t) = 1 - t^2, P_2(t) = -2 t (1 - t^2) ...
1520             // the general recurrence relation for P_n is:
1521             // P_n(x) = (1-t^2) P_(n-1)'(t)
1522             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
1523             final double[] p = new double[order + 2];
1524             p[1] = 1;
1525             final double t2 = t * t;
1526             for (int n = 1; n <= order; ++n) {
1527 
1528                 // update and evaluate polynomial P_n(t)
1529                 double v = 0;
1530                 p[n + 1] = -n * p[n];
1531                 for (int k = n + 1; k >= 0; k -= 2) {
1532                     v = v * t2 + p[k];
1533                     if (k > 2) {
1534                         p[k - 2] = (k - 1) * p[k - 1] - (k - 3) * p[k - 3];
1535                     } else if (k == 2) {
1536                         p[0] = p[1];
1537                     }
1538                 }
1539                 if ((n & 0x1) == 0) {
1540                     v *= t;
1541                 }
1542 
1543                 function[n] = v;
1544             }
1545         }
1546 
1547         // apply function composition
1548         compose(operand, operandOffset, function, result, resultOffset);
1549     }
1550 
1551     /** Compute inverse hyperbolic cosine of a derivative structure.
1552      * @param operand array holding the operand
1553      * @param operandOffset offset of the operand in its array
1554      * @param result array where result must be stored (for
1555      * inverse hyperbolic cosine the result array <em>cannot</em> be the input
1556      * array)
1557      * @param resultOffset offset of the result in its array
1558      */
1559     public void acosh(final double[] operand, final int operandOffset,
1560                      final double[] result, final int resultOffset) {
1561 
1562         // create the function value and derivatives
1563         double[] function = new double[1 + order];
1564         final double x = operand[operandOffset];
1565         function[0] = JdkMath.acosh(x);
1566         if (order > 0) {
1567             // the nth order derivative of acosh has the form:
1568             // dn(acosh(x)/dxn = P_n(x) / [x^2 - 1]^((2n-1)/2)
1569             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
1570             // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 + 1 ...
1571             // the general recurrence relation for P_n is:
1572             // P_n(x) = (x^2-1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
1573             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
1574             final double[] p = new double[order];
1575             p[0] = 1;
1576             final double x2  = x * x;
1577             final double f   = 1.0 / (x2 - 1);
1578             double coeff = JdkMath.sqrt(f);
1579             function[1] = coeff * p[0];
1580             for (int n = 2; n <= order; ++n) {
1581 
1582                 // update and evaluate polynomial P_n(x)
1583                 double v = 0;
1584                 p[n - 1] = (1 - n) * p[n - 2];
1585                 for (int k = n - 1; k >= 0; k -= 2) {
1586                     v = v * x2 + p[k];
1587                     if (k > 2) {
1588                         p[k - 2] = (1 - k) * p[k - 1] + (k - 2 * n) * p[k - 3];
1589                     } else if (k == 2) {
1590                         p[0] = -p[1];
1591                     }
1592                 }
1593                 if ((n & 0x1) == 0) {
1594                     v *= x;
1595                 }
1596 
1597                 coeff *= f;
1598                 function[n] = coeff * v;
1599             }
1600         }
1601 
1602         // apply function composition
1603         compose(operand, operandOffset, function, result, resultOffset);
1604     }
1605 
1606     /** Compute inverse hyperbolic sine of a derivative structure.
1607      * @param operand array holding the operand
1608      * @param operandOffset offset of the operand in its array
1609      * @param result array where result must be stored (for
1610      * inverse hyperbolic sine the result array <em>cannot</em> be the input
1611      * array)
1612      * @param resultOffset offset of the result in its array
1613      */
1614     public void asinh(final double[] operand, final int operandOffset,
1615                      final double[] result, final int resultOffset) {
1616 
1617         // create the function value and derivatives
1618         double[] function = new double[1 + order];
1619         final double x = operand[operandOffset];
1620         function[0] = JdkMath.asinh(x);
1621         if (order > 0) {
1622             // the nth order derivative of asinh has the form:
1623             // dn(asinh(x)/dxn = P_n(x) / [x^2 + 1]^((2n-1)/2)
1624             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
1625             // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 - 1 ...
1626             // the general recurrence relation for P_n is:
1627             // P_n(x) = (x^2+1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
1628             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
1629             final double[] p = new double[order];
1630             p[0] = 1;
1631             final double x2    = x * x;
1632             final double f     = 1.0 / (1 + x2);
1633             double coeff = JdkMath.sqrt(f);
1634             function[1] = coeff * p[0];
1635             for (int n = 2; n <= order; ++n) {
1636 
1637                 // update and evaluate polynomial P_n(x)
1638                 double v = 0;
1639                 p[n - 1] = (1 - n) * p[n - 2];
1640                 for (int k = n - 1; k >= 0; k -= 2) {
1641                     v = v * x2 + p[k];
1642                     if (k > 2) {
1643                         p[k - 2] = (k - 1) * p[k - 1] + (k - 2 * n) * p[k - 3];
1644                     } else if (k == 2) {
1645                         p[0] = p[1];
1646                     }
1647                 }
1648                 if ((n & 0x1) == 0) {
1649                     v *= x;
1650                 }
1651 
1652                 coeff *= f;
1653                 function[n] = coeff * v;
1654             }
1655         }
1656 
1657         // apply function composition
1658         compose(operand, operandOffset, function, result, resultOffset);
1659     }
1660 
1661     /** Compute inverse hyperbolic tangent of a derivative structure.
1662      * @param operand array holding the operand
1663      * @param operandOffset offset of the operand in its array
1664      * @param result array where result must be stored (for
1665      * inverse hyperbolic tangent the result array <em>cannot</em> be the input
1666      * array)
1667      * @param resultOffset offset of the result in its array
1668      */
1669     public void atanh(final double[] operand, final int operandOffset,
1670                       final double[] result, final int resultOffset) {
1671 
1672         // create the function value and derivatives
1673         double[] function = new double[1 + order];
1674         final double x = operand[operandOffset];
1675         function[0] = JdkMath.atanh(x);
1676         if (order > 0) {
1677             // the nth order derivative of atanh has the form:
1678             // dn(atanh(x)/dxn = Q_n(x) / (1 - x^2)^n
1679             // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
1680             // Q_1(x) = 1, Q_2(x) = 2x, Q_3(x) = 6x^2 + 2 ...
1681             // the general recurrence relation for Q_n is:
1682             // Q_n(x) = (1-x^2) Q_(n-1)'(x) + 2(n-1) x Q_(n-1)(x)
1683             // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
1684             final double[] q = new double[order];
1685             q[0] = 1;
1686             final double x2 = x * x;
1687             final double f  = 1.0 / (1 - x2);
1688             double coeff = f;
1689             function[1] = coeff * q[0];
1690             for (int n = 2; n <= order; ++n) {
1691 
1692                 // update and evaluate polynomial Q_n(x)
1693                 double v = 0;
1694                 q[n - 1] = n * q[n - 2];
1695                 for (int k = n - 1; k >= 0; k -= 2) {
1696                     v = v * x2 + q[k];
1697                     if (k > 2) {
1698                         q[k - 2] = (k - 1) * q[k - 1] + (2 * n - k + 1) * q[k - 3];
1699                     } else if (k == 2) {
1700                         q[0] = q[1];
1701                     }
1702                 }
1703                 if ((n & 0x1) == 0) {
1704                     v *= x;
1705                 }
1706 
1707                 coeff *= f;
1708                 function[n] = coeff * v;
1709             }
1710         }
1711 
1712         // apply function composition
1713         compose(operand, operandOffset, function, result, resultOffset);
1714     }
1715 
1716     /** Compute composition of a derivative structure by a function.
1717      * @param operand array holding the operand
1718      * @param operandOffset offset of the operand in its array
1719      * @param f array of value and derivatives of the function at
1720      * the current point (i.e. at {@code operand[operandOffset]}).
1721      * @param result array where result must be stored (for
1722      * composition the result array <em>cannot</em> be the input
1723      * array)
1724      * @param resultOffset offset of the result in its array
1725      */
1726     public void compose(final double[] operand, final int operandOffset, final double[] f,
1727                         final double[] result, final int resultOffset) {
1728         for (int i = 0; i < compIndirection.length; ++i) {
1729             final int[][] mappingI = compIndirection[i];
1730             double r = 0;
1731             for (int j = 0; j < mappingI.length; ++j) {
1732                 final int[] mappingIJ = mappingI[j];
1733                 double product = mappingIJ[0] * f[mappingIJ[1]];
1734                 for (int k = 2; k < mappingIJ.length; ++k) {
1735                     product *= operand[operandOffset + mappingIJ[k]];
1736                 }
1737                 r += product;
1738             }
1739             result[resultOffset + i] = r;
1740         }
1741     }
1742 
1743     /** Evaluate Taylor expansion of a derivative structure.
1744      * @param ds array holding the derivative structure
1745      * @param dsOffset offset of the derivative structure in its array
1746      * @param delta parameters offsets (&Delta;x, &Delta;y, ...)
1747      * @return value of the Taylor expansion at x + &Delta;x, y + &Delta;y, ...
1748      * @throws MathArithmeticException if factorials becomes too large
1749      */
1750     public double taylor(final double[] ds, final int dsOffset, final double ... delta)
1751        throws MathArithmeticException {
1752         double value = 0;
1753         for (int i = getSize() - 1; i >= 0; --i) {
1754             final int[] orders = getPartialDerivativeOrders(i);
1755             double term = ds[dsOffset + i];
1756             for (int k = 0; k < orders.length; ++k) {
1757                 if (orders[k] > 0) {
1758                     try {
1759                         term *= JdkMath.pow(delta[k], orders[k]) / Factorial.doubleValue(orders[k]);
1760                     } catch (NotPositiveException e) {
1761                         // this cannot happen
1762                         throw new MathInternalError(e);
1763                     }
1764                 }
1765             }
1766             value += term;
1767         }
1768         return value;
1769     }
1770 
1771     /** Check rules set compatibility.
1772      * @param compiler other compiler to check against instance
1773      * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
1774      */
1775     public void checkCompatibility(final DSCompiler compiler)
1776             throws DimensionMismatchException {
1777         if (parameters != compiler.parameters) {
1778             throw new DimensionMismatchException(parameters, compiler.parameters);
1779         }
1780         if (order != compiler.order) {
1781             throw new DimensionMismatchException(order, compiler.order);
1782         }
1783     }
1784 }