DSCompiler.java

  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. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.List;
  21. import java.util.concurrent.atomic.AtomicReference;

  22. import org.apache.commons.numbers.core.Sum;
  23. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  24. import org.apache.commons.math4.legacy.exception.MathArithmeticException;
  25. import org.apache.commons.math4.legacy.exception.MathInternalError;
  26. import org.apache.commons.math4.legacy.exception.NotPositiveException;
  27. import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
  28. import org.apache.commons.numbers.combinatorics.Factorial;
  29. import org.apache.commons.math4.core.jdkmath.JdkMath;

  30. /** Class holding "compiled" computation rules for derivative structures.
  31.  * <p>This class implements the computation rules described in Dan Kalman's paper <a
  32.  * href="http://www1.american.edu/cas/mathstat/People/kalman/pdffiles/mmgautodiff.pdf">Doubly
  33.  * Recursive Multivariate Automatic Differentiation</a>, Mathematics Magazine, vol. 75,
  34.  * no. 3, June 2002. However, in order to avoid performances bottlenecks, the recursive
  35.  * rules are "compiled" once in an unfold form. This class does this recursion unrolling
  36.  * and stores the computation rules as simple loops with pre-computed indirection arrays.</p>
  37.  * <p>
  38.  * This class maps all derivative computation into single dimension arrays that hold the
  39.  * value and partial derivatives. The class does not hold these arrays, which remains under
  40.  * the responsibility of the caller. For each combination of number of free parameters and
  41.  * derivation order, only one compiler is necessary, and this compiler will be used to
  42.  * perform computations on all arrays provided to it, which can represent hundreds or
  43.  * thousands of different parameters kept together with all their partial derivatives.
  44.  * </p>
  45.  * <p>
  46.  * The arrays on which compilers operate contain only the partial derivatives together
  47.  * with the 0<sup>th</sup> derivative, i.e. the value. The partial derivatives are stored in
  48.  * a compiler-specific order, which can be retrieved using methods {@link
  49.  * #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} and {@link
  50.  * #getPartialDerivativeOrders(int)}. The value is guaranteed to be stored as the first element
  51.  * (i.e. the {@link #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} method returns
  52.  * 0 when called with 0 for all derivation orders and {@link #getPartialDerivativeOrders(int)
  53.  * getPartialDerivativeOrders} returns an array filled with 0 when called with 0 as the index).
  54.  * </p>
  55.  * <p>
  56.  * Note that the ordering changes with number of parameters and derivation order. For example
  57.  * given 2 parameters x and y, df/dy is stored at index 2 when derivation order is set to 1 (in
  58.  * this case the array has three elements: f, df/dx and df/dy). If derivation order is set to
  59.  * 2, then df/dy will be stored at index 3 (in this case the array has six elements: f, df/dx,
  60.  * df/dxdx, df/dy, df/dxdy and df/dydy).
  61.  * </p>
  62.  * <p>
  63.  * Given this structure, users can perform some simple operations like adding, subtracting
  64.  * or multiplying constants and negating the elements by themselves, knowing if they want to
  65.  * mutate their array or create a new array. These simple operations are not provided by
  66.  * the compiler. The compiler provides only the more complex operations between several arrays.
  67.  * </p>
  68.  * <p>This class is mainly used as the engine for scalar variable {@link DerivativeStructure}.
  69.  * It can also be used directly to hold several variables in arrays for more complex data
  70.  * structures. User can for example store a vector of n variables depending on three x, y
  71.  * and z free parameters in one array as follows:</p> <pre>
  72.  *   // parameter 0 is x, parameter 1 is y, parameter 2 is z
  73.  *   int parameters = 3;
  74.  *   DSCompiler compiler = DSCompiler.getCompiler(parameters, order);
  75.  *   int size = compiler.getSize();
  76.  *
  77.  *   // pack all elements in a single array
  78.  *   double[] array = new double[n * size];
  79.  *   for (int i = 0; i &lt; n; ++i) {
  80.  *
  81.  *     // we know value is guaranteed to be the first element
  82.  *     array[i * size] = v[i];
  83.  *
  84.  *     // we don't know where first derivatives are stored, so we ask the compiler
  85.  *     array[i * size + compiler.getPartialDerivativeIndex(1, 0, 0) = dvOnDx[i][0];
  86.  *     array[i * size + compiler.getPartialDerivativeIndex(0, 1, 0) = dvOnDy[i][0];
  87.  *     array[i * size + compiler.getPartialDerivativeIndex(0, 0, 1) = dvOnDz[i][0];
  88.  *
  89.  *     // we let all higher order derivatives set to 0
  90.  *
  91.  *   }
  92.  * </pre>
  93.  * <p>Then in another function, user can perform some operations on all elements stored
  94.  * in the single array, such as a simple product of all variables:</p> <pre>
  95.  *   // compute the product of all elements
  96.  *   double[] product = new double[size];
  97.  *   prod[0] = 1.0;
  98.  *   for (int i = 0; i &lt; n; ++i) {
  99.  *     double[] tmp = product.clone();
  100.  *     compiler.multiply(tmp, 0, array, i * size, product, 0);
  101.  *   }
  102.  *
  103.  *   // value
  104.  *   double p = product[0];
  105.  *
  106.  *   // first derivatives
  107.  *   double dPdX = product[compiler.getPartialDerivativeIndex(1, 0, 0)];
  108.  *   double dPdY = product[compiler.getPartialDerivativeIndex(0, 1, 0)];
  109.  *   double dPdZ = product[compiler.getPartialDerivativeIndex(0, 0, 1)];
  110.  *
  111.  *   // cross derivatives (assuming order was at least 2)
  112.  *   double dPdXdX = product[compiler.getPartialDerivativeIndex(2, 0, 0)];
  113.  *   double dPdXdY = product[compiler.getPartialDerivativeIndex(1, 1, 0)];
  114.  *   double dPdXdZ = product[compiler.getPartialDerivativeIndex(1, 0, 1)];
  115.  *   double dPdYdY = product[compiler.getPartialDerivativeIndex(0, 2, 0)];
  116.  *   double dPdYdZ = product[compiler.getPartialDerivativeIndex(0, 1, 1)];
  117.  *   double dPdZdZ = product[compiler.getPartialDerivativeIndex(0, 0, 2)];
  118.  * </pre>
  119.  * @see DerivativeStructure
  120.  * @since 3.1
  121.  */
  122. public final class DSCompiler {
  123.     /** Array of all compilers created so far. */
  124.     private static AtomicReference<DSCompiler[][]> compilers =
  125.             new AtomicReference<>(null);

  126.     /** Number of free parameters. */
  127.     private final int parameters;

  128.     /** Derivation order. */
  129.     private final int order;

  130.     /** Number of partial derivatives (including the single 0 order derivative element). */
  131.     private final int[][] sizes;

  132.     /** Indirection array for partial derivatives. */
  133.     private final int[][] derivativesIndirection;

  134.     /** Indirection array of the lower derivative elements. */
  135.     private final int[] lowerIndirection;

  136.     /** Indirection arrays for multiplication. */
  137.     private final int[][][] multIndirection;

  138.     /** Indirection arrays for function composition. */
  139.     private final int[][][] compIndirection;

  140.     /** Private constructor, reserved for the factory method {@link #getCompiler(int, int)}.
  141.      * @param parameters number of free parameters
  142.      * @param order derivation order
  143.      * @param valueCompiler compiler for the value part
  144.      * @param derivativeCompiler compiler for the derivative part
  145.      * @throws NumberIsTooLargeException if order is too large
  146.      */
  147.     private DSCompiler(final int parameters, final int order,
  148.                        final DSCompiler valueCompiler, final DSCompiler derivativeCompiler)
  149.         throws NumberIsTooLargeException {

  150.         this.parameters = parameters;
  151.         this.order      = order;
  152.         this.sizes      = compileSizes(parameters, order, valueCompiler);
  153.         this.derivativesIndirection =
  154.                 compileDerivativesIndirection(parameters, order,
  155.                                               valueCompiler, derivativeCompiler);
  156.         this.lowerIndirection =
  157.                 compileLowerIndirection(parameters, order,
  158.                                         valueCompiler, derivativeCompiler);
  159.         this.multIndirection =
  160.                 compileMultiplicationIndirection(parameters, order,
  161.                                                  valueCompiler, derivativeCompiler, lowerIndirection);
  162.         this.compIndirection =
  163.                 compileCompositionIndirection(parameters, order,
  164.                                               valueCompiler, derivativeCompiler,
  165.                                               sizes, derivativesIndirection);
  166.     }

  167.     /** Get the compiler for number of free parameters and order.
  168.      * @param parameters number of free parameters
  169.      * @param order derivation order
  170.      * @return cached rules set
  171.      * @throws NumberIsTooLargeException if order is too large
  172.      */
  173.     public static DSCompiler getCompiler(int parameters, int order)
  174.         throws NumberIsTooLargeException {

  175.         // get the cached compilers
  176.         final DSCompiler[][] cache = compilers.get();
  177.         if (cache != null && cache.length > parameters &&
  178.             cache[parameters].length > order && cache[parameters][order] != null) {
  179.             // the compiler has already been created
  180.             return cache[parameters][order];
  181.         }

  182.         // we need to create more compilers
  183.         final int maxParameters = JdkMath.max(parameters, cache == null ? 0 : cache.length);
  184.         final int maxOrder      = JdkMath.max(order,     cache == null ? 0 : cache[0].length);
  185.         final DSCompiler[][] newCache = new DSCompiler[maxParameters + 1][maxOrder + 1];

  186.         if (cache != null) {
  187.             // preserve the already created compilers
  188.             for (int i = 0; i < cache.length; ++i) {
  189.                 System.arraycopy(cache[i], 0, newCache[i], 0, cache[i].length);
  190.             }
  191.         }

  192.         // create the array in increasing diagonal order
  193.         for (int diag = 0; diag <= parameters + order; ++diag) {
  194.             for (int o = JdkMath.max(0, diag - parameters); o <= JdkMath.min(order, diag); ++o) {
  195.                 final int p = diag - o;
  196.                 if (newCache[p][o] == null) {
  197.                     final DSCompiler valueCompiler      = (p == 0) ? null : newCache[p - 1][o];
  198.                     final DSCompiler derivativeCompiler = (o == 0) ? null : newCache[p][o - 1];
  199.                     newCache[p][o] = new DSCompiler(p, o, valueCompiler, derivativeCompiler);
  200.                 }
  201.             }
  202.         }

  203.         // atomically reset the cached compilers array
  204.         compilers.compareAndSet(cache, newCache);

  205.         return newCache[parameters][order];
  206.     }

  207.     /** Compile the sizes array.
  208.      * @param parameters number of free parameters
  209.      * @param order derivation order
  210.      * @param valueCompiler compiler for the value part
  211.      * @return sizes array
  212.      */
  213.     private static int[][] compileSizes(final int parameters, final int order,
  214.                                         final DSCompiler valueCompiler) {

  215.         final int[][] sizes = new int[parameters + 1][order + 1];
  216.         if (parameters == 0) {
  217.             Arrays.fill(sizes[0], 1);
  218.         } else {
  219.             System.arraycopy(valueCompiler.sizes, 0, sizes, 0, parameters);
  220.             sizes[parameters][0] = 1;
  221.             for (int i = 0; i < order; ++i) {
  222.                 sizes[parameters][i + 1] = sizes[parameters][i] + sizes[parameters - 1][i + 1];
  223.             }
  224.         }

  225.         return sizes;
  226.     }

  227.     /** Compile the derivatives indirection array.
  228.      * @param parameters number of free parameters
  229.      * @param order derivation order
  230.      * @param valueCompiler compiler for the value part
  231.      * @param derivativeCompiler compiler for the derivative part
  232.      * @return derivatives indirection array
  233.      */
  234.     private static int[][] compileDerivativesIndirection(final int parameters, final int order,
  235.                                                       final DSCompiler valueCompiler,
  236.                                                       final DSCompiler derivativeCompiler) {

  237.         if (parameters == 0 || order == 0) {
  238.             return new int[1][parameters];
  239.         }

  240.         final int vSize = valueCompiler.derivativesIndirection.length;
  241.         final int dSize = derivativeCompiler.derivativesIndirection.length;
  242.         final int[][] derivativesIndirection = new int[vSize + dSize][parameters];

  243.         // set up the indices for the value part
  244.         for (int i = 0; i < vSize; ++i) {
  245.             // copy the first indices, the last one remaining set to 0
  246.             System.arraycopy(valueCompiler.derivativesIndirection[i], 0,
  247.                              derivativesIndirection[i], 0,
  248.                              parameters - 1);
  249.         }

  250.         // set up the indices for the derivative part
  251.         for (int i = 0; i < dSize; ++i) {

  252.             // copy the indices
  253.             System.arraycopy(derivativeCompiler.derivativesIndirection[i], 0,
  254.                              derivativesIndirection[vSize + i], 0,
  255.                              parameters);

  256.             // increment the derivation order for the last parameter
  257.             derivativesIndirection[vSize + i][parameters - 1]++;
  258.         }

  259.         return derivativesIndirection;
  260.     }

  261.     /** Compile the lower derivatives indirection array.
  262.      * <p>
  263.      * This indirection array contains the indices of all elements
  264.      * except derivatives for last derivation order.
  265.      * </p>
  266.      * @param parameters number of free parameters
  267.      * @param order derivation order
  268.      * @param valueCompiler compiler for the value part
  269.      * @param derivativeCompiler compiler for the derivative part
  270.      * @return lower derivatives indirection array
  271.      */
  272.     private static int[] compileLowerIndirection(final int parameters, final int order,
  273.                                               final DSCompiler valueCompiler,
  274.                                               final DSCompiler derivativeCompiler) {

  275.         if (parameters == 0 || order <= 1) {
  276.             return new int[] { 0 };
  277.         }

  278.         // this is an implementation of definition 6 in Dan Kalman's paper.
  279.         final int vSize = valueCompiler.lowerIndirection.length;
  280.         final int dSize = derivativeCompiler.lowerIndirection.length;
  281.         final int[] lowerIndirection = new int[vSize + dSize];
  282.         System.arraycopy(valueCompiler.lowerIndirection, 0, lowerIndirection, 0, vSize);
  283.         for (int i = 0; i < dSize; ++i) {
  284.             lowerIndirection[vSize + i] = valueCompiler.getSize() + derivativeCompiler.lowerIndirection[i];
  285.         }

  286.         return lowerIndirection;
  287.     }

  288.     /** Compile the multiplication indirection array.
  289.      * <p>
  290.      * This indirection array contains the indices of all pairs of elements
  291.      * involved when computing a multiplication. This allows a straightforward
  292.      * loop-based multiplication (see {@link #multiply(double[], int, double[], int, double[], int)}).
  293.      * </p>
  294.      * @param parameters number of free parameters
  295.      * @param order derivation order
  296.      * @param valueCompiler compiler for the value part
  297.      * @param derivativeCompiler compiler for the derivative part
  298.      * @param lowerIndirection lower derivatives indirection array
  299.      * @return multiplication indirection array
  300.      */
  301.     private static int[][][] compileMultiplicationIndirection(final int parameters, final int order,
  302.                                                            final DSCompiler valueCompiler,
  303.                                                            final DSCompiler derivativeCompiler,
  304.                                                            final int[] lowerIndirection) {

  305.         if (parameters == 0 || order == 0) {
  306.             return new int[][][] { { { 1, 0, 0 } } };
  307.         }

  308.         // this is an implementation of definition 3 in Dan Kalman's paper.
  309.         final int vSize = valueCompiler.multIndirection.length;
  310.         final int dSize = derivativeCompiler.multIndirection.length;
  311.         final int[][][] multIndirection = new int[vSize + dSize][][];

  312.         System.arraycopy(valueCompiler.multIndirection, 0, multIndirection, 0, vSize);

  313.         for (int i = 0; i < dSize; ++i) {
  314.             final int[][] dRow = derivativeCompiler.multIndirection[i];
  315.             List<int[]> row = new ArrayList<>(dRow.length * 2);
  316.             for (int j = 0; j < dRow.length; ++j) {
  317.                 row.add(new int[] { dRow[j][0], lowerIndirection[dRow[j][1]], vSize + dRow[j][2] });
  318.                 row.add(new int[] { dRow[j][0], vSize + dRow[j][1], lowerIndirection[dRow[j][2]] });
  319.             }

  320.             // combine terms with similar derivation orders
  321.             final List<int[]> combined = new ArrayList<>(row.size());
  322.             for (int j = 0; j < row.size(); ++j) {
  323.                 final int[] termJ = row.get(j);
  324.                 if (termJ[0] > 0) {
  325.                     for (int k = j + 1; k < row.size(); ++k) {
  326.                         final int[] termK = row.get(k);
  327.                         if (termJ[1] == termK[1] && termJ[2] == termK[2]) {
  328.                             // combine termJ and termK
  329.                             termJ[0] += termK[0];
  330.                             // make sure we will skip termK later on in the outer loop
  331.                             termK[0] = 0;
  332.                         }
  333.                     }
  334.                     combined.add(termJ);
  335.                 }
  336.             }

  337.             multIndirection[vSize + i] = combined.toArray(new int[combined.size()][]);
  338.         }

  339.         return multIndirection;
  340.     }

  341.     /** Compile the function composition indirection array.
  342.      * <p>
  343.      * This indirection array contains the indices of all sets of elements
  344.      * involved when computing a composition. This allows a straightforward
  345.      * loop-based composition (see {@link #compose(double[], int, double[], double[], int)}).
  346.      * </p>
  347.      * @param parameters number of free parameters
  348.      * @param order derivation order
  349.      * @param valueCompiler compiler for the value part
  350.      * @param derivativeCompiler compiler for the derivative part
  351.      * @param sizes sizes array
  352.      * @param derivativesIndirection derivatives indirection array
  353.      * @return multiplication indirection array
  354.      * @throws NumberIsTooLargeException if order is too large
  355.      */
  356.     private static int[][][] compileCompositionIndirection(final int parameters, final int order,
  357.                                                            final DSCompiler valueCompiler,
  358.                                                            final DSCompiler derivativeCompiler,
  359.                                                            final int[][] sizes,
  360.                                                            final int[][] derivativesIndirection)
  361.        throws NumberIsTooLargeException {

  362.         if (parameters == 0 || order == 0) {
  363.             return new int[][][] { { { 1, 0 } } };
  364.         }

  365.         final int vSize = valueCompiler.compIndirection.length;
  366.         final int dSize = derivativeCompiler.compIndirection.length;
  367.         final int[][][] compIndirection = new int[vSize + dSize][][];

  368.         // the composition rules from the value part can be reused as is
  369.         System.arraycopy(valueCompiler.compIndirection, 0, compIndirection, 0, vSize);

  370.         // the composition rules for the derivative part are deduced by
  371.         // differentiation the rules from the underlying compiler once
  372.         // with respect to the parameter this compiler handles and the
  373.         // underlying one did not handle
  374.         for (int i = 0; i < dSize; ++i) {
  375.             List<int[]> row = new ArrayList<>();
  376.             for (int[] term : derivativeCompiler.compIndirection[i]) {

  377.                 // handle term p * f_k(g(x)) * g_l1(x) * g_l2(x) * ... * g_lp(x)

  378.                 // derive the first factor in the term: f_k with respect to new parameter
  379.                 int[] derivedTermF = new int[term.length + 1];
  380.                 derivedTermF[0] = term[0];     // p
  381.                 derivedTermF[1] = term[1] + 1; // f_(k+1)
  382.                 int[] orders = new int[parameters];
  383.                 orders[parameters - 1] = 1;
  384.                 derivedTermF[term.length] = getPartialDerivativeIndex(parameters, order, sizes, orders);  // g_1
  385.                 for (int j = 2; j < term.length; ++j) {
  386.                     // convert the indices as the mapping for the current order
  387.                     // is different from the mapping with one less order
  388.                     derivedTermF[j] = convertIndex(term[j], parameters,
  389.                                                    derivativeCompiler.derivativesIndirection,
  390.                                                    parameters, order, sizes);
  391.                 }
  392.                 Arrays.sort(derivedTermF, 2, derivedTermF.length);
  393.                 row.add(derivedTermF);

  394.                 // derive the various g_l
  395.                 for (int l = 2; l < term.length; ++l) {
  396.                     int[] derivedTermG = new int[term.length];
  397.                     derivedTermG[0] = term[0];
  398.                     derivedTermG[1] = term[1];
  399.                     for (int j = 2; j < term.length; ++j) {
  400.                         // convert the indices as the mapping for the current order
  401.                         // is different from the mapping with one less order
  402.                         derivedTermG[j] = convertIndex(term[j], parameters,
  403.                                                        derivativeCompiler.derivativesIndirection,
  404.                                                        parameters, order, sizes);
  405.                         if (j == l) {
  406.                             // derive this term
  407.                             System.arraycopy(derivativesIndirection[derivedTermG[j]], 0, orders, 0, parameters);
  408.                             orders[parameters - 1]++;
  409.                             derivedTermG[j] = getPartialDerivativeIndex(parameters, order, sizes, orders);
  410.                         }
  411.                     }
  412.                     Arrays.sort(derivedTermG, 2, derivedTermG.length);
  413.                     row.add(derivedTermG);
  414.                 }
  415.             }

  416.             // combine terms with similar derivation orders
  417.             final List<int[]> combined = new ArrayList<>(row.size());
  418.             for (int j = 0; j < row.size(); ++j) {
  419.                 final int[] termJ = row.get(j);
  420.                 if (termJ[0] > 0) {
  421.                     for (int k = j + 1; k < row.size(); ++k) {
  422.                         final int[] termK = row.get(k);
  423.                         boolean equals = termJ.length == termK.length;
  424.                         for (int l = 1; equals && l < termJ.length; ++l) {
  425.                             equals &= termJ[l] == termK[l];
  426.                         }
  427.                         if (equals) {
  428.                             // combine termJ and termK
  429.                             termJ[0] += termK[0];
  430.                             // make sure we will skip termK later on in the outer loop
  431.                             termK[0] = 0;
  432.                         }
  433.                     }
  434.                     combined.add(termJ);
  435.                 }
  436.             }

  437.             compIndirection[vSize + i] = combined.toArray(new int[combined.size()][]);
  438.         }

  439.         return compIndirection;
  440.     }

  441.     /** Get the index of a partial derivative in the array.
  442.      * <p>
  443.      * If all orders are set to 0, then the 0<sup>th</sup> order derivative
  444.      * is returned, which is the value of the function.
  445.      * </p>
  446.      * <p>The indices of derivatives are between 0 and {@link #getSize() getSize()} - 1.
  447.      * Their specific order is fixed for a given compiler, but otherwise not
  448.      * publicly specified. There are however some simple cases which have guaranteed
  449.      * indices:
  450.      * </p>
  451.      * <ul>
  452.      *   <li>the index of 0<sup>th</sup> order derivative is always 0</li>
  453.      *   <li>if there is only 1 {@link #getFreeParameters() free parameter}, then the
  454.      *   derivatives are sorted in increasing derivation order (i.e. f at index 0, df/dp
  455.      *   at index 1, d<sup>2</sup>f/dp<sup>2</sup> at index 2 ...
  456.      *   d<sup>k</sup>f/dp<sup>k</sup> at index k),</li>
  457.      *   <li>if the {@link #getOrder() derivation order} is 1, then the derivatives
  458.      *   are sorted in increasing free parameter order (i.e. f at index 0, df/dx<sub>1</sub>
  459.      *   at index 1, df/dx<sub>2</sub> at index 2 ... df/dx<sub>k</sub> at index k),</li>
  460.      *   <li>all other cases are not publicly specified</li>
  461.      * </ul>
  462.      * <p>
  463.      * This method is the inverse of method {@link #getPartialDerivativeOrders(int)}
  464.      * </p>
  465.      * @param orders derivation orders with respect to each parameter
  466.      * @return index of the partial derivative
  467.      * @exception DimensionMismatchException if the numbers of parameters does not
  468.      * match the instance
  469.      * @exception NumberIsTooLargeException if sum of derivation orders is larger
  470.      * than the instance limits
  471.      * @see #getPartialDerivativeOrders(int)
  472.      */
  473.     public int getPartialDerivativeIndex(final int ... orders)
  474.             throws DimensionMismatchException, NumberIsTooLargeException {

  475.         // safety check
  476.         if (orders.length != getFreeParameters()) {
  477.             throw new DimensionMismatchException(orders.length, getFreeParameters());
  478.         }

  479.         return getPartialDerivativeIndex(parameters, order, sizes, orders);
  480.     }

  481.     /** Get the index of a partial derivative in an array.
  482.      * @param parameters number of free parameters
  483.      * @param order derivation order
  484.      * @param sizes sizes array
  485.      * @param orders derivation orders with respect to each parameter
  486.      * (the length of this array must match the number of parameters)
  487.      * @return index of the partial derivative
  488.      * @exception NumberIsTooLargeException if sum of derivation orders is larger
  489.      * than the instance limits
  490.      */
  491.     private static int getPartialDerivativeIndex(final int parameters, final int order,
  492.                                                  final int[][] sizes, final int ... orders)
  493.         throws NumberIsTooLargeException {

  494.         // the value is obtained by diving into the recursive Dan Kalman's structure
  495.         // this is theorem 2 of his paper, with recursion replaced by iteration
  496.         int index     = 0;
  497.         int m         = order;
  498.         int ordersSum = 0;
  499.         for (int i = parameters - 1; i >= 0; --i) {

  500.             // derivative order for current free parameter
  501.             int derivativeOrder = orders[i];

  502.             // safety check
  503.             ordersSum += derivativeOrder;
  504.             if (ordersSum > order) {
  505.                 throw new NumberIsTooLargeException(ordersSum, order, true);
  506.             }

  507.             while (derivativeOrder-- > 0) {
  508.                 // as long as we differentiate according to current free parameter,
  509.                 // we have to skip the value part and dive into the derivative part
  510.                 // so we add the size of the value part to the base index
  511.                 index += sizes[i][m--];
  512.             }
  513.         }

  514.         return index;
  515.     }

  516.     /** Convert an index from one (parameters, order) structure to another.
  517.      * @param index index of a partial derivative in source derivative structure
  518.      * @param srcP number of free parameters in source derivative structure
  519.      * @param srcDerivativesIndirection derivatives indirection array for the source
  520.      * derivative structure
  521.      * @param destP number of free parameters in destination derivative structure
  522.      * @param destO derivation order in destination derivative structure
  523.      * @param destSizes sizes array for the destination derivative structure
  524.      * @return index of the partial derivative with the <em>same</em> characteristics
  525.      * in destination derivative structure
  526.      * @throws NumberIsTooLargeException if order is too large
  527.      */
  528.     private static int convertIndex(final int index,
  529.                                     final int srcP, final int[][] srcDerivativesIndirection,
  530.                                     final int destP, final int destO, final int[][] destSizes)
  531.         throws NumberIsTooLargeException {
  532.         int[] orders = new int[destP];
  533.         System.arraycopy(srcDerivativesIndirection[index], 0, orders, 0, JdkMath.min(srcP, destP));
  534.         return getPartialDerivativeIndex(destP, destO, destSizes, orders);
  535.     }

  536.     /** Get the derivation orders for a specific index in the array.
  537.      * <p>
  538.      * This method is the inverse of {@link #getPartialDerivativeIndex(int...)}.
  539.      * </p>
  540.      * @param index of the partial derivative
  541.      * @return orders derivation orders with respect to each parameter
  542.      * @see #getPartialDerivativeIndex(int...)
  543.      */
  544.     public int[] getPartialDerivativeOrders(final int index) {
  545.         return derivativesIndirection[index];
  546.     }

  547.     /** Get the number of free parameters.
  548.      * @return number of free parameters
  549.      */
  550.     public int getFreeParameters() {
  551.         return parameters;
  552.     }

  553.     /** Get the derivation order.
  554.      * @return derivation order
  555.      */
  556.     public int getOrder() {
  557.         return order;
  558.     }

  559.     /** Get the array size required for holding partial derivatives data.
  560.      * <p>
  561.      * This number includes the single 0 order derivative element, which is
  562.      * guaranteed to be stored in the first element of the array.
  563.      * </p>
  564.      * @return array size required for holding partial derivatives data
  565.      */
  566.     public int getSize() {
  567.         return sizes[parameters][order];
  568.     }

  569.     /** Compute linear combination.
  570.      * The derivative structure built will be a1 * ds1 + a2 * ds2
  571.      * @param a1 first scale factor
  572.      * @param c1 first base (unscaled) component
  573.      * @param offset1 offset of first operand in its array
  574.      * @param a2 second scale factor
  575.      * @param c2 second base (unscaled) component
  576.      * @param offset2 offset of second operand in its array
  577.      * @param result array where result must be stored (it may be
  578.      * one of the input arrays)
  579.      * @param resultOffset offset of the result in its array
  580.      */
  581.     public void linearCombination(final double a1, final double[] c1, final int offset1,
  582.                                   final double a2, final double[] c2, final int offset2,
  583.                                   final double[] result, final int resultOffset) {
  584.         for (int i = 0; i < getSize(); ++i) {
  585.             result[resultOffset + i] = Sum.create()
  586.                 .addProduct(a1, c1[offset1 + i])
  587.                 .addProduct(a2, c2[offset2 + i]).getAsDouble();
  588.         }
  589.     }

  590.     /** Compute linear combination.
  591.      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
  592.      * @param a1 first scale factor
  593.      * @param c1 first base (unscaled) component
  594.      * @param offset1 offset of first operand in its array
  595.      * @param a2 second scale factor
  596.      * @param c2 second base (unscaled) component
  597.      * @param offset2 offset of second operand in its array
  598.      * @param a3 third scale factor
  599.      * @param c3 third base (unscaled) component
  600.      * @param offset3 offset of third operand in its array
  601.      * @param result array where result must be stored (it may be
  602.      * one of the input arrays)
  603.      * @param resultOffset offset of the result in its array
  604.      */
  605.     public void linearCombination(final double a1, final double[] c1, final int offset1,
  606.                                   final double a2, final double[] c2, final int offset2,
  607.                                   final double a3, final double[] c3, final int offset3,
  608.                                   final double[] result, final int resultOffset) {
  609.         for (int i = 0; i < getSize(); ++i) {
  610.             result[resultOffset + i] = Sum.create()
  611.                 .addProduct(a1, c1[offset1 + i])
  612.                 .addProduct(a2, c2[offset2 + i])
  613.                 .addProduct(a3, c3[offset3 + i]).getAsDouble();
  614.         }
  615.     }

  616.     /** Compute linear combination.
  617.      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
  618.      * @param a1 first scale factor
  619.      * @param c1 first base (unscaled) component
  620.      * @param offset1 offset of first operand in its array
  621.      * @param a2 second scale factor
  622.      * @param c2 second base (unscaled) component
  623.      * @param offset2 offset of second operand in its array
  624.      * @param a3 third scale factor
  625.      * @param c3 third base (unscaled) component
  626.      * @param offset3 offset of third operand in its array
  627.      * @param a4 fourth scale factor
  628.      * @param c4 fourth base (unscaled) component
  629.      * @param offset4 offset of fourth operand in its array
  630.      * @param result array where result must be stored (it may be
  631.      * one of the input arrays)
  632.      * @param resultOffset offset of the result in its array
  633.      */
  634.     public void linearCombination(final double a1, final double[] c1, final int offset1,
  635.                                   final double a2, final double[] c2, final int offset2,
  636.                                   final double a3, final double[] c3, final int offset3,
  637.                                   final double a4, final double[] c4, final int offset4,
  638.                                   final double[] result, final int resultOffset) {
  639.         for (int i = 0; i < getSize(); ++i) {
  640.             result[resultOffset + i] = Sum.create()
  641.                 .addProduct(a1, c1[offset1 + i])
  642.                 .addProduct(a2, c2[offset2 + i])
  643.                 .addProduct(a3, c3[offset3 + i])
  644.                 .addProduct(a4, c4[offset4 + i]).getAsDouble();
  645.         }
  646.     }

  647.     /** Perform addition of two derivative structures.
  648.      * @param lhs array holding left hand side of addition
  649.      * @param lhsOffset offset of the left hand side in its array
  650.      * @param rhs array right hand side of addition
  651.      * @param rhsOffset offset of the right hand side in its array
  652.      * @param result array where result must be stored (it may be
  653.      * one of the input arrays)
  654.      * @param resultOffset offset of the result in its array
  655.      */
  656.     public void add(final double[] lhs, final int lhsOffset,
  657.                     final double[] rhs, final int rhsOffset,
  658.                     final double[] result, final int resultOffset) {
  659.         for (int i = 0; i < getSize(); ++i) {
  660.             result[resultOffset + i] = lhs[lhsOffset + i] + rhs[rhsOffset + i];
  661.         }
  662.     }
  663.     /** Perform subtraction of two derivative structures.
  664.      * @param lhs array holding left hand side of subtraction
  665.      * @param lhsOffset offset of the left hand side in its array
  666.      * @param rhs array right hand side of subtraction
  667.      * @param rhsOffset offset of the right hand side in its array
  668.      * @param result array where result must be stored (it may be
  669.      * one of the input arrays)
  670.      * @param resultOffset offset of the result in its array
  671.      */
  672.     public void subtract(final double[] lhs, final int lhsOffset,
  673.                          final double[] rhs, final int rhsOffset,
  674.                          final double[] result, final int resultOffset) {
  675.         for (int i = 0; i < getSize(); ++i) {
  676.             result[resultOffset + i] = lhs[lhsOffset + i] - rhs[rhsOffset + i];
  677.         }
  678.     }

  679.     /** Perform multiplication of two derivative structures.
  680.      * @param lhs array holding left hand side of multiplication
  681.      * @param lhsOffset offset of the left hand side in its array
  682.      * @param rhs array right hand side of multiplication
  683.      * @param rhsOffset offset of the right hand side in its array
  684.      * @param result array where result must be stored (for
  685.      * multiplication the result array <em>cannot</em> be one of
  686.      * the input arrays)
  687.      * @param resultOffset offset of the result in its array
  688.      */
  689.     public void multiply(final double[] lhs, final int lhsOffset,
  690.                          final double[] rhs, final int rhsOffset,
  691.                          final double[] result, final int resultOffset) {
  692.         for (int i = 0; i < multIndirection.length; ++i) {
  693.             final int[][] mappingI = multIndirection[i];
  694.             double r = 0;
  695.             for (int j = 0; j < mappingI.length; ++j) {
  696.                 r += mappingI[j][0] *
  697.                      lhs[lhsOffset + mappingI[j][1]] *
  698.                      rhs[rhsOffset + mappingI[j][2]];
  699.             }
  700.             result[resultOffset + i] = r;
  701.         }
  702.     }

  703.     /** Perform division of two derivative structures.
  704.      * @param lhs array holding left hand side of division
  705.      * @param lhsOffset offset of the left hand side in its array
  706.      * @param rhs array right hand side of division
  707.      * @param rhsOffset offset of the right hand side in its array
  708.      * @param result array where result must be stored (for
  709.      * division the result array <em>cannot</em> be one of
  710.      * the input arrays)
  711.      * @param resultOffset offset of the result in its array
  712.      */
  713.     public void divide(final double[] lhs, final int lhsOffset,
  714.                        final double[] rhs, final int rhsOffset,
  715.                        final double[] result, final int resultOffset) {
  716.         final double[] reciprocal = new double[getSize()];
  717.         pow(rhs, lhsOffset, -1, reciprocal, 0);
  718.         multiply(lhs, lhsOffset, reciprocal, 0, result, resultOffset);
  719.     }

  720.     /** Perform remainder of two derivative structures.
  721.      * @param lhs array holding left hand side of remainder
  722.      * @param lhsOffset offset of the left hand side in its array
  723.      * @param rhs array right hand side of remainder
  724.      * @param rhsOffset offset of the right hand side in its array
  725.      * @param result array where result must be stored (it may be
  726.      * one of the input arrays)
  727.      * @param resultOffset offset of the result in its array
  728.      */
  729.     public void remainder(final double[] lhs, final int lhsOffset,
  730.                           final double[] rhs, final int rhsOffset,
  731.                           final double[] result, final int resultOffset) {

  732.         // compute k such that lhs % rhs = lhs - k rhs
  733.         final double rem = JdkMath.IEEEremainder(lhs[lhsOffset], rhs[rhsOffset]);
  734.         final double k   = JdkMath.rint((lhs[lhsOffset] - rem) / rhs[rhsOffset]);

  735.         // set up value
  736.         result[resultOffset] = rem;

  737.         // set up partial derivatives
  738.         for (int i = 1; i < getSize(); ++i) {
  739.             result[resultOffset + i] = lhs[lhsOffset + i] - k * rhs[rhsOffset + i];
  740.         }
  741.     }

  742.     /** Compute power of a double to a derivative structure.
  743.      * @param a number to exponentiate
  744.      * @param operand array holding the power
  745.      * @param operandOffset offset of the power in its array
  746.      * @param result array where result must be stored (for
  747.      * power the result array <em>cannot</em> be the input
  748.      * array)
  749.      * @param resultOffset offset of the result in its array
  750.      * @since 3.3
  751.      */
  752.     public void pow(final double a,
  753.                     final double[] operand, final int operandOffset,
  754.                     final double[] result, final int resultOffset) {

  755.         // create the function value and derivatives
  756.         // [a^x, ln(a) a^x, ln(a)^2 a^x,, ln(a)^3 a^x, ... ]
  757.         final double[] function = new double[1 + order];
  758.         if (a == 0) {
  759.             if (operand[operandOffset] == 0) {
  760.                 function[0] = 1;
  761.                 double infinity = Double.POSITIVE_INFINITY;
  762.                 for (int i = 1; i < function.length; ++i) {
  763.                     infinity = -infinity;
  764.                     function[i] = infinity;
  765.                 }
  766.             } else if (operand[operandOffset] < 0) {
  767.                 Arrays.fill(function, Double.NaN);
  768.             }
  769.         } else {
  770.             function[0] = JdkMath.pow(a, operand[operandOffset]);
  771.             final double lnA = JdkMath.log(a);
  772.             for (int i = 1; i < function.length; ++i) {
  773.                 function[i] = lnA * function[i - 1];
  774.             }
  775.         }


  776.         // apply function composition
  777.         compose(operand, operandOffset, function, result, resultOffset);
  778.     }

  779.     /** Compute power of a derivative structure.
  780.      * @param operand array holding the operand
  781.      * @param operandOffset offset of the operand in its array
  782.      * @param p power to apply
  783.      * @param result array where result must be stored (for
  784.      * power the result array <em>cannot</em> be the input
  785.      * array)
  786.      * @param resultOffset offset of the result in its array
  787.      */
  788.     public void pow(final double[] operand, final int operandOffset, final double p,
  789.                     final double[] result, final int resultOffset) {

  790.         if (p == 0) {
  791.             // special case, x^0 = 1 for all x
  792.             result[resultOffset] = 1.0;
  793.             Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), 0);
  794.             return;
  795.         }

  796.         if (operand[operandOffset] == 0) {
  797.             // special case, 0^p = 0 for all p
  798.             Arrays.fill(result, resultOffset, resultOffset + getSize(), 0);
  799.             return;
  800.         }

  801.         // create the function value and derivatives
  802.         // [x^p, px^(p-1), p(p-1)x^(p-2), ... ]
  803.         double[] function = new double[1 + order];
  804.         double xk = JdkMath.pow(operand[operandOffset], p - order);
  805.         for (int i = order; i > 0; --i) {
  806.             function[i] = xk;
  807.             xk *= operand[operandOffset];
  808.         }
  809.         function[0] = xk;
  810.         double coefficient = p;
  811.         for (int i = 1; i <= order; ++i) {
  812.             function[i] *= coefficient;
  813.             coefficient *= p - i;
  814.         }

  815.         // apply function composition
  816.         compose(operand, operandOffset, function, result, resultOffset);
  817.     }

  818.     /** Compute integer power of a derivative structure.
  819.      * @param operand array holding the operand
  820.      * @param operandOffset offset of the operand in its array
  821.      * @param n power to apply
  822.      * @param result array where result must be stored (for
  823.      * power the result array <em>cannot</em> be the input
  824.      * array)
  825.      * @param resultOffset offset of the result in its array
  826.      */
  827.     public void pow(final double[] operand, final int operandOffset, final int n,
  828.                     final double[] result, final int resultOffset) {

  829.         if (n == 0) {
  830.             // special case, x^0 = 1 for all x
  831.             result[resultOffset] = 1.0;
  832.             Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), 0);
  833.             return;
  834.         }

  835.         // create the power function value and derivatives
  836.         // [x^n, nx^(n-1), n(n-1)x^(n-2), ... ]
  837.         double[] function = new double[1 + order];

  838.         if (n > 0) {
  839.             // strictly positive power
  840.             final int maxOrder = JdkMath.min(order, n);
  841.             double xk = JdkMath.pow(operand[operandOffset], n - maxOrder);
  842.             for (int i = maxOrder; i > 0; --i) {
  843.                 function[i] = xk;
  844.                 xk *= operand[operandOffset];
  845.             }
  846.             function[0] = xk;
  847.         } else {
  848.             // strictly negative power
  849.             final double inv = 1.0 / operand[operandOffset];
  850.             double xk = JdkMath.pow(inv, -n);
  851.             for (int i = 0; i <= order; ++i) {
  852.                 function[i] = xk;
  853.                 xk *= inv;
  854.             }
  855.         }

  856.         double coefficient = n;
  857.         for (int i = 1; i <= order; ++i) {
  858.             function[i] *= coefficient;
  859.             coefficient *= n - i;
  860.         }

  861.         // apply function composition
  862.         compose(operand, operandOffset, function, result, resultOffset);
  863.     }

  864.     /** Compute power of a derivative structure.
  865.      * @param x array holding the base
  866.      * @param xOffset offset of the base in its array
  867.      * @param y array holding the exponent
  868.      * @param yOffset offset of the exponent in its array
  869.      * @param result array where result must be stored (for
  870.      * power the result array <em>cannot</em> be the input
  871.      * array)
  872.      * @param resultOffset offset of the result in its array
  873.      */
  874.     public void pow(final double[] x, final int xOffset,
  875.                     final double[] y, final int yOffset,
  876.                     final double[] result, final int resultOffset) {
  877.         final double[] logX = new double[getSize()];
  878.         log(x, xOffset, logX, 0);
  879.         final double[] yLogX = new double[getSize()];
  880.         multiply(logX, 0, y, yOffset, yLogX, 0);
  881.         exp(yLogX, 0, result, resultOffset);
  882.     }

  883.     /** Compute n<sup>th</sup> root of a derivative structure.
  884.      * @param operand array holding the operand
  885.      * @param operandOffset offset of the operand in its array
  886.      * @param n order of the root
  887.      * @param result array where result must be stored (for
  888.      * n<sup>th</sup> root the result array <em>cannot</em> be the input
  889.      * array)
  890.      * @param resultOffset offset of the result in its array
  891.      */
  892.     public void rootN(final double[] operand, final int operandOffset, final int n,
  893.                       final double[] result, final int resultOffset) {

  894.         // create the function value and derivatives
  895.         // [x^(1/n), (1/n)x^((1/n)-1), (1-n)/n^2x^((1/n)-2), ... ]
  896.         double[] function = new double[1 + order];
  897.         double xk;
  898.         if (n == 2) {
  899.             function[0] = JdkMath.sqrt(operand[operandOffset]);
  900.             xk          = 0.5 / function[0];
  901.         } else if (n == 3) {
  902.             function[0] = JdkMath.cbrt(operand[operandOffset]);
  903.             xk          = 1.0 / (3.0 * function[0] * function[0]);
  904.         } else {
  905.             function[0] = JdkMath.pow(operand[operandOffset], 1.0 / n);
  906.             xk          = 1.0 / (n * JdkMath.pow(function[0], n - 1));
  907.         }
  908.         final double nReciprocal = 1.0 / n;
  909.         final double xReciprocal = 1.0 / operand[operandOffset];
  910.         for (int i = 1; i <= order; ++i) {
  911.             function[i] = xk;
  912.             xk *= xReciprocal * (nReciprocal - i);
  913.         }

  914.         // apply function composition
  915.         compose(operand, operandOffset, function, result, resultOffset);
  916.     }

  917.     /** Compute exponential of a derivative structure.
  918.      * @param operand array holding the operand
  919.      * @param operandOffset offset of the operand in its array
  920.      * @param result array where result must be stored (for
  921.      * exponential the result array <em>cannot</em> be the input
  922.      * array)
  923.      * @param resultOffset offset of the result in its array
  924.      */
  925.     public void exp(final double[] operand, final int operandOffset,
  926.                     final double[] result, final int resultOffset) {

  927.         // create the function value and derivatives
  928.         double[] function = new double[1 + order];
  929.         Arrays.fill(function, JdkMath.exp(operand[operandOffset]));

  930.         // apply function composition
  931.         compose(operand, operandOffset, function, result, resultOffset);
  932.     }

  933.     /** Compute exp(x) - 1 of a derivative structure.
  934.      * @param operand array holding the operand
  935.      * @param operandOffset offset of the operand in its array
  936.      * @param result array where result must be stored (for
  937.      * exponential the result array <em>cannot</em> be the input
  938.      * array)
  939.      * @param resultOffset offset of the result in its array
  940.      */
  941.     public void expm1(final double[] operand, final int operandOffset,
  942.                       final double[] result, final int resultOffset) {

  943.         // create the function value and derivatives
  944.         double[] function = new double[1 + order];
  945.         function[0] = JdkMath.expm1(operand[operandOffset]);
  946.         Arrays.fill(function, 1, 1 + order, JdkMath.exp(operand[operandOffset]));

  947.         // apply function composition
  948.         compose(operand, operandOffset, function, result, resultOffset);
  949.     }

  950.     /** Compute natural logarithm of a derivative structure.
  951.      * @param operand array holding the operand
  952.      * @param operandOffset offset of the operand in its array
  953.      * @param result array where result must be stored (for
  954.      * logarithm the result array <em>cannot</em> be the input
  955.      * array)
  956.      * @param resultOffset offset of the result in its array
  957.      */
  958.     public void log(final double[] operand, final int operandOffset,
  959.                     final double[] result, final int resultOffset) {

  960.         // create the function value and derivatives
  961.         double[] function = new double[1 + order];
  962.         function[0] = JdkMath.log(operand[operandOffset]);
  963.         if (order > 0) {
  964.             double inv = 1.0 / operand[operandOffset];
  965.             double xk  = inv;
  966.             for (int i = 1; i <= order; ++i) {
  967.                 function[i] = xk;
  968.                 xk *= -i * inv;
  969.             }
  970.         }

  971.         // apply function composition
  972.         compose(operand, operandOffset, function, result, resultOffset);
  973.     }

  974.     /** Computes shifted logarithm of a derivative structure.
  975.      * @param operand array holding the operand
  976.      * @param operandOffset offset of the operand in its array
  977.      * @param result array where result must be stored (for
  978.      * shifted logarithm the result array <em>cannot</em> be the input array)
  979.      * @param resultOffset offset of the result in its array
  980.      */
  981.     public void log1p(final double[] operand, final int operandOffset,
  982.                       final double[] result, final int resultOffset) {

  983.         // create the function value and derivatives
  984.         double[] function = new double[1 + order];
  985.         function[0] = JdkMath.log1p(operand[operandOffset]);
  986.         if (order > 0) {
  987.             double inv = 1.0 / (1.0 + operand[operandOffset]);
  988.             double xk  = inv;
  989.             for (int i = 1; i <= order; ++i) {
  990.                 function[i] = xk;
  991.                 xk *= -i * inv;
  992.             }
  993.         }

  994.         // apply function composition
  995.         compose(operand, operandOffset, function, result, resultOffset);
  996.     }

  997.     /** Computes base 10 logarithm of a derivative structure.
  998.      * @param operand array holding the operand
  999.      * @param operandOffset offset of the operand in its array
  1000.      * @param result array where result must be stored (for
  1001.      * base 10 logarithm the result array <em>cannot</em> be the input array)
  1002.      * @param resultOffset offset of the result in its array
  1003.      */
  1004.     public void log10(final double[] operand, final int operandOffset,
  1005.                       final double[] result, final int resultOffset) {

  1006.         // create the function value and derivatives
  1007.         double[] function = new double[1 + order];
  1008.         function[0] = JdkMath.log10(operand[operandOffset]);
  1009.         if (order > 0) {
  1010.             double inv = 1.0 / operand[operandOffset];
  1011.             double xk  = inv / JdkMath.log(10.0);
  1012.             for (int i = 1; i <= order; ++i) {
  1013.                 function[i] = xk;
  1014.                 xk *= -i * inv;
  1015.             }
  1016.         }

  1017.         // apply function composition
  1018.         compose(operand, operandOffset, function, result, resultOffset);
  1019.     }

  1020.     /** Compute cosine of a derivative structure.
  1021.      * @param operand array holding the operand
  1022.      * @param operandOffset offset of the operand in its array
  1023.      * @param result array where result must be stored (for
  1024.      * cosine the result array <em>cannot</em> be the input
  1025.      * array)
  1026.      * @param resultOffset offset of the result in its array
  1027.      */
  1028.     public void cos(final double[] operand, final int operandOffset,
  1029.                     final double[] result, final int resultOffset) {

  1030.         // create the function value and derivatives
  1031.         double[] function = new double[1 + order];
  1032.         function[0] = JdkMath.cos(operand[operandOffset]);
  1033.         if (order > 0) {
  1034.             function[1] = -JdkMath.sin(operand[operandOffset]);
  1035.             for (int i = 2; i <= order; ++i) {
  1036.                 function[i] = -function[i - 2];
  1037.             }
  1038.         }

  1039.         // apply function composition
  1040.         compose(operand, operandOffset, function, result, resultOffset);
  1041.     }

  1042.     /** Compute sine of a derivative structure.
  1043.      * @param operand array holding the operand
  1044.      * @param operandOffset offset of the operand in its array
  1045.      * @param result array where result must be stored (for
  1046.      * sine the result array <em>cannot</em> be the input
  1047.      * array)
  1048.      * @param resultOffset offset of the result in its array
  1049.      */
  1050.     public void sin(final double[] operand, final int operandOffset,
  1051.                     final double[] result, final int resultOffset) {

  1052.         // create the function value and derivatives
  1053.         double[] function = new double[1 + order];
  1054.         function[0] = JdkMath.sin(operand[operandOffset]);
  1055.         if (order > 0) {
  1056.             function[1] = JdkMath.cos(operand[operandOffset]);
  1057.             for (int i = 2; i <= order; ++i) {
  1058.                 function[i] = -function[i - 2];
  1059.             }
  1060.         }

  1061.         // apply function composition
  1062.         compose(operand, operandOffset, function, result, resultOffset);
  1063.     }

  1064.     /** Compute tangent of a derivative structure.
  1065.      * @param operand array holding the operand
  1066.      * @param operandOffset offset of the operand in its array
  1067.      * @param result array where result must be stored (for
  1068.      * tangent the result array <em>cannot</em> be the input
  1069.      * array)
  1070.      * @param resultOffset offset of the result in its array
  1071.      */
  1072.     public void tan(final double[] operand, final int operandOffset,
  1073.                     final double[] result, final int resultOffset) {

  1074.         // create the function value and derivatives
  1075.         final double[] function = new double[1 + order];
  1076.         final double t = JdkMath.tan(operand[operandOffset]);
  1077.         function[0] = t;

  1078.         if (order > 0) {

  1079.             // the nth order derivative of tan has the form:
  1080.             // dn(tan(x)/dxn = P_n(tan(x))
  1081.             // where P_n(t) is a degree n+1 polynomial with same parity as n+1
  1082.             // P_0(t) = t, P_1(t) = 1 + t^2, P_2(t) = 2 t (1 + t^2) ...
  1083.             // the general recurrence relation for P_n is:
  1084.             // P_n(x) = (1+t^2) P_(n-1)'(t)
  1085.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  1086.             final double[] p = new double[order + 2];
  1087.             p[1] = 1;
  1088.             final double t2 = t * t;
  1089.             for (int n = 1; n <= order; ++n) {

  1090.                 // update and evaluate polynomial P_n(t)
  1091.                 double v = 0;
  1092.                 p[n + 1] = n * p[n];
  1093.                 for (int k = n + 1; k >= 0; k -= 2) {
  1094.                     v = v * t2 + p[k];
  1095.                     if (k > 2) {
  1096.                         p[k - 2] = (k - 1) * p[k - 1] + (k - 3) * p[k - 3];
  1097.                     } else if (k == 2) {
  1098.                         p[0] = p[1];
  1099.                     }
  1100.                 }
  1101.                 if ((n & 0x1) == 0) {
  1102.                     v *= t;
  1103.                 }

  1104.                 function[n] = v;
  1105.             }
  1106.         }

  1107.         // apply function composition
  1108.         compose(operand, operandOffset, function, result, resultOffset);
  1109.     }

  1110.     /** Compute arc cosine of a derivative structure.
  1111.      * @param operand array holding the operand
  1112.      * @param operandOffset offset of the operand in its array
  1113.      * @param result array where result must be stored (for
  1114.      * arc cosine the result array <em>cannot</em> be the input
  1115.      * array)
  1116.      * @param resultOffset offset of the result in its array
  1117.      */
  1118.     public void acos(final double[] operand, final int operandOffset,
  1119.                     final double[] result, final int resultOffset) {

  1120.         // create the function value and derivatives
  1121.         double[] function = new double[1 + order];
  1122.         final double x = operand[operandOffset];
  1123.         function[0] = JdkMath.acos(x);
  1124.         if (order > 0) {
  1125.             // the nth order derivative of acos has the form:
  1126.             // dn(acos(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
  1127.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  1128.             // P_1(x) = -1, P_2(x) = -x, P_3(x) = -2x^2 - 1 ...
  1129.             // the general recurrence relation for P_n is:
  1130.             // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
  1131.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  1132.             final double[] p = new double[order];
  1133.             p[0] = -1;
  1134.             final double x2    = x * x;
  1135.             final double f     = 1.0 / (1 - x2);
  1136.             double coeff = JdkMath.sqrt(f);
  1137.             function[1] = coeff * p[0];
  1138.             for (int n = 2; n <= order; ++n) {

  1139.                 // update and evaluate polynomial P_n(x)
  1140.                 double v = 0;
  1141.                 p[n - 1] = (n - 1) * p[n - 2];
  1142.                 for (int k = n - 1; k >= 0; k -= 2) {
  1143.                     v = v * x2 + p[k];
  1144.                     if (k > 2) {
  1145.                         p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3];
  1146.                     } else if (k == 2) {
  1147.                         p[0] = p[1];
  1148.                     }
  1149.                 }
  1150.                 if ((n & 0x1) == 0) {
  1151.                     v *= x;
  1152.                 }

  1153.                 coeff *= f;
  1154.                 function[n] = coeff * v;
  1155.             }
  1156.         }

  1157.         // apply function composition
  1158.         compose(operand, operandOffset, function, result, resultOffset);
  1159.     }

  1160.     /** Compute arc sine of a derivative structure.
  1161.      * @param operand array holding the operand
  1162.      * @param operandOffset offset of the operand in its array
  1163.      * @param result array where result must be stored (for
  1164.      * arc sine the result array <em>cannot</em> be the input
  1165.      * array)
  1166.      * @param resultOffset offset of the result in its array
  1167.      */
  1168.     public void asin(final double[] operand, final int operandOffset,
  1169.                     final double[] result, final int resultOffset) {

  1170.         // create the function value and derivatives
  1171.         double[] function = new double[1 + order];
  1172.         final double x = operand[operandOffset];
  1173.         function[0] = JdkMath.asin(x);
  1174.         if (order > 0) {
  1175.             // the nth order derivative of asin has the form:
  1176.             // dn(asin(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
  1177.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  1178.             // P_1(x) = 1, P_2(x) = x, P_3(x) = 2x^2 + 1 ...
  1179.             // the general recurrence relation for P_n is:
  1180.             // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
  1181.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  1182.             final double[] p = new double[order];
  1183.             p[0] = 1;
  1184.             final double x2    = x * x;
  1185.             final double f     = 1.0 / (1 - x2);
  1186.             double coeff = JdkMath.sqrt(f);
  1187.             function[1] = coeff * p[0];
  1188.             for (int n = 2; n <= order; ++n) {

  1189.                 // update and evaluate polynomial P_n(x)
  1190.                 double v = 0;
  1191.                 p[n - 1] = (n - 1) * p[n - 2];
  1192.                 for (int k = n - 1; k >= 0; k -= 2) {
  1193.                     v = v * x2 + p[k];
  1194.                     if (k > 2) {
  1195.                         p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3];
  1196.                     } else if (k == 2) {
  1197.                         p[0] = p[1];
  1198.                     }
  1199.                 }
  1200.                 if ((n & 0x1) == 0) {
  1201.                     v *= x;
  1202.                 }

  1203.                 coeff *= f;
  1204.                 function[n] = coeff * v;
  1205.             }
  1206.         }

  1207.         // apply function composition
  1208.         compose(operand, operandOffset, function, result, resultOffset);
  1209.     }

  1210.     /** Compute arc tangent of a derivative structure.
  1211.      * @param operand array holding the operand
  1212.      * @param operandOffset offset of the operand in its array
  1213.      * @param result array where result must be stored (for
  1214.      * arc tangent the result array <em>cannot</em> be the input
  1215.      * array)
  1216.      * @param resultOffset offset of the result in its array
  1217.      */
  1218.     public void atan(final double[] operand, final int operandOffset,
  1219.                      final double[] result, final int resultOffset) {

  1220.         // create the function value and derivatives
  1221.         double[] function = new double[1 + order];
  1222.         final double x = operand[operandOffset];
  1223.         function[0] = JdkMath.atan(x);
  1224.         if (order > 0) {
  1225.             // the nth order derivative of atan has the form:
  1226.             // dn(atan(x)/dxn = Q_n(x) / (1 + x^2)^n
  1227.             // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
  1228.             // Q_1(x) = 1, Q_2(x) = -2x, Q_3(x) = 6x^2 - 2 ...
  1229.             // the general recurrence relation for Q_n is:
  1230.             // Q_n(x) = (1+x^2) Q_(n-1)'(x) - 2(n-1) x Q_(n-1)(x)
  1231.             // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
  1232.             final double[] q = new double[order];
  1233.             q[0] = 1;
  1234.             final double x2    = x * x;
  1235.             final double f     = 1.0 / (1 + x2);
  1236.             double coeff = f;
  1237.             function[1] = coeff * q[0];
  1238.             for (int n = 2; n <= order; ++n) {

  1239.                 // update and evaluate polynomial Q_n(x)
  1240.                 double v = 0;
  1241.                 q[n - 1] = -n * q[n - 2];
  1242.                 for (int k = n - 1; k >= 0; k -= 2) {
  1243.                     v = v * x2 + q[k];
  1244.                     if (k > 2) {
  1245.                         q[k - 2] = (k - 1) * q[k - 1] + (k - 1 - 2 * n) * q[k - 3];
  1246.                     } else if (k == 2) {
  1247.                         q[0] = q[1];
  1248.                     }
  1249.                 }
  1250.                 if ((n & 0x1) == 0) {
  1251.                     v *= x;
  1252.                 }

  1253.                 coeff *= f;
  1254.                 function[n] = coeff * v;
  1255.             }
  1256.         }

  1257.         // apply function composition
  1258.         compose(operand, operandOffset, function, result, resultOffset);
  1259.     }

  1260.     /** Compute two arguments arc tangent of a derivative structure.
  1261.      * @param y array holding the first operand
  1262.      * @param yOffset offset of the first operand in its array
  1263.      * @param x array holding the second operand
  1264.      * @param xOffset offset of the second operand in its array
  1265.      * @param result array where result must be stored (for
  1266.      * two arguments arc tangent the result array <em>cannot</em>
  1267.      * be the input array)
  1268.      * @param resultOffset offset of the result in its array
  1269.      */
  1270.     public void atan2(final double[] y, final int yOffset,
  1271.                       final double[] x, final int xOffset,
  1272.                       final double[] result, final int resultOffset) {

  1273.         // compute r = sqrt(x^2+y^2)
  1274.         double[] tmp1 = new double[getSize()];
  1275.         multiply(x, xOffset, x, xOffset, tmp1, 0);      // x^2
  1276.         double[] tmp2 = new double[getSize()];
  1277.         multiply(y, yOffset, y, yOffset, tmp2, 0);      // y^2
  1278.         add(tmp1, 0, tmp2, 0, tmp2, 0);                 // x^2 + y^2
  1279.         rootN(tmp2, 0, 2, tmp1, 0);                     // r = sqrt(x^2 + y^2)

  1280.         if (x[xOffset] >= 0) {

  1281.             // compute atan2(y, x) = 2 atan(y / (r + x))
  1282.             add(tmp1, 0, x, xOffset, tmp2, 0);          // r + x
  1283.             divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r + x)
  1284.             atan(tmp1, 0, tmp2, 0);                     // atan(y / (r + x))
  1285.             for (int i = 0; i < tmp2.length; ++i) {
  1286.                 result[resultOffset + i] = 2 * tmp2[i]; // 2 * atan(y / (r + x))
  1287.             }
  1288.         } else {

  1289.             // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x))
  1290.             subtract(tmp1, 0, x, xOffset, tmp2, 0);     // r - x
  1291.             divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r - x)
  1292.             atan(tmp1, 0, tmp2, 0);                     // atan(y / (r - x))
  1293.             result[resultOffset] =
  1294.                     ((tmp2[0] <= 0) ? -JdkMath.PI : JdkMath.PI) - 2 * tmp2[0]; // +/-pi - 2 * atan(y / (r - x))
  1295.             for (int i = 1; i < tmp2.length; ++i) {
  1296.                 result[resultOffset + i] = -2 * tmp2[i]; // +/-pi - 2 * atan(y / (r - x))
  1297.             }
  1298.         }

  1299.         // fix value to take special cases (+0/+0, +0/-0, -0/+0, -0/-0, +/-infinity) correctly
  1300.         result[resultOffset] = JdkMath.atan2(y[yOffset], x[xOffset]);
  1301.     }

  1302.     /** Compute hyperbolic cosine of a derivative structure.
  1303.      * @param operand array holding the operand
  1304.      * @param operandOffset offset of the operand in its array
  1305.      * @param result array where result must be stored (for
  1306.      * hyperbolic cosine the result array <em>cannot</em> be the input
  1307.      * array)
  1308.      * @param resultOffset offset of the result in its array
  1309.      */
  1310.     public void cosh(final double[] operand, final int operandOffset,
  1311.                      final double[] result, final int resultOffset) {

  1312.         // create the function value and derivatives
  1313.         double[] function = new double[1 + order];
  1314.         function[0] = JdkMath.cosh(operand[operandOffset]);
  1315.         if (order > 0) {
  1316.             function[1] = JdkMath.sinh(operand[operandOffset]);
  1317.             for (int i = 2; i <= order; ++i) {
  1318.                 function[i] = function[i - 2];
  1319.             }
  1320.         }

  1321.         // apply function composition
  1322.         compose(operand, operandOffset, function, result, resultOffset);
  1323.     }

  1324.     /** Compute hyperbolic sine of a derivative structure.
  1325.      * @param operand array holding the operand
  1326.      * @param operandOffset offset of the operand in its array
  1327.      * @param result array where result must be stored (for
  1328.      * hyperbolic sine the result array <em>cannot</em> be the input
  1329.      * array)
  1330.      * @param resultOffset offset of the result in its array
  1331.      */
  1332.     public void sinh(final double[] operand, final int operandOffset,
  1333.                      final double[] result, final int resultOffset) {

  1334.         // create the function value and derivatives
  1335.         double[] function = new double[1 + order];
  1336.         function[0] = JdkMath.sinh(operand[operandOffset]);
  1337.         if (order > 0) {
  1338.             function[1] = JdkMath.cosh(operand[operandOffset]);
  1339.             for (int i = 2; i <= order; ++i) {
  1340.                 function[i] = function[i - 2];
  1341.             }
  1342.         }

  1343.         // apply function composition
  1344.         compose(operand, operandOffset, function, result, resultOffset);
  1345.     }

  1346.     /** Compute hyperbolic tangent of a derivative structure.
  1347.      * @param operand array holding the operand
  1348.      * @param operandOffset offset of the operand in its array
  1349.      * @param result array where result must be stored (for
  1350.      * hyperbolic tangent the result array <em>cannot</em> be the input
  1351.      * array)
  1352.      * @param resultOffset offset of the result in its array
  1353.      */
  1354.     public void tanh(final double[] operand, final int operandOffset,
  1355.                      final double[] result, final int resultOffset) {

  1356.         // create the function value and derivatives
  1357.         final double[] function = new double[1 + order];
  1358.         final double t = JdkMath.tanh(operand[operandOffset]);
  1359.         function[0] = t;

  1360.         if (order > 0) {

  1361.             // the nth order derivative of tanh has the form:
  1362.             // dn(tanh(x)/dxn = P_n(tanh(x))
  1363.             // where P_n(t) is a degree n+1 polynomial with same parity as n+1
  1364.             // P_0(t) = t, P_1(t) = 1 - t^2, P_2(t) = -2 t (1 - t^2) ...
  1365.             // the general recurrence relation for P_n is:
  1366.             // P_n(x) = (1-t^2) P_(n-1)'(t)
  1367.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  1368.             final double[] p = new double[order + 2];
  1369.             p[1] = 1;
  1370.             final double t2 = t * t;
  1371.             for (int n = 1; n <= order; ++n) {

  1372.                 // update and evaluate polynomial P_n(t)
  1373.                 double v = 0;
  1374.                 p[n + 1] = -n * p[n];
  1375.                 for (int k = n + 1; k >= 0; k -= 2) {
  1376.                     v = v * t2 + p[k];
  1377.                     if (k > 2) {
  1378.                         p[k - 2] = (k - 1) * p[k - 1] - (k - 3) * p[k - 3];
  1379.                     } else if (k == 2) {
  1380.                         p[0] = p[1];
  1381.                     }
  1382.                 }
  1383.                 if ((n & 0x1) == 0) {
  1384.                     v *= t;
  1385.                 }

  1386.                 function[n] = v;
  1387.             }
  1388.         }

  1389.         // apply function composition
  1390.         compose(operand, operandOffset, function, result, resultOffset);
  1391.     }

  1392.     /** Compute inverse hyperbolic cosine of a derivative structure.
  1393.      * @param operand array holding the operand
  1394.      * @param operandOffset offset of the operand in its array
  1395.      * @param result array where result must be stored (for
  1396.      * inverse hyperbolic cosine the result array <em>cannot</em> be the input
  1397.      * array)
  1398.      * @param resultOffset offset of the result in its array
  1399.      */
  1400.     public void acosh(final double[] operand, final int operandOffset,
  1401.                      final double[] result, final int resultOffset) {

  1402.         // create the function value and derivatives
  1403.         double[] function = new double[1 + order];
  1404.         final double x = operand[operandOffset];
  1405.         function[0] = JdkMath.acosh(x);
  1406.         if (order > 0) {
  1407.             // the nth order derivative of acosh has the form:
  1408.             // dn(acosh(x)/dxn = P_n(x) / [x^2 - 1]^((2n-1)/2)
  1409.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  1410.             // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 + 1 ...
  1411.             // the general recurrence relation for P_n is:
  1412.             // P_n(x) = (x^2-1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
  1413.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  1414.             final double[] p = new double[order];
  1415.             p[0] = 1;
  1416.             final double x2  = x * x;
  1417.             final double f   = 1.0 / (x2 - 1);
  1418.             double coeff = JdkMath.sqrt(f);
  1419.             function[1] = coeff * p[0];
  1420.             for (int n = 2; n <= order; ++n) {

  1421.                 // update and evaluate polynomial P_n(x)
  1422.                 double v = 0;
  1423.                 p[n - 1] = (1 - n) * p[n - 2];
  1424.                 for (int k = n - 1; k >= 0; k -= 2) {
  1425.                     v = v * x2 + p[k];
  1426.                     if (k > 2) {
  1427.                         p[k - 2] = (1 - k) * p[k - 1] + (k - 2 * n) * p[k - 3];
  1428.                     } else if (k == 2) {
  1429.                         p[0] = -p[1];
  1430.                     }
  1431.                 }
  1432.                 if ((n & 0x1) == 0) {
  1433.                     v *= x;
  1434.                 }

  1435.                 coeff *= f;
  1436.                 function[n] = coeff * v;
  1437.             }
  1438.         }

  1439.         // apply function composition
  1440.         compose(operand, operandOffset, function, result, resultOffset);
  1441.     }

  1442.     /** Compute inverse hyperbolic sine of a derivative structure.
  1443.      * @param operand array holding the operand
  1444.      * @param operandOffset offset of the operand in its array
  1445.      * @param result array where result must be stored (for
  1446.      * inverse hyperbolic sine the result array <em>cannot</em> be the input
  1447.      * array)
  1448.      * @param resultOffset offset of the result in its array
  1449.      */
  1450.     public void asinh(final double[] operand, final int operandOffset,
  1451.                      final double[] result, final int resultOffset) {

  1452.         // create the function value and derivatives
  1453.         double[] function = new double[1 + order];
  1454.         final double x = operand[operandOffset];
  1455.         function[0] = JdkMath.asinh(x);
  1456.         if (order > 0) {
  1457.             // the nth order derivative of asinh has the form:
  1458.             // dn(asinh(x)/dxn = P_n(x) / [x^2 + 1]^((2n-1)/2)
  1459.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  1460.             // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 - 1 ...
  1461.             // the general recurrence relation for P_n is:
  1462.             // P_n(x) = (x^2+1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
  1463.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  1464.             final double[] p = new double[order];
  1465.             p[0] = 1;
  1466.             final double x2    = x * x;
  1467.             final double f     = 1.0 / (1 + x2);
  1468.             double coeff = JdkMath.sqrt(f);
  1469.             function[1] = coeff * p[0];
  1470.             for (int n = 2; n <= order; ++n) {

  1471.                 // update and evaluate polynomial P_n(x)
  1472.                 double v = 0;
  1473.                 p[n - 1] = (1 - n) * p[n - 2];
  1474.                 for (int k = n - 1; k >= 0; k -= 2) {
  1475.                     v = v * x2 + p[k];
  1476.                     if (k > 2) {
  1477.                         p[k - 2] = (k - 1) * p[k - 1] + (k - 2 * n) * p[k - 3];
  1478.                     } else if (k == 2) {
  1479.                         p[0] = p[1];
  1480.                     }
  1481.                 }
  1482.                 if ((n & 0x1) == 0) {
  1483.                     v *= x;
  1484.                 }

  1485.                 coeff *= f;
  1486.                 function[n] = coeff * v;
  1487.             }
  1488.         }

  1489.         // apply function composition
  1490.         compose(operand, operandOffset, function, result, resultOffset);
  1491.     }

  1492.     /** Compute inverse hyperbolic tangent of a derivative structure.
  1493.      * @param operand array holding the operand
  1494.      * @param operandOffset offset of the operand in its array
  1495.      * @param result array where result must be stored (for
  1496.      * inverse hyperbolic tangent the result array <em>cannot</em> be the input
  1497.      * array)
  1498.      * @param resultOffset offset of the result in its array
  1499.      */
  1500.     public void atanh(final double[] operand, final int operandOffset,
  1501.                       final double[] result, final int resultOffset) {

  1502.         // create the function value and derivatives
  1503.         double[] function = new double[1 + order];
  1504.         final double x = operand[operandOffset];
  1505.         function[0] = JdkMath.atanh(x);
  1506.         if (order > 0) {
  1507.             // the nth order derivative of atanh has the form:
  1508.             // dn(atanh(x)/dxn = Q_n(x) / (1 - x^2)^n
  1509.             // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
  1510.             // Q_1(x) = 1, Q_2(x) = 2x, Q_3(x) = 6x^2 + 2 ...
  1511.             // the general recurrence relation for Q_n is:
  1512.             // Q_n(x) = (1-x^2) Q_(n-1)'(x) + 2(n-1) x Q_(n-1)(x)
  1513.             // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
  1514.             final double[] q = new double[order];
  1515.             q[0] = 1;
  1516.             final double x2 = x * x;
  1517.             final double f  = 1.0 / (1 - x2);
  1518.             double coeff = f;
  1519.             function[1] = coeff * q[0];
  1520.             for (int n = 2; n <= order; ++n) {

  1521.                 // update and evaluate polynomial Q_n(x)
  1522.                 double v = 0;
  1523.                 q[n - 1] = n * q[n - 2];
  1524.                 for (int k = n - 1; k >= 0; k -= 2) {
  1525.                     v = v * x2 + q[k];
  1526.                     if (k > 2) {
  1527.                         q[k - 2] = (k - 1) * q[k - 1] + (2 * n - k + 1) * q[k - 3];
  1528.                     } else if (k == 2) {
  1529.                         q[0] = q[1];
  1530.                     }
  1531.                 }
  1532.                 if ((n & 0x1) == 0) {
  1533.                     v *= x;
  1534.                 }

  1535.                 coeff *= f;
  1536.                 function[n] = coeff * v;
  1537.             }
  1538.         }

  1539.         // apply function composition
  1540.         compose(operand, operandOffset, function, result, resultOffset);
  1541.     }

  1542.     /** Compute composition of a derivative structure by a function.
  1543.      * @param operand array holding the operand
  1544.      * @param operandOffset offset of the operand in its array
  1545.      * @param f array of value and derivatives of the function at
  1546.      * the current point (i.e. at {@code operand[operandOffset]}).
  1547.      * @param result array where result must be stored (for
  1548.      * composition the result array <em>cannot</em> be the input
  1549.      * array)
  1550.      * @param resultOffset offset of the result in its array
  1551.      */
  1552.     public void compose(final double[] operand, final int operandOffset, final double[] f,
  1553.                         final double[] result, final int resultOffset) {
  1554.         for (int i = 0; i < compIndirection.length; ++i) {
  1555.             final int[][] mappingI = compIndirection[i];
  1556.             double r = 0;
  1557.             for (int j = 0; j < mappingI.length; ++j) {
  1558.                 final int[] mappingIJ = mappingI[j];
  1559.                 double product = mappingIJ[0] * f[mappingIJ[1]];
  1560.                 for (int k = 2; k < mappingIJ.length; ++k) {
  1561.                     product *= operand[operandOffset + mappingIJ[k]];
  1562.                 }
  1563.                 r += product;
  1564.             }
  1565.             result[resultOffset + i] = r;
  1566.         }
  1567.     }

  1568.     /** Evaluate Taylor expansion of a derivative structure.
  1569.      * @param ds array holding the derivative structure
  1570.      * @param dsOffset offset of the derivative structure in its array
  1571.      * @param delta parameters offsets (&Delta;x, &Delta;y, ...)
  1572.      * @return value of the Taylor expansion at x + &Delta;x, y + &Delta;y, ...
  1573.      * @throws MathArithmeticException if factorials becomes too large
  1574.      */
  1575.     public double taylor(final double[] ds, final int dsOffset, final double ... delta)
  1576.        throws MathArithmeticException {
  1577.         double value = 0;
  1578.         for (int i = getSize() - 1; i >= 0; --i) {
  1579.             final int[] orders = getPartialDerivativeOrders(i);
  1580.             double term = ds[dsOffset + i];
  1581.             for (int k = 0; k < orders.length; ++k) {
  1582.                 if (orders[k] > 0) {
  1583.                     try {
  1584.                         term *= JdkMath.pow(delta[k], orders[k]) / Factorial.doubleValue(orders[k]);
  1585.                     } catch (NotPositiveException e) {
  1586.                         // this cannot happen
  1587.                         throw new MathInternalError(e);
  1588.                     }
  1589.                 }
  1590.             }
  1591.             value += term;
  1592.         }
  1593.         return value;
  1594.     }

  1595.     /** Check rules set compatibility.
  1596.      * @param compiler other compiler to check against instance
  1597.      * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
  1598.      */
  1599.     public void checkCompatibility(final DSCompiler compiler)
  1600.             throws DimensionMismatchException {
  1601.         if (parameters != compiler.parameters) {
  1602.             throw new DimensionMismatchException(parameters, compiler.parameters);
  1603.         }
  1604.         if (order != compiler.order) {
  1605.             throw new DimensionMismatchException(order, compiler.order);
  1606.         }
  1607.     }
  1608. }