001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.math4.legacy.analysis.differentiation;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.List;
022import java.util.concurrent.atomic.AtomicReference;
023
024import org.apache.commons.numbers.core.Sum;
025import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
026import org.apache.commons.math4.legacy.exception.MathArithmeticException;
027import org.apache.commons.math4.legacy.exception.MathInternalError;
028import org.apache.commons.math4.legacy.exception.NotPositiveException;
029import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
030import org.apache.commons.numbers.combinatorics.Factorial;
031import org.apache.commons.math4.core.jdkmath.JdkMath;
032
033/** Class holding "compiled" computation rules for derivative structures.
034 * <p>This class implements the computation rules described in Dan Kalman's paper <a
035 * href="http://www1.american.edu/cas/mathstat/People/kalman/pdffiles/mmgautodiff.pdf">Doubly
036 * Recursive Multivariate Automatic Differentiation</a>, Mathematics Magazine, vol. 75,
037 * no. 3, June 2002. However, in order to avoid performances bottlenecks, the recursive
038 * rules are "compiled" once in an unfold form. This class does this recursion unrolling
039 * and stores the computation rules as simple loops with pre-computed indirection arrays.</p>
040 * <p>
041 * This class maps all derivative computation into single dimension arrays that hold the
042 * value and partial derivatives. The class does not hold these arrays, which remains under
043 * the responsibility of the caller. For each combination of number of free parameters and
044 * derivation order, only one compiler is necessary, and this compiler will be used to
045 * perform computations on all arrays provided to it, which can represent hundreds or
046 * thousands of different parameters kept together with all their partial derivatives.
047 * </p>
048 * <p>
049 * The arrays on which compilers operate contain only the partial derivatives together
050 * with the 0<sup>th</sup> derivative, i.e. the value. The partial derivatives are stored in
051 * a compiler-specific order, which can be retrieved using methods {@link
052 * #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} and {@link
053 * #getPartialDerivativeOrders(int)}. The value is guaranteed to be stored as the first element
054 * (i.e. the {@link #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} method returns
055 * 0 when called with 0 for all derivation orders and {@link #getPartialDerivativeOrders(int)
056 * getPartialDerivativeOrders} returns an array filled with 0 when called with 0 as the index).
057 * </p>
058 * <p>
059 * Note that the ordering changes with number of parameters and derivation order. For example
060 * given 2 parameters x and y, df/dy is stored at index 2 when derivation order is set to 1 (in
061 * this case the array has three elements: f, df/dx and df/dy). If derivation order is set to
062 * 2, then df/dy will be stored at index 3 (in this case the array has six elements: f, df/dx,
063 * df/dxdx, df/dy, df/dxdy and df/dydy).
064 * </p>
065 * <p>
066 * Given this structure, users can perform some simple operations like adding, subtracting
067 * or multiplying constants and negating the elements by themselves, knowing if they want to
068 * mutate their array or create a new array. These simple operations are not provided by
069 * the compiler. The compiler provides only the more complex operations between several arrays.
070 * </p>
071 * <p>This class is mainly used as the engine for scalar variable {@link DerivativeStructure}.
072 * It can also be used directly to hold several variables in arrays for more complex data
073 * structures. User can for example store a vector of n variables depending on three x, y
074 * and z free parameters in one array as follows:</p> <pre>
075 *   // parameter 0 is x, parameter 1 is y, parameter 2 is z
076 *   int parameters = 3;
077 *   DSCompiler compiler = DSCompiler.getCompiler(parameters, order);
078 *   int size = compiler.getSize();
079 *
080 *   // pack all elements in a single array
081 *   double[] array = new double[n * size];
082 *   for (int i = 0; i &lt; n; ++i) {
083 *
084 *     // we know value is guaranteed to be the first element
085 *     array[i * size] = v[i];
086 *
087 *     // we don't know where first derivatives are stored, so we ask the compiler
088 *     array[i * size + compiler.getPartialDerivativeIndex(1, 0, 0) = dvOnDx[i][0];
089 *     array[i * size + compiler.getPartialDerivativeIndex(0, 1, 0) = dvOnDy[i][0];
090 *     array[i * size + compiler.getPartialDerivativeIndex(0, 0, 1) = dvOnDz[i][0];
091 *
092 *     // we let all higher order derivatives set to 0
093 *
094 *   }
095 * </pre>
096 * <p>Then in another function, user can perform some operations on all elements stored
097 * in the single array, such as a simple product of all variables:</p> <pre>
098 *   // compute the product of all elements
099 *   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 */
125public 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}