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.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.analysis.differentiation;

  22. import java.lang.reflect.Array;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.List;
  26. import java.util.concurrent.atomic.AtomicReference;

  27. import org.hipparchus.CalculusFieldElement;
  28. import org.hipparchus.Field;
  29. import org.hipparchus.exception.LocalizedCoreFormats;
  30. import org.hipparchus.exception.MathIllegalArgumentException;
  31. import org.hipparchus.exception.MathRuntimeException;
  32. import org.hipparchus.util.CombinatoricsUtils;
  33. import org.hipparchus.util.FastMath;
  34. import org.hipparchus.util.FieldSinCos;
  35. import org.hipparchus.util.FieldSinhCosh;
  36. import org.hipparchus.util.MathArrays;
  37. import org.hipparchus.util.MathUtils;
  38. import org.hipparchus.util.SinCos;
  39. import org.hipparchus.util.SinhCosh;

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

  133.     /** Array of all compilers created so far. */
  134.     private static AtomicReference<DSCompiler[][]> compilers = new AtomicReference<>(null);

  135.     /** Number of free parameters. */
  136.     private final int parameters;

  137.     /** Derivation order. */
  138.     private final int order;

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

  141.     /** Orders array for partial derivatives. */
  142.     private final int[][] derivativesOrders;

  143.     /** Sum of orders array for partial derivatives. */
  144.     private final int[] derivativesOrdersSum;

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

  147.     /** Indirection arrays for multiplication. */
  148.     private final MultiplicationMapper[][] multIndirection;

  149.     /** Indirection arrays for univariate function composition. */
  150.     private final UnivariateCompositionMapper[][] compIndirection;

  151.     /** Indirection arrays for multivariate function rebasing. */
  152.     private final List<MultivariateCompositionMapper[][]> rebaseIndirection;

  153.     /** Private constructor, reserved for the factory method {@link #getCompiler(int, int)}.
  154.      * @param parameters number of free parameters
  155.      * @param order derivation order
  156.      * @param valueCompiler compiler for the value part
  157.      * @param derivativeCompiler compiler for the derivative part
  158.      * @throws MathIllegalArgumentException if order is too large
  159.      */
  160.     private DSCompiler(final int parameters, final int order,
  161.                        final DSCompiler valueCompiler, final DSCompiler derivativeCompiler)
  162.         throws MathIllegalArgumentException {

  163.         this.parameters           = parameters;
  164.         this.order                = order;
  165.         this.sizes                = compileSizes(parameters, order, valueCompiler);
  166.         this.derivativesOrders    = compileDerivativesOrders(parameters, order,
  167.                                                              valueCompiler, derivativeCompiler);
  168.         this.derivativesOrdersSum = compileDerivativesOrdersSum(derivativesOrders);
  169.         this.lowerIndirection     = compileLowerIndirection(parameters, order,
  170.                                                             valueCompiler, derivativeCompiler);
  171.         this.multIndirection      = compileMultiplicationIndirection(parameters, order,
  172.                                                                      valueCompiler, derivativeCompiler,
  173.                                                                      lowerIndirection);
  174.         this.compIndirection      = compileCompositionIndirection(parameters, order,
  175.                                                                   valueCompiler, derivativeCompiler,
  176.                                                                   sizes, derivativesOrders);

  177.         this.rebaseIndirection = new ArrayList<>();
  178.     }

  179.     /** Get the compiler for number of free parameters and order.
  180.      * @param parameters number of free parameters
  181.      * @param order derivation order
  182.      * @return cached rules set
  183.      * @throws MathIllegalArgumentException if order is too large
  184.      */
  185.     public static DSCompiler getCompiler(int parameters, int order)
  186.         throws MathIllegalArgumentException {

  187.         // get the cached compilers
  188.         final DSCompiler[][] cache = compilers.get();
  189.         if (cache != null && cache.length > parameters &&
  190.             cache[parameters].length > order && cache[parameters][order] != null) {
  191.             // the compiler has already been created
  192.             return cache[parameters][order];
  193.         }

  194.         // we need to create more compilers
  195.         final int maxParameters = FastMath.max(parameters, cache == null ? 0 : cache.length);
  196.         final int maxOrder      = FastMath.max(order,     cache == null ? 0 : cache[0].length);
  197.         final DSCompiler[][] newCache = new DSCompiler[maxParameters + 1][maxOrder + 1];

  198.         if (cache != null) {
  199.             // preserve the already created compilers
  200.             for (int i = 0; i < cache.length; ++i) {
  201.                 System.arraycopy(cache[i], 0, newCache[i], 0, cache[i].length);
  202.             }
  203.         }

  204.         // create the array in increasing diagonal order
  205.         for (int diag = 0; diag <= parameters + order; ++diag) {
  206.             for (int o = FastMath.max(0, diag - parameters); o <= FastMath.min(order, diag); ++o) {
  207.                 final int p = diag - o;
  208.                 if (newCache[p][o] == null) {
  209.                     final DSCompiler valueCompiler      = (p == 0) ? null : newCache[p - 1][o];
  210.                     final DSCompiler derivativeCompiler = (o == 0) ? null : newCache[p][o - 1];
  211.                     newCache[p][o] = new DSCompiler(p, o, valueCompiler, derivativeCompiler);
  212.                 }
  213.             }
  214.         }

  215.         // atomically reset the cached compilers array
  216.         compilers.compareAndSet(cache, newCache);

  217.         return newCache[parameters][order];

  218.     }

  219.     /** Compile the sizes array.
  220.      * @param parameters number of free parameters
  221.      * @param order derivation order
  222.      * @param valueCompiler compiler for the value part
  223.      * @return sizes array
  224.      */
  225.     private static int[][] compileSizes(final int parameters, final int order,
  226.                                         final DSCompiler valueCompiler) {

  227.         final int[][] sizes = new int[parameters + 1][order + 1];
  228.         if (parameters == 0) {
  229.             Arrays.fill(sizes[0], 1);
  230.         } else {
  231.             System.arraycopy(valueCompiler.sizes, 0, sizes, 0, parameters);
  232.             sizes[parameters][0] = 1;
  233.             for (int i = 0; i < order; ++i) {
  234.                 sizes[parameters][i + 1] = sizes[parameters][i] + sizes[parameters - 1][i + 1];
  235.             }
  236.         }

  237.         return sizes;

  238.     }

  239.     /** Compile the derivatives orders array.
  240.      * @param parameters number of free parameters
  241.      * @param order derivation order
  242.      * @param valueCompiler compiler for the value part
  243.      * @param derivativeCompiler compiler for the derivative part
  244.      * @return derivatives orders array
  245.      */
  246.     private static int[][] compileDerivativesOrders(final int parameters, final int order,
  247.                                                     final DSCompiler valueCompiler,
  248.                                                     final DSCompiler derivativeCompiler) {

  249.         if (parameters == 0 || order == 0) {
  250.             return new int[1][parameters];
  251.         }

  252.         final int vSize = valueCompiler.derivativesOrders.length;
  253.         final int dSize = derivativeCompiler.derivativesOrders.length;
  254.         final int[][] derivativesOrders = new int[vSize + dSize][parameters];

  255.         // set up the indices for the value part
  256.         for (int i = 0; i < vSize; ++i) {
  257.             // copy the first indices, the last one remaining set to 0
  258.             System.arraycopy(valueCompiler.derivativesOrders[i], 0,
  259.                              derivativesOrders[i], 0,
  260.                              parameters - 1);
  261.         }

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

  264.             // copy the indices
  265.             System.arraycopy(derivativeCompiler.derivativesOrders[i], 0,
  266.                              derivativesOrders[vSize + i], 0,
  267.                              parameters);

  268.             // increment the derivation order for the last parameter
  269.             derivativesOrders[vSize + i][parameters - 1]++;

  270.         }

  271.         return derivativesOrders;

  272.     }

  273.     /** Compile the sum of orders array for partial derivatives.
  274.      * @param derivativesOrders orders array for partial derivatives
  275.      * @return sum of orders array for partial derivatives
  276.      */
  277.     private static int[] compileDerivativesOrdersSum(final int[][] derivativesOrders) {

  278.         final int[] derivativesOrdersSum = new int[derivativesOrders.length];

  279.         // locate the partial derivatives at order 1
  280.         for (int i = 0; i < derivativesOrdersSum.length; ++i) {
  281.             for (final int o : derivativesOrders[i]) {
  282.                 derivativesOrdersSum[i] += o;
  283.             }
  284.         }

  285.         return derivativesOrdersSum;

  286.     }

  287.     /** Compile the lower derivatives indirection array.
  288.      * <p>
  289.      * This indirection array contains the indices of all elements
  290.      * except derivatives for last derivation order.
  291.      * </p>
  292.      * @param parameters number of free parameters
  293.      * @param order derivation order
  294.      * @param valueCompiler compiler for the value part
  295.      * @param derivativeCompiler compiler for the derivative part
  296.      * @return lower derivatives indirection array
  297.      */
  298.     private static int[] compileLowerIndirection(final int parameters, final int order,
  299.                                                  final DSCompiler valueCompiler,
  300.                                                  final DSCompiler derivativeCompiler) {

  301.         if (parameters == 0 || order <= 1) {
  302.             return new int[] { 0 };
  303.         }

  304.         // this is an implementation of definition 6 in Dan Kalman's paper.
  305.         final int vSize = valueCompiler.lowerIndirection.length;
  306.         final int dSize = derivativeCompiler.lowerIndirection.length;
  307.         final int[] lowerIndirection = new int[vSize + dSize];
  308.         System.arraycopy(valueCompiler.lowerIndirection, 0, lowerIndirection, 0, vSize);
  309.         for (int i = 0; i < dSize; ++i) {
  310.             lowerIndirection[vSize + i] = valueCompiler.getSize() + derivativeCompiler.lowerIndirection[i];
  311.         }

  312.         return lowerIndirection;

  313.     }

  314.     /** Compile the multiplication indirection array.
  315.      * <p>
  316.      * This indirection array contains the indices of all pairs of elements
  317.      * involved when computing a multiplication. This allows a straightforward
  318.      * loop-based multiplication (see {@link #multiply(double[], int, double[], int, double[], int)}).
  319.      * </p>
  320.      * @param parameters number of free parameters
  321.      * @param order derivation order
  322.      * @param valueCompiler compiler for the value part
  323.      * @param derivativeCompiler compiler for the derivative part
  324.      * @param lowerIndirection lower derivatives indirection array
  325.      * @return multiplication indirection array
  326.      */
  327.     private static MultiplicationMapper[][] compileMultiplicationIndirection(final int parameters, final int order,
  328.                                                                              final DSCompiler valueCompiler,
  329.                                                                              final DSCompiler derivativeCompiler,
  330.                                                                              final int[] lowerIndirection) {

  331.         if (parameters == 0 || order == 0) {
  332.             return new MultiplicationMapper[][] { { new MultiplicationMapper(1, 0, 0) } };
  333.         }

  334.         // this is an implementation of definition 3 in Dan Kalman's paper.
  335.         final int vSize = valueCompiler.multIndirection.length;
  336.         final int dSize = derivativeCompiler.multIndirection.length;
  337.         final MultiplicationMapper[][] multIndirection = new MultiplicationMapper[vSize + dSize][];

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

  339.         for (int i = 0; i < dSize; ++i) {
  340.             final MultiplicationMapper[] dRow = derivativeCompiler.multIndirection[i];
  341.             final List<MultiplicationMapper> row = new ArrayList<>(dRow.length * 2);
  342.             for (MultiplicationMapper dj : dRow) {
  343.                 row.add(new MultiplicationMapper(dj.getCoeff(), lowerIndirection[dj.lhsIndex], vSize + dj.rhsIndex));
  344.                 row.add(new MultiplicationMapper(dj.getCoeff(), vSize + dj.lhsIndex, lowerIndirection[dj.rhsIndex]));
  345.             }
  346.             multIndirection[vSize + i] = combineSimilarTerms(row);
  347.         }

  348.         return multIndirection;

  349.     }

  350.     /** Compile the function composition indirection array.
  351.      * <p>
  352.      * This indirection array contains the indices of all sets of elements
  353.      * involved when computing a composition. This allows a straightforward
  354.      * loop-based composition (see {@link #compose(double[], int, double[], double[], int)}).
  355.      * </p>
  356.      * @param parameters number of free parameters
  357.      * @param order derivation order
  358.      * @param valueCompiler compiler for the value part
  359.      * @param derivativeCompiler compiler for the derivative part
  360.      * @param sizes sizes array
  361.      * @param derivativesIndirection derivatives indirection array
  362.      * @return multiplication indirection array
  363.      * @throws MathIllegalArgumentException if order is too large
  364.      */
  365.     private static UnivariateCompositionMapper[][] compileCompositionIndirection(final int parameters, final int order,
  366.                                                                                  final DSCompiler valueCompiler,
  367.                                                                                  final DSCompiler derivativeCompiler,
  368.                                                                                  final int[][] sizes,
  369.                                                                                  final int[][] derivativesIndirection)
  370.        throws MathIllegalArgumentException {

  371.         if (parameters == 0 || order == 0) {
  372.             return new UnivariateCompositionMapper[][] { { new UnivariateCompositionMapper(1, 0, new int[0]) } };
  373.         }

  374.         final int vSize = valueCompiler.compIndirection.length;
  375.         final int dSize = derivativeCompiler.compIndirection.length;
  376.         final UnivariateCompositionMapper[][] compIndirection = new UnivariateCompositionMapper[vSize + dSize][];

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

  379.         // the composition rules for the derivative part are deduced by
  380.         // differentiating the rules from the underlying compiler once
  381.         // with respect to the parameter this compiler handles and the
  382.         // underlying one did not handle
  383.         for (int i = 0; i < dSize; ++i) {
  384.             List<UnivariateCompositionMapper> row = new ArrayList<>();
  385.             for (UnivariateCompositionMapper term : derivativeCompiler.compIndirection[i]) {

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

  387.                 // derive the first factor in the term: f_k with respect to new parameter
  388.                 UnivariateCompositionMapper derivedTermF = new UnivariateCompositionMapper(term.getCoeff(),  // p
  389.                                                                                            term.fIndex + 1,  // f_(k+1)
  390.                                                                                            new int[term.dsIndices.length + 1]);
  391.                 int[] orders = new int[parameters];
  392.                 orders[parameters - 1] = 1;
  393.                 derivedTermF.dsIndices[term.dsIndices.length] = getPartialDerivativeIndex(parameters, order, sizes, orders);  // g_1
  394.                 for (int j = 0; j < term.dsIndices.length; ++j) {
  395.                     // convert the indices as the mapping for the current order
  396.                     // is different from the mapping with one less order
  397.                     derivedTermF.dsIndices[j] = convertIndex(term.dsIndices[j], parameters,
  398.                                                            derivativeCompiler.derivativesOrders,
  399.                                                            parameters, order, sizes);
  400.                 }
  401.                 derivedTermF.sort();
  402.                 row.add(derivedTermF);

  403.                 // derive the various g_l
  404.                 for (int l = 0; l < term.dsIndices.length; ++l) {
  405.                     UnivariateCompositionMapper derivedTermG = new UnivariateCompositionMapper(term.getCoeff(),
  406.                                                                                                term.fIndex,
  407.                                                                                                new int[term.dsIndices.length]);
  408.                     for (int j = 0; j < term.dsIndices.length; ++j) {
  409.                         // convert the indices as the mapping for the current order
  410.                         // is different from the mapping with one less order
  411.                         derivedTermG.dsIndices[j] = convertIndex(term.dsIndices[j], parameters,
  412.                                                                derivativeCompiler.derivativesOrders,
  413.                                                                parameters, order, sizes);
  414.                         if (j == l) {
  415.                             // derive this term
  416.                             System.arraycopy(derivativesIndirection[derivedTermG.dsIndices[j]], 0, orders, 0, parameters);
  417.                             orders[parameters - 1]++;
  418.                             derivedTermG.dsIndices[j] = getPartialDerivativeIndex(parameters, order, sizes, orders);
  419.                         }
  420.                     }
  421.                     derivedTermG.sort();
  422.                     row.add(derivedTermG);
  423.                 }

  424.             }

  425.             // combine terms with similar derivation orders
  426.             compIndirection[vSize + i] = combineSimilarTerms(row);

  427.         }

  428.         return compIndirection;

  429.     }

  430.     /** Get rebaser, creating it if needed.
  431.      * @param baseCompiler compiler associated with the low level parameter functions
  432.      * @return rebaser for the number of base variables specified
  433.      * @since 2.2
  434.      */
  435.     private MultivariateCompositionMapper[][] getRebaser(final DSCompiler baseCompiler) {
  436.         synchronized (rebaseIndirection) {

  437.             final int m = baseCompiler.getFreeParameters();
  438.             while (rebaseIndirection.size() <= m) {
  439.                 rebaseIndirection.add(null);
  440.             }

  441.             if (rebaseIndirection.get(m) == null) {
  442.                 // we need to create the rebaser from instance to m base variables

  443.                 if (order == 0) {
  444.                     // at order 0, the rebaser just copies the function value
  445.                     final MultivariateCompositionMapper[][] rebaser  = {
  446.                         { new MultivariateCompositionMapper(1, 0, new int[0]) }
  447.                     };
  448.                     rebaseIndirection.set(m, rebaser);
  449.                     return rebaser;
  450.                 }

  451.                 // at order n > 0, the rebaser starts from the rebaser at order n-1
  452.                 // so the first rows of the rebaser (corresponding to orders 0 to n-1)
  453.                 // are just copies of the lower rebaser rows with indices adjusted,
  454.                 // the last row corresponding to order n is a term ∂ⁿf/∂qⱼ⋯∂qₖ∂qₗ
  455.                 // which can be written ∂(∂fⁿ⁻¹/∂qⱼ⋯∂qₖ)/∂qₗ, selecting any arbitrary
  456.                 // qₗ with non-zero derivation order as the base for recursion
  457.                 // the lower level rebaser provides ∂fⁿ⁻¹/∂qⱼ⋯∂qₖ as a
  458.                 // sum of products: Σᵢ ∂fⁿ⁻¹/∂pᵤ⋯∂pᵥ ∂pᵤ/∂qⱼ⋯∂qₖ ⋯ ∂pᵥ/∂qⱼ⋯∂qₖ
  459.                 // so we have to differentiate this sum of products
  460.                 //   - the term ∂fⁿ⁻¹/∂pᵤ⋯∂pᵥ depends on the p intermediate variables,
  461.                 //     not on the q base variables, so we use the composition formula
  462.                 //     ∂g/∂qₗ = Σᵢ ∂g/∂pᵢ ∂pᵢ/∂qₗ
  463.                 //   - the terms ∂pᵤ/∂qⱼ⋯∂qₖ are directly the intermediate variables p and we
  464.                 //     know their derivatives with respect to the base variables q
  465.                 final int baseSize = baseCompiler.getSize();
  466.                 final MultivariateCompositionMapper[][] rebaser = initializeFromLowerRebaser(baseCompiler);

  467.                 // derivatives for last order
  468.                 for (int k = 1; k < baseSize; ++k) {
  469.                     // outer loop on rebased derivatives
  470.                     // at each iteration of the loop we are dealing with one derivative
  471.                     // like for example ∂³f/∂qⱼ∂qₖ∂qₗ, i.e. the components the rebaser produces
  472.                     if (rebaser[k] == null) {
  473.                         // the entry has not been set earlier
  474.                         // it is an entry of the form ∂ⁿf/∂qⱼ⋯∂qₖ∂qₗ where n is max order
  475.                         final List<MultivariateCompositionMapper> row = new ArrayList<>();

  476.                         // find a variable with respect to which we have a derivative
  477.                         final int[] orders = baseCompiler.derivativesOrders[k].clone();
  478.                         int qIndex = -1;
  479.                         for (int j = 0; j < orders.length; ++j) {
  480.                             if (orders[j] > 0) {
  481.                                 qIndex = j;
  482.                                 break;
  483.                             }
  484.                         }

  485.                         // find the entry corresponding to differentiating one order less with respect to this variable
  486.                         // ∂fⁿ⁻¹/∂qⱼ⋯∂qₖ
  487.                         orders[qIndex]--;
  488.                         final MultivariateCompositionMapper[] lowerRow =
  489.                                         rebaser[baseCompiler.getPartialDerivativeIndex(orders)];

  490.                         // apply recursion formula
  491.                         for (final MultivariateCompositionMapper lowerTerm : lowerRow) {

  492.                             for (int i = 0; i < parameters; ++i) {
  493.                                 // differentiate the term ∂fⁿ⁻¹/∂pᵤ⋯∂pᵥ part
  494.                                 row.add(differentiateFPart(lowerTerm, i, qIndex, baseCompiler));
  495.                             }

  496.                             // differentiate the products ∂pᵤ/∂qⱼ⋯∂qₖ ⋯ ∂pᵥ/∂qⱼ⋯∂qₖ
  497.                             for (int j = 0; j < lowerTerm.productIndices.length; ++j) {
  498.                                 row.add(differentiateProductPart(lowerTerm, j, qIndex, baseCompiler));
  499.                             }

  500.                         }

  501.                         // simplifies and store the completed entry
  502.                         rebaser[k] = combineSimilarTerms(row);

  503.                     }

  504.                 }

  505.                 rebaseIndirection.set(m, rebaser);

  506.             }

  507.             return rebaseIndirection.get(m);

  508.         }
  509.     }

  510.     /** Initialize a rebaser by copying the rules from a lower rebaser.
  511.      * @param baseCompiler compiler associated with the low level parameter functions
  512.      * @return rebaser with rules up to order - 1 copied (with indices adjusted)
  513.      * @since 2.2
  514.      */
  515.     private MultivariateCompositionMapper[][] initializeFromLowerRebaser(final DSCompiler baseCompiler) {

  516.         // get the rebaser at order - 1
  517.         final DSCompiler lowerCompiler     = getCompiler(parameters, order - 1);
  518.         final DSCompiler lowerBaseCompiler = getCompiler(baseCompiler.parameters, order - 1);
  519.         final int        lowerBaseSize     = lowerBaseCompiler.getSize();
  520.         final MultivariateCompositionMapper[][] lowerRebaser = lowerCompiler.getRebaser(lowerBaseCompiler);

  521.         // allocate array for rebaser at current order
  522.         final int baseSize = baseCompiler.getSize();
  523.         final MultivariateCompositionMapper[][] rebaser = new MultivariateCompositionMapper[baseSize][];

  524.         // copy the rebasing rules for orders 0 to order - 1, adjusting indices
  525.         for (int i = 0; i < lowerRebaser.length; ++i) {
  526.             final int index = convertIndex(i, lowerBaseCompiler.parameters, lowerBaseCompiler.derivativesOrders,
  527.                                            baseCompiler.parameters, baseCompiler.order, baseCompiler.sizes);
  528.             rebaser[index] = new MultivariateCompositionMapper[lowerRebaser[i].length];
  529.             for (int j = 0; j < rebaser[index].length; ++j) {
  530.                 final int coeff  = lowerRebaser[i][j].getCoeff();
  531.                 final int dsIndex = convertIndex(lowerRebaser[i][j].dsIndex,
  532.                                                  lowerCompiler.parameters, lowerCompiler.derivativesOrders,
  533.                                                  parameters, order, sizes);
  534.                 final int[] productIndices = new int[lowerRebaser[i][j].productIndices.length];
  535.                 for (int k = 0; k < productIndices.length; ++k) {
  536.                     final int pIndex      = lowerRebaser[i][j].productIndices[k] / lowerBaseSize;
  537.                     final int baseDSIndex = lowerRebaser[i][j].productIndices[k] % lowerBaseSize;
  538.                     productIndices[k] = pIndex * baseSize +
  539.                                         convertIndex(baseDSIndex,
  540.                                                      lowerBaseCompiler.parameters, lowerBaseCompiler.derivativesOrders,
  541.                                                      baseCompiler.parameters, baseCompiler.order, baseCompiler.sizes);
  542.                 }
  543.                 rebaser[index][j] = new MultivariateCompositionMapper(coeff, dsIndex, productIndices);
  544.             }
  545.         }

  546.         return rebaser;

  547.     }

  548.     /** Differentiate the ∂fⁿ⁻¹/∂pᵤ⋯∂pᵥ part of a {@link MultivariateCompositionMapper}.
  549.      * @param lowerTerm term to differentiate
  550.      * @param i index of the intermediate variable pᵢ
  551.      * @param qIndex index of the qₗ variable
  552.      * @param baseCompiler compiler associated with the low level parameter functions
  553.      * @return ∂fⁿ⁻¹/∂pᵤ⋯∂pᵥ
  554.      */
  555.     private MultivariateCompositionMapper differentiateFPart(final MultivariateCompositionMapper lowerTerm,
  556.                                                              final int i, final int qIndex,
  557.                                                              final DSCompiler baseCompiler) {

  558.         // differentiate the term ∂fⁿ⁻¹/∂pᵤ⋯∂pᵥ with respect to pi
  559.         final int[] termOrders = derivativesOrders[lowerTerm.dsIndex].clone();
  560.         termOrders[i]++;

  561.         // multiply by ∂pᵢ/∂qₗ
  562.         final int fDSIndex = getPartialDerivativeIndex(termOrders);
  563.         final int[] productIndicesF = new int[lowerTerm.productIndices.length + 1];
  564.         System.arraycopy(lowerTerm.productIndices, 0, productIndicesF, 0, lowerTerm.productIndices.length);
  565.         final int[] qOrders = new int[baseCompiler.parameters];
  566.         qOrders[qIndex] = 1;
  567.         productIndicesF[productIndicesF.length - 1] = i * baseCompiler.getSize() +
  568.                                                       baseCompiler.getPartialDerivativeIndex(qOrders);

  569.         // generate the differentiated term
  570.         final MultivariateCompositionMapper termF =
  571.                         new MultivariateCompositionMapper(lowerTerm.getCoeff(), fDSIndex, productIndicesF);
  572.         termF.sort();
  573.         return termF;

  574.     }

  575.     /** Differentiate a product part of a {@link MultivariateCompositionMapper}.
  576.      * @param lowerTerm term to differentiate
  577.      * @param j index of the product to differentiate
  578.      * @param qIndex index of the qₗ variable
  579.      * @param baseCompiler compiler associated with the low level parameter functions
  580.      * @return ∂fⁿ⁻¹/∂pᵤ⋯∂pᵥ
  581.      */
  582.     private MultivariateCompositionMapper differentiateProductPart(final MultivariateCompositionMapper lowerTerm,
  583.                                                                    final int j, final int qIndex,
  584.                                                                    final DSCompiler baseCompiler) {

  585.         // get derivation orders of ∂p/∂q
  586.         final int baseSize              = baseCompiler.getSize();
  587.         final int[] productIndicesP     = lowerTerm.productIndices.clone();
  588.         final int   pIndex              = productIndicesP[j] / baseSize;
  589.         final int   pDSIndex            = productIndicesP[j] % baseSize;
  590.         final int[] pOrders             = baseCompiler.getPartialDerivativeOrders(pDSIndex);

  591.         // derive once more with respect to the selected q
  592.         pOrders[qIndex]++;
  593.         final int   pDSIndexHigherOrder = baseCompiler.getPartialDerivativeIndex(pOrders);
  594.         productIndicesP[j]              = pIndex * baseSize + pDSIndexHigherOrder;

  595.         // create new term
  596.         final MultivariateCompositionMapper termP =
  597.                         new MultivariateCompositionMapper(lowerTerm.getCoeff(), lowerTerm.dsIndex, productIndicesP);
  598.         termP.sort();
  599.         return termP;

  600.     }

  601.     /** Get the index of a partial derivative in the array.
  602.      * <p>
  603.      * If all orders are set to 0, then the 0<sup>th</sup> order derivative
  604.      * is returned, which is the value of the function.
  605.      * </p>
  606.      * <p>The indices of derivatives are between 0 and {@link #getSize() getSize()} - 1.
  607.      * Their specific order is fixed for a given compiler, but otherwise not
  608.      * publicly specified. There are however some simple cases which have guaranteed
  609.      * indices:
  610.      * </p>
  611.      * <ul>
  612.      *   <li>the index of 0<sup>th</sup> order derivative is always 0</li>
  613.      *   <li>if there is only 1 {@link #getFreeParameters() free parameter}, then the
  614.      *   derivatives are sorted in increasing derivation order (i.e. f at index 0, df/dp
  615.      *   at index 1, d<sup>2</sup>f/dp<sup>2</sup> at index 2 ...
  616.      *   d<sup>k</sup>f/dp<sup>k</sup> at index k),</li>
  617.      *   <li>if the {@link #getOrder() derivation order} is 1, then the derivatives
  618.      *   are sorted in increasing free parameter order (i.e. f at index 0, df/dx<sub>1</sub>
  619.      *   at index 1, df/dx<sub>2</sub> at index 2 ... df/dx<sub>k</sub> at index k),</li>
  620.      *   <li>all other cases are not publicly specified</li>
  621.      * </ul>
  622.      * <p>
  623.      * This method is the inverse of method {@link #getPartialDerivativeOrders(int)}
  624.      * </p>
  625.      * @param orders derivation orders with respect to each parameter
  626.      * @return index of the partial derivative
  627.      * @exception MathIllegalArgumentException if the numbers of parameters does not
  628.      * match the instance
  629.      * @exception MathIllegalArgumentException if sum of derivation orders is larger
  630.      * than the instance limits
  631.      * @see #getPartialDerivativeOrders(int)
  632.      */
  633.     public int getPartialDerivativeIndex(final int ... orders)
  634.             throws MathIllegalArgumentException {

  635.         // safety check
  636.         MathUtils.checkDimension(orders.length, getFreeParameters());
  637.         return getPartialDerivativeIndex(parameters, order, sizes, orders);

  638.     }

  639.     /** Get the index of a partial derivative in an array.
  640.      * @param parameters number of free parameters
  641.      * @param order derivation order
  642.      * @param sizes sizes array
  643.      * @param orders derivation orders with respect to each parameter
  644.      * (the length of this array must match the number of parameters)
  645.      * @return index of the partial derivative
  646.      * @exception MathIllegalArgumentException if sum of derivation orders is larger
  647.      * than the instance limits
  648.      */
  649.     private static int getPartialDerivativeIndex(final int parameters, final int order,
  650.                                                  final int[][] sizes, final int ... orders)
  651.         throws MathIllegalArgumentException {

  652.         // the value is obtained by diving into the recursive Dan Kalman's structure
  653.         // this is theorem 2 of his paper, with recursion replaced by iteration
  654.         int index     = 0;
  655.         int m         = order;
  656.         int ordersSum = 0;
  657.         for (int i = parameters - 1; i >= 0; --i) {

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

  660.             // safety check
  661.             ordersSum += derivativeOrder;
  662.             if (ordersSum > order) {
  663.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
  664.                                                        ordersSum, order);
  665.             }

  666.             while (derivativeOrder > 0) {
  667.                 --derivativeOrder;
  668.                 // as long as we differentiate according to current free parameter,
  669.                 // we have to skip the value part and dive into the derivative part
  670.                 // so we add the size of the value part to the base index
  671.                 index += sizes[i][m--];
  672.             }

  673.         }

  674.         return index;

  675.     }

  676.     /** Convert an index from one (parameters, order) structure to another.
  677.      * @param index index of a partial derivative in source derivative structure
  678.      * @param srcP number of free parameters in source derivative structure
  679.      * @param srcDerivativesOrders derivatives orders array for the source
  680.      * derivative structure
  681.      * @param destP number of free parameters in destination derivative structure
  682.      * @param destO derivation order in destination derivative structure
  683.      * @param destSizes sizes array for the destination derivative structure
  684.      * @return index of the partial derivative with the <em>same</em> characteristics
  685.      * in destination derivative structure
  686.      * @throws MathIllegalArgumentException if order is too large
  687.      */
  688.     private static int convertIndex(final int index,
  689.                                     final int srcP, final int[][] srcDerivativesOrders,
  690.                                     final int destP, final int destO, final int[][] destSizes)
  691.         throws MathIllegalArgumentException {
  692.         int[] orders = new int[destP];
  693.         System.arraycopy(srcDerivativesOrders[index], 0, orders, 0, FastMath.min(srcP, destP));
  694.         return getPartialDerivativeIndex(destP, destO, destSizes, orders);
  695.     }

  696.     /** Get the derivation orders for a specific index in the array.
  697.      * <p>
  698.      * This method is the inverse of {@link #getPartialDerivativeIndex(int...)}.
  699.      * </p>
  700.      * @param index of the partial derivative
  701.      * @return derivation orders with respect to each parameter
  702.      * @see #getPartialDerivativeIndex(int...)
  703.      */
  704.     public int[] getPartialDerivativeOrders(final int index) {
  705.         return derivativesOrders[index].clone();
  706.     }

  707.     /** Get the sum of derivation orders for a specific index in the array.
  708.      * <p>
  709.      * This method return the sum of the elements returned by
  710.      * {@link #getPartialDerivativeIndex(int...)}, using precomputed
  711.      * values
  712.      * </p>
  713.      * @param index of the partial derivative
  714.      * @return sum of derivation orders with respect to each parameter
  715.      * @see #getPartialDerivativeIndex(int...)
  716.      * @since 2.2
  717.      */
  718.     public int getPartialDerivativeOrdersSum(final int index) {
  719.         return derivativesOrdersSum[index];
  720.     }

  721.     /** Get the number of free parameters.
  722.      * @return number of free parameters
  723.      */
  724.     public int getFreeParameters() {
  725.         return parameters;
  726.     }

  727.     /** Get the derivation order.
  728.      * @return derivation order
  729.      */
  730.     public int getOrder() {
  731.         return order;
  732.     }

  733.     /** Get the array size required for holding partial derivatives data.
  734.      * <p>
  735.      * This number includes the single 0 order derivative element, which is
  736.      * guaranteed to be stored in the first element of the array.
  737.      * </p>
  738.      * @return array size required for holding partial derivatives data
  739.      */
  740.     public int getSize() {
  741.         return sizes[parameters][order];
  742.     }

  743.     /** Compute linear combination.
  744.      * The derivative structure built will be a1 * ds1 + a2 * ds2
  745.      * @param a1 first scale factor
  746.      * @param c1 first base (unscaled) component
  747.      * @param offset1 offset of first operand in its array
  748.      * @param a2 second scale factor
  749.      * @param c2 second base (unscaled) component
  750.      * @param offset2 offset of second operand in its array
  751.      * @param result array where result must be stored (it may be
  752.      * one of the input arrays)
  753.      * @param resultOffset offset of the result in its array
  754.      */
  755.     public void linearCombination(final double a1, final double[] c1, final int offset1,
  756.                                   final double a2, final double[] c2, final int offset2,
  757.                                   final double[] result, final int resultOffset) {
  758.         for (int i = 0; i < getSize(); ++i) {
  759.             result[resultOffset + i] =
  760.                     MathArrays.linearCombination(a1, c1[offset1 + i], a2, c2[offset2 + i]);
  761.         }
  762.     }

  763.     /** Compute linear combination.
  764.      * The derivative structure built will be a1 * ds1 + a2 * ds2
  765.      * @param a1 first scale factor
  766.      * @param c1 first base (unscaled) component
  767.      * @param offset1 offset of first operand in its array
  768.      * @param a2 second scale factor
  769.      * @param c2 second base (unscaled) component
  770.      * @param offset2 offset of second operand in its array
  771.      * @param result array where result must be stored (it may be
  772.      * one of the input arrays)
  773.      * @param resultOffset offset of the result in its array
  774.      * @param <T> the type of the function parameters and value
  775.      */
  776.     public <T extends CalculusFieldElement<T>> void linearCombination(final T a1, final T[] c1, final int offset1,
  777.                                                                       final T a2, final T[] c2, final int offset2,
  778.                                                                       final T[] result, final int resultOffset) {
  779.         for (int i = 0; i < getSize(); ++i) {
  780.             result[resultOffset + i] =
  781.                     a1.linearCombination(a1, c1[offset1 + i], a2, c2[offset2 + i]);
  782.         }
  783.     }

  784.     /** Compute linear combination.
  785.      * The derivative structure built will be a1 * ds1 + a2 * ds2
  786.      * @param a1 first scale factor
  787.      * @param c1 first base (unscaled) component
  788.      * @param offset1 offset of first operand in its array
  789.      * @param a2 second scale factor
  790.      * @param c2 second base (unscaled) component
  791.      * @param offset2 offset of second operand in its array
  792.      * @param result array where result must be stored (it may be
  793.      * one of the input arrays)
  794.      * @param resultOffset offset of the result in its array
  795.      * @param <T> the type of the function parameters and value
  796.      */
  797.     public <T extends CalculusFieldElement<T>> void linearCombination(final double a1, final T[] c1, final int offset1,
  798.                                                                       final double a2, final T[] c2, final int offset2,
  799.                                                                       final T[] result, final int resultOffset) {
  800.         for (int i = 0; i < getSize(); ++i) {
  801.             result[resultOffset + i] =
  802.                     c1[offset1].linearCombination(a1, c1[offset1 + i], a2, c2[offset2 + i]);
  803.         }
  804.     }

  805.     /** Compute linear combination.
  806.      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
  807.      * @param a1 first scale factor
  808.      * @param c1 first base (unscaled) component
  809.      * @param offset1 offset of first operand in its array
  810.      * @param a2 second scale factor
  811.      * @param c2 second base (unscaled) component
  812.      * @param offset2 offset of second operand in its array
  813.      * @param a3 third scale factor
  814.      * @param c3 third base (unscaled) component
  815.      * @param offset3 offset of third operand in its array
  816.      * @param result array where result must be stored (it may be
  817.      * one of the input arrays)
  818.      * @param resultOffset offset of the result in its array
  819.      */
  820.     public void linearCombination(final double a1, final double[] c1, final int offset1,
  821.                                   final double a2, final double[] c2, final int offset2,
  822.                                   final double a3, final double[] c3, final int offset3,
  823.                                   final double[] result, final int resultOffset) {
  824.         for (int i = 0; i < getSize(); ++i) {
  825.             result[resultOffset + i] =
  826.                     MathArrays.linearCombination(a1, c1[offset1 + i],
  827.                                                  a2, c2[offset2 + i],
  828.                                                  a3, c3[offset3 + i]);
  829.         }
  830.     }

  831.     /** Compute linear combination.
  832.      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
  833.      * @param a1 first scale factor
  834.      * @param c1 first base (unscaled) component
  835.      * @param offset1 offset of first operand in its array
  836.      * @param a2 second scale factor
  837.      * @param c2 second base (unscaled) component
  838.      * @param offset2 offset of second operand in its array
  839.      * @param a3 third scale factor
  840.      * @param c3 third base (unscaled) component
  841.      * @param offset3 offset of third operand in its array
  842.      * @param result array where result must be stored (it may be
  843.      * one of the input arrays)
  844.      * @param resultOffset offset of the result in its array
  845.      * @param <T> the type of the function parameters and value
  846.      */
  847.     public <T extends CalculusFieldElement<T>> void linearCombination(final T a1, final T[] c1, final int offset1,
  848.                                                                       final T a2, final T[] c2, final int offset2,
  849.                                                                       final T a3, final T[] c3, final int offset3,
  850.                                                                       final T[] result, final int resultOffset) {
  851.         for (int i = 0; i < getSize(); ++i) {
  852.             result[resultOffset + i] =
  853.                     a1.linearCombination(a1, c1[offset1 + i],
  854.                                          a2, c2[offset2 + i],
  855.                                          a3, c3[offset3 + i]);
  856.         }
  857.     }

  858.     /** Compute linear combination.
  859.      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
  860.      * @param a1 first scale factor
  861.      * @param c1 first base (unscaled) component
  862.      * @param offset1 offset of first operand in its array
  863.      * @param a2 second scale factor
  864.      * @param c2 second base (unscaled) component
  865.      * @param offset2 offset of second operand in its array
  866.      * @param a3 third scale factor
  867.      * @param c3 third base (unscaled) component
  868.      * @param offset3 offset of third operand in its array
  869.      * @param result array where result must be stored (it may be
  870.      * one of the input arrays)
  871.      * @param resultOffset offset of the result in its array
  872.      * @param <T> the type of the function parameters and value
  873.      */
  874.     public <T extends CalculusFieldElement<T>> void linearCombination(final double a1, final T[] c1, final int offset1,
  875.                                                                       final double a2, final T[] c2, final int offset2,
  876.                                                                       final double a3, final T[] c3, final int offset3,
  877.                                                                       final T[] result, final int resultOffset) {
  878.         for (int i = 0; i < getSize(); ++i) {
  879.             result[resultOffset + i] =
  880.                     c1[offset1].linearCombination(a1, c1[offset1 + i],
  881.                                                   a2, c2[offset2 + i],
  882.                                                   a3, c3[offset3 + i]);
  883.         }
  884.     }

  885.     /** Compute linear combination.
  886.      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
  887.      * @param a1 first scale factor
  888.      * @param c1 first base (unscaled) component
  889.      * @param offset1 offset of first operand in its array
  890.      * @param a2 second scale factor
  891.      * @param c2 second base (unscaled) component
  892.      * @param offset2 offset of second operand in its array
  893.      * @param a3 third scale factor
  894.      * @param c3 third base (unscaled) component
  895.      * @param offset3 offset of third operand in its array
  896.      * @param a4 fourth scale factor
  897.      * @param c4 fourth base (unscaled) component
  898.      * @param offset4 offset of fourth operand in its array
  899.      * @param result array where result must be stored (it may be
  900.      * one of the input arrays)
  901.      * @param resultOffset offset of the result in its array
  902.      */
  903.     public void linearCombination(final double a1, final double[] c1, final int offset1,
  904.                                   final double a2, final double[] c2, final int offset2,
  905.                                   final double a3, final double[] c3, final int offset3,
  906.                                   final double a4, final double[] c4, final int offset4,
  907.                                   final double[] result, final int resultOffset) {
  908.         for (int i = 0; i < getSize(); ++i) {
  909.             result[resultOffset + i] =
  910.                     MathArrays.linearCombination(a1, c1[offset1 + i],
  911.                                                  a2, c2[offset2 + i],
  912.                                                  a3, c3[offset3 + i],
  913.                                                  a4, c4[offset4 + i]);
  914.         }
  915.     }

  916.     /** Compute linear combination.
  917.      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
  918.      * @param a1 first scale factor
  919.      * @param c1 first base (unscaled) component
  920.      * @param offset1 offset of first operand in its array
  921.      * @param a2 second scale factor
  922.      * @param c2 second base (unscaled) component
  923.      * @param offset2 offset of second operand in its array
  924.      * @param a3 third scale factor
  925.      * @param c3 third base (unscaled) component
  926.      * @param offset3 offset of third operand in its array
  927.      * @param a4 fourth scale factor
  928.      * @param c4 fourth base (unscaled) component
  929.      * @param offset4 offset of fourth operand in its array
  930.      * @param result array where result must be stored (it may be
  931.      * one of the input arrays)
  932.      * @param resultOffset offset of the result in its array
  933.      * @param <T> the type of the function parameters and value
  934.      */
  935.     public <T extends CalculusFieldElement<T>> void linearCombination(final T a1, final T[] c1, final int offset1,
  936.                                                                       final T a2, final T[] c2, final int offset2,
  937.                                                                       final T a3, final T[] c3, final int offset3,
  938.                                                                       final T a4, final T[] c4, final int offset4,
  939.                                                                       final T[] result, final int resultOffset) {
  940.         for (int i = 0; i < getSize(); ++i) {
  941.             result[resultOffset + i] =
  942.                     a1.linearCombination(a1, c1[offset1 + i],
  943.                                          a2, c2[offset2 + i],
  944.                                          a3, c3[offset3 + i],
  945.                                          a4, c4[offset4 + i]);
  946.         }
  947.     }

  948.     /** Compute linear combination.
  949.      * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
  950.      * @param a1 first scale factor
  951.      * @param c1 first base (unscaled) component
  952.      * @param offset1 offset of first operand in its array
  953.      * @param a2 second scale factor
  954.      * @param c2 second base (unscaled) component
  955.      * @param offset2 offset of second operand in its array
  956.      * @param a3 third scale factor
  957.      * @param c3 third base (unscaled) component
  958.      * @param offset3 offset of third operand in its array
  959.      * @param a4 fourth scale factor
  960.      * @param c4 fourth base (unscaled) component
  961.      * @param offset4 offset of fourth operand in its array
  962.      * @param result array where result must be stored (it may be
  963.      * one of the input arrays)
  964.      * @param resultOffset offset of the result in its array
  965.      * @param <T> the type of the function parameters and value
  966.      */
  967.     public <T extends CalculusFieldElement<T>> void linearCombination(final double a1, final T[] c1, final int offset1,
  968.                                                                       final double a2, final T[] c2, final int offset2,
  969.                                                                       final double a3, final T[] c3, final int offset3,
  970.                                                                       final double a4, final T[] c4, final int offset4,
  971.                                                                       final T[] result, final int resultOffset) {
  972.         for (int i = 0; i < getSize(); ++i) {
  973.             result[resultOffset + i] =
  974.                             c1[offset1].linearCombination(a1, c1[offset1 + i],
  975.                                                           a2, c2[offset2 + i],
  976.                                                           a3, c3[offset3 + i],
  977.                                                           a4, c4[offset4 + i]);
  978.         }
  979.     }

  980.     /** Perform addition of two derivative structures.
  981.      * @param lhs array holding left hand side of addition
  982.      * @param lhsOffset offset of the left hand side in its array
  983.      * @param rhs array right hand side of addition
  984.      * @param rhsOffset offset of the right hand side in its array
  985.      * @param result array where result must be stored (it may be
  986.      * one of the input arrays)
  987.      * @param resultOffset offset of the result in its array
  988.      */
  989.     public void add(final double[] lhs, final int lhsOffset,
  990.                     final double[] rhs, final int rhsOffset,
  991.                     final double[] result, final int resultOffset) {
  992.         for (int i = 0; i < getSize(); ++i) {
  993.             result[resultOffset + i] = lhs[lhsOffset + i] + rhs[rhsOffset + i];
  994.         }
  995.     }

  996.     /** Perform addition of two derivative structures.
  997.      * @param lhs array holding left hand side of addition
  998.      * @param lhsOffset offset of the left hand side in its array
  999.      * @param rhs array right hand side of addition
  1000.      * @param rhsOffset offset of the right hand side in its array
  1001.      * @param result array where result must be stored (it may be
  1002.      * one of the input arrays)
  1003.      * @param resultOffset offset of the result in its array
  1004.      * @param <T> the type of the function parameters and value
  1005.      */
  1006.     public <T extends CalculusFieldElement<T>> void add(final T[] lhs, final int lhsOffset,
  1007.                                                         final T[] rhs, final int rhsOffset,
  1008.                                                         final T[] result, final int resultOffset) {
  1009.         for (int i = 0; i < getSize(); ++i) {
  1010.             result[resultOffset + i] = lhs[lhsOffset + i].add(rhs[rhsOffset + i]);
  1011.         }
  1012.     }

  1013.     /** Perform subtraction of two derivative structures.
  1014.      * @param lhs array holding left hand side of subtraction
  1015.      * @param lhsOffset offset of the left hand side in its array
  1016.      * @param rhs array right hand side of subtraction
  1017.      * @param rhsOffset offset of the right hand side in its array
  1018.      * @param result array where result must be stored (it may be
  1019.      * one of the input arrays)
  1020.      * @param resultOffset offset of the result in its array
  1021.      */
  1022.     public void subtract(final double[] lhs, final int lhsOffset,
  1023.                          final double[] rhs, final int rhsOffset,
  1024.                          final double[] result, final int resultOffset) {
  1025.         for (int i = 0; i < getSize(); ++i) {
  1026.             result[resultOffset + i] = lhs[lhsOffset + i] - rhs[rhsOffset + i];
  1027.         }
  1028.     }

  1029.     /** Perform subtraction of two derivative structures.
  1030.      * @param lhs array holding left hand side of subtraction
  1031.      * @param lhsOffset offset of the left hand side in its array
  1032.      * @param rhs array right hand side of subtraction
  1033.      * @param rhsOffset offset of the right hand side in its array
  1034.      * @param result array where result must be stored (it may be
  1035.      * one of the input arrays)
  1036.      * @param resultOffset offset of the result in its array
  1037.      * @param <T> the type of the function parameters and value
  1038.      */
  1039.     public <T extends CalculusFieldElement<T>> void subtract(final T[] lhs, final int lhsOffset,
  1040.                                                              final T[] rhs, final int rhsOffset,
  1041.                                                              final T[] result, final int resultOffset) {
  1042.         for (int i = 0; i < getSize(); ++i) {
  1043.             result[resultOffset + i] = lhs[lhsOffset + i].subtract(rhs[rhsOffset + i]);
  1044.         }
  1045.     }

  1046.    /** Perform multiplication of two derivative structures.
  1047.      * @param lhs array holding left hand side of multiplication
  1048.      * @param lhsOffset offset of the left hand side in its array
  1049.      * @param rhs array right hand side of multiplication
  1050.      * @param rhsOffset offset of the right hand side in its array
  1051.      * @param result array where result must be stored (for
  1052.      * multiplication the result array <em>cannot</em> be one of
  1053.      * the input arrays)
  1054.      * @param resultOffset offset of the result in its array
  1055.      */
  1056.     public void multiply(final double[] lhs, final int lhsOffset,
  1057.                          final double[] rhs, final int rhsOffset,
  1058.                          final double[] result, final int resultOffset) {
  1059.         for (int i = 0; i < multIndirection.length; ++i) {
  1060.             double r = 0;
  1061.             for (final MultiplicationMapper mapping : multIndirection[i]) {
  1062.                 r += mapping.getCoeff() *
  1063.                      lhs[lhsOffset + mapping.lhsIndex] *
  1064.                      rhs[rhsOffset + mapping.rhsIndex];
  1065.             }
  1066.             result[resultOffset + i] = r;
  1067.         }
  1068.     }

  1069.     /** Perform multiplication of two derivative structures.
  1070.      * @param lhs array holding left hand side of multiplication
  1071.      * @param lhsOffset offset of the left hand side in its array
  1072.      * @param rhs array right hand side of multiplication
  1073.      * @param rhsOffset offset of the right hand side in its array
  1074.      * @param result array where result must be stored (for
  1075.      * multiplication the result array <em>cannot</em> be one of
  1076.      * the input arrays)
  1077.      * @param resultOffset offset of the result in its array
  1078.      * @param <T> the type of the function parameters and value
  1079.      */
  1080.     public <T extends CalculusFieldElement<T>> void multiply(final T[] lhs, final int lhsOffset,
  1081.                                                              final T[] rhs, final int rhsOffset,
  1082.                                                              final T[] result, final int resultOffset) {
  1083.         T zero = lhs[lhsOffset].getField().getZero();
  1084.         for (int i = 0; i < multIndirection.length; ++i) {
  1085.             T r = zero;
  1086.             for (final MultiplicationMapper mapping : multIndirection[i]) {
  1087.                 r = r.add(lhs[lhsOffset + mapping.lhsIndex].
  1088.                           multiply(rhs[rhsOffset + mapping.rhsIndex]).
  1089.                           multiply(mapping.getCoeff()));
  1090.             }
  1091.             result[resultOffset + i] = r;
  1092.         }
  1093.     }

  1094.     /** Perform division of two derivative structures. Based on the multiplication operator.
  1095.      * @param lhs array holding left hand side of division
  1096.      * @param lhsOffset offset of the left hand side in its array
  1097.      * @param rhs array right hand side of division
  1098.      * @param rhsOffset offset of the right hand side in its array
  1099.      * @param result array where result must be stored (for
  1100.      * division the result array <em>cannot</em> be one of
  1101.      * the input arrays)
  1102.      * @param resultOffset offset of the result in its array
  1103.      */
  1104.     public void divide(final double[] lhs, final int lhsOffset,
  1105.                        final double[] rhs, final int rhsOffset,
  1106.                        final double[] result, final int resultOffset) {
  1107.         result[resultOffset] = lhs[lhsOffset] / rhs[rhsOffset];
  1108.         for (int i = 1; i < multIndirection.length; ++i) {
  1109.             result[resultOffset + i] = lhs[lhsOffset + i];
  1110.             for (int j = 0; j < multIndirection[i].length - 1; j++) {
  1111.                 final MultiplicationMapper mapping = multIndirection[i][j];
  1112.                 result[resultOffset + i] -= mapping.getCoeff() *
  1113.                         (result[resultOffset + mapping.lhsIndex] * rhs[rhsOffset + mapping.rhsIndex]);
  1114.             }
  1115.             result[resultOffset + i] /= rhs[lhsOffset] * multIndirection[i][0].getCoeff();
  1116.         }
  1117.     }

  1118.     /** Perform division of two derivative structures. Based on the multiplication operator.
  1119.      * @param lhs array holding left hand side of division
  1120.      * @param lhsOffset offset of the left hand side in its array
  1121.      * @param rhs array right hand side of division
  1122.      * @param rhsOffset offset of the right hand side in its array
  1123.      * @param result array where result must be stored (for
  1124.      * division the result array <em>cannot</em> be one of
  1125.      * the input arrays)
  1126.      * @param resultOffset offset of the result in its array
  1127.      * @param <T> the type of the function parameters and value
  1128.      */
  1129.     public <T extends CalculusFieldElement<T>> void divide(final T[] lhs, final int lhsOffset,
  1130.                                                            final T[] rhs, final int rhsOffset,
  1131.                                                            final T[] result, final int resultOffset) {
  1132.         final T zero = lhs[lhsOffset].getField().getZero();
  1133.         result[resultOffset] = lhs[lhsOffset].divide(rhs[rhsOffset]);
  1134.         for (int i = 1; i < multIndirection.length; ++i) {
  1135.             result[resultOffset + i] = lhs[lhsOffset + i].add(zero);
  1136.             for (int j = 0; j < multIndirection[i].length - 1; j++) {
  1137.                 final MultiplicationMapper mapping = multIndirection[i][j];
  1138.                 result[resultOffset + i] = result[resultOffset + i].subtract(
  1139.                         result[resultOffset + mapping.lhsIndex].multiply(rhs[rhsOffset + mapping.rhsIndex]).
  1140.                                 multiply(mapping.getCoeff()));
  1141.             }
  1142.             result[resultOffset + i] = result[resultOffset + i].divide(rhs[lhsOffset].
  1143.                     multiply(multIndirection[i][0].getCoeff()));
  1144.         }
  1145.     }

  1146.     /** Compute reciprocal of derivative structure. Based on the multiplication operator.
  1147.      * @param operand array holding the operand
  1148.      * @param operandOffset offset of the operand in its array
  1149.      * @param result array where result must be stored
  1150.      * @param resultOffset offset of the result in its array
  1151.      */
  1152.     public void reciprocal(final double[] operand, final int operandOffset,
  1153.                            final double[] result, final int resultOffset) {
  1154.         result[resultOffset] = 1. / operand[operandOffset];
  1155.         for (int i = 1; i < multIndirection.length; ++i) {
  1156.             result[resultOffset + i] = 0.;
  1157.             for (int j = 0; j < multIndirection[i].length - 1; j++) {
  1158.                 final MultiplicationMapper mapping = multIndirection[i][j];
  1159.                 result[resultOffset + i] -= mapping.getCoeff() *
  1160.                         (result[resultOffset + mapping.lhsIndex] * operand[operandOffset + mapping.rhsIndex]);
  1161.             }
  1162.             result[resultOffset + i] /= operand[operandOffset] * multIndirection[i][0].getCoeff();
  1163.         }
  1164.     }

  1165.     /** Compute reciprocal of derivative structure. Based on the multiplication operator.
  1166.      * @param operand array holding the operand
  1167.      * @param operandOffset offset of the operand in its array
  1168.      * @param result array where result must be stored
  1169.      * @param resultOffset offset of the result in its array
  1170.      * @param <T> the type of the function parameters and value
  1171.      */
  1172.     public <T extends CalculusFieldElement<T>> void reciprocal(final T[] operand, final int operandOffset,
  1173.                                                                final T[] result, final int resultOffset) {
  1174.         final T zero = operand[operandOffset].getField().getZero();
  1175.         result[resultOffset] = operand[operandOffset].reciprocal();
  1176.         for (int i = 1; i < multIndirection.length; ++i) {
  1177.             result[resultOffset + i] = zero;
  1178.             for (int j = 0; j < multIndirection[i].length - 1; j++) {
  1179.                 final MultiplicationMapper mapping = multIndirection[i][j];
  1180.                 result[resultOffset + i] = result[resultOffset + i].subtract(
  1181.                         (result[resultOffset + mapping.lhsIndex].multiply(operand[operandOffset + mapping.rhsIndex])).
  1182.                                 multiply(mapping.getCoeff()));
  1183.             }
  1184.             result[resultOffset + i] = result[resultOffset + i].divide(operand[operandOffset].
  1185.                     multiply(multIndirection[i][0].getCoeff()));
  1186.         }
  1187.     }

  1188.     /** Perform remainder of two derivative structures.
  1189.      * @param lhs array holding left hand side of remainder
  1190.      * @param lhsOffset offset of the left hand side in its array
  1191.      * @param rhs array right hand side of remainder
  1192.      * @param rhsOffset offset of the right hand side in its array
  1193.      * @param result array where result must be stored (it may be
  1194.      * one of the input arrays)
  1195.      * @param resultOffset offset of the result in its array
  1196.      */
  1197.     public void remainder(final double[] lhs, final int lhsOffset,
  1198.                           final double[] rhs, final int rhsOffset,
  1199.                           final double[] result, final int resultOffset) {

  1200.         // compute k such that lhs % rhs = lhs - k rhs
  1201.         final double rem = FastMath.IEEEremainder(lhs[lhsOffset], rhs[rhsOffset]);
  1202.         final double k   = FastMath.rint((lhs[lhsOffset] - rem) / rhs[rhsOffset]);

  1203.         // set up value
  1204.         result[resultOffset] = rem;

  1205.         // set up partial derivatives
  1206.         for (int i = 1; i < getSize(); ++i) {
  1207.             result[resultOffset + i] = lhs[lhsOffset + i] - k * rhs[rhsOffset + i];
  1208.         }

  1209.     }

  1210.     /** Perform remainder of two derivative structures.
  1211.      * @param lhs array holding left hand side of remainder
  1212.      * @param lhsOffset offset of the left hand side in its array
  1213.      * @param rhs array right hand side of remainder
  1214.      * @param rhsOffset offset of the right hand side in its array
  1215.      * @param result array where result must be stored (it may be
  1216.      * one of the input arrays)
  1217.      * @param resultOffset offset of the result in its array
  1218.      * @param <T> the type of the function parameters and value
  1219.      */
  1220.     public <T extends CalculusFieldElement<T>> void remainder(final T[] lhs, final int lhsOffset,
  1221.                                                               final T[] rhs, final int rhsOffset,
  1222.                                                               final T[] result, final int resultOffset) {

  1223.         // compute k such that lhs % rhs = lhs - k rhs
  1224.         final T      rem = lhs[lhsOffset].remainder(rhs[rhsOffset]);
  1225.         final double k   = FastMath.rint((lhs[lhsOffset].getReal() - rem.getReal()) / rhs[rhsOffset].getReal());

  1226.         // set up value
  1227.         result[resultOffset] = rem;

  1228.         // set up partial derivatives
  1229.         for (int i = 1; i < getSize(); ++i) {
  1230.             result[resultOffset + i] = lhs[lhsOffset + i].subtract(rhs[rhsOffset + i].multiply(k));
  1231.         }

  1232.     }

  1233.     /** Compute power of a double to a derivative structure.
  1234.      * @param a number to exponentiate
  1235.      * @param operand array holding the power
  1236.      * @param operandOffset offset of the power in its array
  1237.      * @param result array where result must be stored (for
  1238.      * power the result array <em>cannot</em> be the input
  1239.      * array)
  1240.      * @param resultOffset offset of the result in its array
  1241.      */
  1242.     public void pow(final double a,
  1243.                     final double[] operand, final int operandOffset,
  1244.                     final double[] result, final int resultOffset) {

  1245.         // create the function value and derivatives
  1246.         // [a^x, ln(a) a^x, ln(a)^2 a^x,, ln(a)^3 a^x, ... ]
  1247.         final double[] function = new double[1 + order];
  1248.         if (a == 0) {
  1249.             if (operand[operandOffset] == 0) {
  1250.                 function[0] = 1;
  1251.                 double infinity = Double.POSITIVE_INFINITY;
  1252.                 for (int i = 1; i < function.length; ++i) {
  1253.                     infinity = -infinity;
  1254.                     function[i] = infinity;
  1255.                 }
  1256.             } else if (operand[operandOffset] < 0) {
  1257.                 Arrays.fill(function, Double.NaN);
  1258.             }
  1259.         } else {
  1260.             function[0] = FastMath.pow(a, operand[operandOffset]);
  1261.             final double lnA = FastMath.log(a);
  1262.             for (int i = 1; i < function.length; ++i) {
  1263.                 function[i] = lnA * function[i - 1];
  1264.             }
  1265.         }


  1266.         // apply function composition
  1267.         compose(operand, operandOffset, function, result, resultOffset);

  1268.     }

  1269.     /** Compute power of a double to a derivative structure.
  1270.      * @param a number to exponentiate
  1271.      * @param operand array holding the power
  1272.      * @param operandOffset offset of the power in its array
  1273.      * @param result array where result must be stored (for
  1274.      * power the result array <em>cannot</em> be the input
  1275.      * array)
  1276.      * @param resultOffset offset of the result in its array
  1277.      * @param <T> the type of the function parameters and value
  1278.      */
  1279.     public <T extends CalculusFieldElement<T>> void pow(final double a,
  1280.                                                         final T[] operand, final int operandOffset,
  1281.                                                         final T[] result, final int resultOffset) {

  1282.         final T zero = operand[operandOffset].getField().getZero();

  1283.         // create the function value and derivatives
  1284.         // [a^x, ln(a) a^x, ln(a)^2 a^x,, ln(a)^3 a^x, ... ]
  1285.         final T[] function = MathArrays.buildArray(operand[operandOffset].getField(), 1 + order);
  1286.         if (a == 0) {
  1287.             if (operand[operandOffset].getReal() == 0) {
  1288.                 function[0] = zero.add(1);
  1289.                 T infinity = zero.add(Double.POSITIVE_INFINITY);
  1290.                 for (int i = 1; i < function.length; ++i) {
  1291.                     infinity = infinity.negate();
  1292.                     function[i] = infinity;
  1293.                 }
  1294.             } else if (operand[operandOffset].getReal() < 0) {
  1295.                 Arrays.fill(function, zero.add(Double.NaN));
  1296.             }
  1297.         } else {
  1298.             function[0] = zero.add(a).pow(operand[operandOffset]);
  1299.             final double lnA = FastMath.log(a);
  1300.             for (int i = 1; i < function.length; ++i) {
  1301.                 function[i] = function[i - 1].multiply(lnA);
  1302.             }
  1303.         }


  1304.         // apply function composition
  1305.         compose(operand, operandOffset, function, result, resultOffset);

  1306.     }

  1307.     /** Compute power of a derivative structure.
  1308.      * @param operand array holding the operand
  1309.      * @param operandOffset offset of the operand in its array
  1310.      * @param p power to apply
  1311.      * @param result array where result must be stored (for
  1312.      * power the result array <em>cannot</em> be the input
  1313.      * array)
  1314.      * @param resultOffset offset of the result in its array
  1315.      */
  1316.     public void pow(final double[] operand, final int operandOffset, final double p,
  1317.                     final double[] result, final int resultOffset) {

  1318.         if (p == 0) {
  1319.             // special case, x^0 = 1 for all x
  1320.             result[resultOffset] = 1.0;
  1321.             Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), 0);
  1322.             return;
  1323.         }

  1324.         if (operand[operandOffset] == 0) {
  1325.             // special case, 0^p = 0 for all p
  1326.             Arrays.fill(result, resultOffset, resultOffset + getSize(), 0);
  1327.             return;
  1328.         }

  1329.         // create the function value and derivatives
  1330.         // [x^p, px^(p-1), p(p-1)x^(p-2), ... ]
  1331.         double[] function = new double[1 + order];
  1332.         double xk = FastMath.pow(operand[operandOffset], p - order);
  1333.         for (int i = order; i > 0; --i) {
  1334.             function[i] = xk;
  1335.             xk *= operand[operandOffset];
  1336.         }
  1337.         function[0] = xk;
  1338.         double coefficient = p;
  1339.         for (int i = 1; i <= order; ++i) {
  1340.             function[i] *= coefficient;
  1341.             coefficient *= p - i;
  1342.         }

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

  1345.     }

  1346.     /** Compute power of a derivative structure.
  1347.      * @param operand array holding the operand
  1348.      * @param operandOffset offset of the operand in its array
  1349.      * @param p power to apply
  1350.      * @param result array where result must be stored (for
  1351.      * power the result array <em>cannot</em> be the input
  1352.      * array)
  1353.      * @param resultOffset offset of the result in its array
  1354.      * @param <T> the type of the function parameters and value
  1355.      */
  1356.     public <T extends CalculusFieldElement<T>> void pow(final T[] operand, final int operandOffset, final double p,
  1357.                                                         final T[] result, final int resultOffset) {

  1358.         final Field<T> field = operand[operandOffset].getField();

  1359.         if (p == 0) {
  1360.             // special case, x^0 = 1 for all x
  1361.             result[resultOffset] = field.getOne();
  1362.             Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), field.getZero());
  1363.             return;
  1364.         }

  1365.         if (operand[operandOffset].getReal() == 0) {
  1366.             // special case, 0^p = 0 for all p
  1367.             Arrays.fill(result, resultOffset, resultOffset + getSize(), field.getZero());
  1368.             return;
  1369.         }

  1370.         // create the function value and derivatives
  1371.         // [x^p, px^(p-1), p(p-1)x^(p-2), ... ]
  1372.         T[] function = MathArrays.buildArray(field, 1 + order);
  1373.         T xk = operand[operandOffset].pow(p - order);
  1374.         for (int i = order; i > 0; --i) {
  1375.             function[i] = xk;
  1376.             xk = xk.multiply(operand[operandOffset]);
  1377.         }
  1378.         function[0] = xk;
  1379.         double coefficient = p;
  1380.         for (int i = 1; i <= order; ++i) {
  1381.             function[i]  = function[i].multiply(coefficient);
  1382.             coefficient *= p - i;
  1383.         }

  1384.         // apply function composition
  1385.         compose(operand, operandOffset, function, result, resultOffset);

  1386.     }

  1387.     /** Compute integer power of a derivative structure.
  1388.      * @param operand array holding the operand
  1389.      * @param operandOffset offset of the operand in its array
  1390.      * @param n power to apply
  1391.      * @param result array where result must be stored (for
  1392.      * power the result array <em>cannot</em> be the input
  1393.      * array)
  1394.      * @param resultOffset offset of the result in its array
  1395.      */
  1396.     public void pow(final double[] operand, final int operandOffset, final int n,
  1397.                     final double[] result, final int resultOffset) {

  1398.         if (n == 0) {
  1399.             // special case, x^0 = 1 for all x
  1400.             result[resultOffset] = 1.0;
  1401.             Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), 0);
  1402.             return;
  1403.         }

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

  1407.         if (n > 0) {
  1408.             // strictly positive power
  1409.             final int maxOrder = FastMath.min(order, n);
  1410.             double xk = FastMath.pow(operand[operandOffset], n - maxOrder);
  1411.             for (int i = maxOrder; i > 0; --i) {
  1412.                 function[i] = xk;
  1413.                 xk *= operand[operandOffset];
  1414.             }
  1415.             function[0] = xk;
  1416.         } else {
  1417.             // strictly negative power
  1418.             final double inv = 1.0 / operand[operandOffset];
  1419.             double xk = FastMath.pow(inv, -n);
  1420.             for (int i = 0; i <= order; ++i) {
  1421.                 function[i] = xk;
  1422.                 xk *= inv;
  1423.             }
  1424.         }

  1425.         double coefficient = n;
  1426.         for (int i = 1; i <= order; ++i) {
  1427.             function[i] *= coefficient;
  1428.             coefficient *= n - i;
  1429.         }

  1430.         // apply function composition
  1431.         compose(operand, operandOffset, function, result, resultOffset);

  1432.     }

  1433.     /** Compute integer power of a derivative structure.
  1434.      * @param operand array holding the operand
  1435.      * @param operandOffset offset of the operand in its array
  1436.      * @param n power to apply
  1437.      * @param result array where result must be stored (for
  1438.      * power the result array <em>cannot</em> be the input
  1439.      * array)
  1440.      * @param resultOffset offset of the result in its array
  1441.      * @param <T> the type of the function parameters and value
  1442.      */
  1443.     public <T extends CalculusFieldElement<T>> void pow(final T[] operand, final int operandOffset, final int n,
  1444.                                                         final T[] result, final int resultOffset) {

  1445.         final Field<T> field = operand[operandOffset].getField();

  1446.         if (n == 0) {
  1447.             // special case, x^0 = 1 for all x
  1448.             result[resultOffset] = field.getOne();
  1449.             Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), field.getZero());
  1450.             return;
  1451.         }

  1452.         // create the power function value and derivatives
  1453.         // [x^n, nx^(n-1), n(n-1)x^(n-2), ... ]
  1454.         T[] function = MathArrays.buildArray(field, 1 + order);

  1455.         if (n > 0) {
  1456.             // strictly positive power
  1457.             final int maxOrder = FastMath.min(order, n);
  1458.             T xk = operand[operandOffset].pow(n - maxOrder);
  1459.             for (int i = maxOrder; i > 0; --i) {
  1460.                 function[i] = xk;
  1461.                 xk = xk.multiply(operand[operandOffset]);
  1462.             }
  1463.             function[0] = xk;
  1464.         } else {
  1465.             // strictly negative power
  1466.             final T inv = operand[operandOffset].reciprocal();
  1467.             T xk = inv.pow(-n);
  1468.             for (int i = 0; i <= order; ++i) {
  1469.                 function[i] = xk;
  1470.                 xk = xk.multiply(inv);
  1471.             }
  1472.         }

  1473.         double coefficient = n;
  1474.         for (int i = 1; i <= order; ++i) {
  1475.             function[i]  = function[i].multiply(coefficient);
  1476.             coefficient *= n - i;
  1477.         }

  1478.         // apply function composition
  1479.         compose(operand, operandOffset, function, result, resultOffset);

  1480.     }

  1481.     /** Compute power of a derivative structure.
  1482.      * @param x array holding the base
  1483.      * @param xOffset offset of the base in its array
  1484.      * @param y array holding the exponent
  1485.      * @param yOffset offset of the exponent in its array
  1486.      * @param result array where result must be stored (for
  1487.      * power the result array <em>cannot</em> be the input
  1488.      * array)
  1489.      * @param resultOffset offset of the result in its array
  1490.      */
  1491.     public void pow(final double[] x, final int xOffset,
  1492.                     final double[] y, final int yOffset,
  1493.                     final double[] result, final int resultOffset) {
  1494.         final double[] logX = new double[getSize()];
  1495.         log(x, xOffset, logX, 0);
  1496.         final double[] yLogX = new double[getSize()];
  1497.         multiply(logX, 0, y, yOffset, yLogX, 0);
  1498.         exp(yLogX, 0, result, resultOffset);
  1499.     }

  1500.     /** Compute power of a derivative structure.
  1501.      * @param x array holding the base
  1502.      * @param xOffset offset of the base in its array
  1503.      * @param y array holding the exponent
  1504.      * @param yOffset offset of the exponent in its array
  1505.      * @param result array where result must be stored (for
  1506.      * power the result array <em>cannot</em> be the input
  1507.      * array)
  1508.      * @param resultOffset offset of the result in its array
  1509.      * @param <T> the type of the function parameters and value
  1510.      */
  1511.     public <T extends CalculusFieldElement<T>> void pow(final T[] x, final int xOffset,
  1512.                                                         final T[] y, final int yOffset,
  1513.                                                         final T[] result, final int resultOffset) {
  1514.         final T[] logX = MathArrays.buildArray(x[xOffset].getField(), getSize());
  1515.         log(x, xOffset, logX, 0);
  1516.         final T[] yLogX = MathArrays.buildArray(x[xOffset].getField(), getSize());
  1517.         multiply(logX, 0, y, yOffset, yLogX, 0);
  1518.         exp(yLogX, 0, result, resultOffset);
  1519.     }

  1520.     /** Compute square root of a derivative structure. Based on the multiplication operator.
  1521.      * @param operand array holding the operand
  1522.      * @param operandOffset offset of the operand in its array
  1523.      * @param result array where result must be stored (for
  1524.      * square root the result array <em>cannot</em> be the input
  1525.      * array)
  1526.      * @param resultOffset offset of the result in its array
  1527.      */
  1528.     public void sqrt(final double[] operand, final int operandOffset,
  1529.                      final double[] result, final int resultOffset) {
  1530.         final double sqrtConstant = FastMath.sqrt(operand[operandOffset]);
  1531.         result[resultOffset] = sqrtConstant;
  1532.         for (int i = 1; i < multIndirection.length; ++i) {
  1533.             result[resultOffset + i] = operand[operandOffset + i];
  1534.             for (int j = 1; j < multIndirection[i].length - 1; j++) {
  1535.                 final MultiplicationMapper mapping = multIndirection[i][j];
  1536.                 result[resultOffset + i] -= mapping.getCoeff() *
  1537.                         (result[resultOffset + mapping.lhsIndex] * result[operandOffset + mapping.rhsIndex]);
  1538.             }
  1539.             result[resultOffset + i] /= sqrtConstant * (multIndirection[i][multIndirection[i].length - 1].getCoeff() +
  1540.                     multIndirection[i][0].getCoeff());
  1541.         }
  1542.     }

  1543.     /** Compute square root of a derivative structure. Based on the multiplication operator.
  1544.      * @param operand array holding the operand
  1545.      * @param operandOffset offset of the operand in its array
  1546.      * @param result array where result must be stored (for
  1547.      * square root the result array <em>cannot</em> be the input
  1548.      * array)
  1549.      * @param resultOffset offset of the result in its array
  1550.      * @param <T> the type of the function parameters and value
  1551.      */
  1552.     public <T extends CalculusFieldElement<T>> void sqrt(final T[] operand, final int operandOffset,
  1553.                                                          final T[] result, final int resultOffset) {
  1554.         final T zero = operand[operandOffset].getField().getZero();
  1555.         final T sqrtConstant = operand[operandOffset].sqrt();
  1556.         result[resultOffset] = sqrtConstant.add(zero);
  1557.         for (int i = 1; i < multIndirection.length; ++i) {
  1558.             result[resultOffset + i] = operand[operandOffset + i].add(zero);
  1559.             for (int j = 1; j < multIndirection[i].length - 1; j++) {
  1560.                 final MultiplicationMapper mapping = multIndirection[i][j];
  1561.                 result[resultOffset + i] = result[resultOffset + i].subtract(
  1562.                         (result[resultOffset + mapping.lhsIndex].multiply(result[operandOffset + mapping.rhsIndex])).
  1563.                                 multiply(mapping.getCoeff()));
  1564.             }
  1565.             result[resultOffset + i] = result[resultOffset + i].divide(sqrtConstant.multiply(
  1566.                     multIndirection[i][0].getCoeff() + multIndirection[i][multIndirection[i].length - 1].getCoeff()));
  1567.         }
  1568.     }

  1569.     /** Compute n<sup>th</sup> root of a derivative structure.
  1570.      * @param operand array holding the operand
  1571.      * @param operandOffset offset of the operand in its array
  1572.      * @param n order of the root
  1573.      * @param result array where result must be stored (for
  1574.      * n<sup>th</sup> root the result array <em>cannot</em> be the input
  1575.      * array)
  1576.      * @param resultOffset offset of the result in its array
  1577.      */
  1578.     public void rootN(final double[] operand, final int operandOffset, final int n,
  1579.                       final double[] result, final int resultOffset) {

  1580.         // create the function value and derivatives
  1581.         // [x^(1/n), (1/n)x^((1/n)-1), (1-n)/n^2x^((1/n)-2), ... ]
  1582.         double[] function = new double[1 + order];
  1583.         double xk;
  1584.         if (n == 2) {
  1585.             function[0] = FastMath.sqrt(operand[operandOffset]);
  1586.             xk          = 0.5 / function[0];
  1587.         } else if (n == 3) {
  1588.             function[0] = FastMath.cbrt(operand[operandOffset]);
  1589.             xk          = 1.0 / (3.0 * function[0] * function[0]);
  1590.         } else {
  1591.             function[0] = FastMath.pow(operand[operandOffset], 1.0 / n);
  1592.             xk          = 1.0 / (n * FastMath.pow(function[0], n - 1));
  1593.         }
  1594.         final double nReciprocal = 1.0 / n;
  1595.         final double xReciprocal = 1.0 / operand[operandOffset];
  1596.         for (int i = 1; i <= order; ++i) {
  1597.             function[i] = xk;
  1598.             xk *= xReciprocal * (nReciprocal - i);
  1599.         }

  1600.         // apply function composition
  1601.         compose(operand, operandOffset, function, result, resultOffset);

  1602.     }

  1603.     /** Compute n<sup>th</sup> root of a derivative structure.
  1604.      * @param operand array holding the operand
  1605.      * @param operandOffset offset of the operand in its array
  1606.      * @param n order of the root
  1607.      * @param result array where result must be stored (for
  1608.      * n<sup>th</sup> root the result array <em>cannot</em> be the input
  1609.      * array)
  1610.      * @param resultOffset offset of the result in its array
  1611.      * @param <T> the type of the function parameters and value
  1612.      */
  1613.     public <T extends CalculusFieldElement<T>> void rootN(final T[] operand, final int operandOffset, final int n,
  1614.                                                           final T[] result, final int resultOffset) {

  1615.         final Field<T> field = operand[operandOffset].getField();

  1616.         // create the function value and derivatives
  1617.         // [x^(1/n), (1/n)x^((1/n)-1), (1-n)/n^2x^((1/n)-2), ... ]
  1618.         T[] function = MathArrays.buildArray(field, 1 + order);
  1619.         T xk;
  1620.         if (n == 2) {
  1621.             function[0] = operand[operandOffset].sqrt();
  1622.             xk          = function[0].add(function[0]).reciprocal();
  1623.         } else if (n == 3) {
  1624.             function[0] = operand[operandOffset].cbrt();
  1625.             xk          = function[0].multiply(function[0]).multiply(3).reciprocal();
  1626.         } else {
  1627.             function[0] = operand[operandOffset].pow(1.0 / n);
  1628.             xk          = function[0].pow(n - 1).multiply(n).reciprocal();
  1629.         }
  1630.         final double nReciprocal = 1.0 / n;
  1631.         final T      xReciprocal = operand[operandOffset].reciprocal();
  1632.         for (int i = 1; i <= order; ++i) {
  1633.             function[i] = xk;
  1634.             xk = xk.multiply(xReciprocal.multiply(nReciprocal - i));
  1635.         }

  1636.         // apply function composition
  1637.         compose(operand, operandOffset, function, result, resultOffset);

  1638.     }

  1639.     /** Compute exponential of a derivative structure.
  1640.      * @param operand array holding the operand
  1641.      * @param operandOffset offset of the operand in its array
  1642.      * @param result array where result must be stored (for
  1643.      * exponential the result array <em>cannot</em> be the input
  1644.      * array)
  1645.      * @param resultOffset offset of the result in its array
  1646.      */
  1647.     public void exp(final double[] operand, final int operandOffset,
  1648.                     final double[] result, final int resultOffset) {

  1649.         // create the function value and derivatives
  1650.         double[] function = new double[1 + order];
  1651.         Arrays.fill(function, FastMath.exp(operand[operandOffset]));

  1652.         // apply function composition
  1653.         compose(operand, operandOffset, function, result, resultOffset);

  1654.     }

  1655.     /** Compute exponential of a derivative structure.
  1656.      * @param operand array holding the operand
  1657.      * @param operandOffset offset of the operand in its array
  1658.      * @param result array where result must be stored (for
  1659.      * exponential the result array <em>cannot</em> be the input
  1660.      * array)
  1661.      * @param resultOffset offset of the result in its array
  1662.      * @param <T> the type of the function parameters and value
  1663.      */
  1664.     public <T extends CalculusFieldElement<T>> void exp(final T[] operand, final int operandOffset,
  1665.                                                         final T[] result, final int resultOffset) {

  1666.         final Field<T> field = operand[operandOffset].getField();

  1667.         // create the function value and derivatives
  1668.         T[] function = MathArrays.buildArray(field, 1 + order);
  1669.         Arrays.fill(function, operand[operandOffset].exp());

  1670.         // apply function composition
  1671.         compose(operand, operandOffset, function, result, resultOffset);

  1672.     }

  1673.     /** Compute exp(x) - 1 of a derivative structure.
  1674.      * @param operand array holding the operand
  1675.      * @param operandOffset offset of the operand in its array
  1676.      * @param result array where result must be stored (for
  1677.      * exponential the result array <em>cannot</em> be the input
  1678.      * array)
  1679.      * @param resultOffset offset of the result in its array
  1680.      */
  1681.     public void expm1(final double[] operand, final int operandOffset,
  1682.                       final double[] result, final int resultOffset) {

  1683.         // create the function value and derivatives
  1684.         double[] function = new double[1 + order];
  1685.         function[0] = FastMath.expm1(operand[operandOffset]);
  1686.         Arrays.fill(function, 1, 1 + order, FastMath.exp(operand[operandOffset]));

  1687.         // apply function composition
  1688.         compose(operand, operandOffset, function, result, resultOffset);

  1689.     }

  1690.     /** Compute exp(x) - 1 of a derivative structure.
  1691.      * @param operand array holding the operand
  1692.      * @param operandOffset offset of the operand in its array
  1693.      * @param result array where result must be stored (for
  1694.      * exponential the result array <em>cannot</em> be the input
  1695.      * array)
  1696.      * @param resultOffset offset of the result in its array
  1697.      * @param <T> the type of the function parameters and value
  1698.      */
  1699.     public <T extends CalculusFieldElement<T>> void expm1(final T[] operand, final int operandOffset,
  1700.                                                           final T[] result, final int resultOffset) {

  1701.         final Field<T> field = operand[operandOffset].getField();

  1702.         // create the function value and derivatives
  1703.         T[] function = MathArrays.buildArray(field, 1 + order);
  1704.         function[0] = operand[operandOffset].expm1();
  1705.         Arrays.fill(function, 1, 1 + order, operand[operandOffset].exp());

  1706.         // apply function composition
  1707.         compose(operand, operandOffset, function, result, resultOffset);

  1708.     }

  1709.     /** Compute natural logarithm of a derivative structure.
  1710.      * @param operand array holding the operand
  1711.      * @param operandOffset offset of the operand in its array
  1712.      * @param result array where result must be stored (for
  1713.      * logarithm the result array <em>cannot</em> be the input
  1714.      * array)
  1715.      * @param resultOffset offset of the result in its array
  1716.      */
  1717.     public void log(final double[] operand, final int operandOffset,
  1718.                     final double[] result, final int resultOffset) {

  1719.         // create the function value and derivatives
  1720.         double[] function = new double[1 + order];
  1721.         function[0] = FastMath.log(operand[operandOffset]);
  1722.         if (order > 0) {
  1723.             double inv = 1.0 / operand[operandOffset];
  1724.             double xk  = inv;
  1725.             for (int i = 1; i <= order; ++i) {
  1726.                 function[i] = xk;
  1727.                 xk *= -i * inv;
  1728.             }
  1729.         }

  1730.         // apply function composition
  1731.         compose(operand, operandOffset, function, result, resultOffset);

  1732.     }

  1733.     /** Compute natural logarithm of a derivative structure.
  1734.      * @param operand array holding the operand
  1735.      * @param operandOffset offset of the operand in its array
  1736.      * @param result array where result must be stored (for
  1737.      * logarithm the result array <em>cannot</em> be the input
  1738.      * array)
  1739.      * @param resultOffset offset of the result in its array
  1740.      * @param <T> the type of the function parameters and value
  1741.      */
  1742.     public <T extends CalculusFieldElement<T>> void log(final T[] operand, final int operandOffset,
  1743.                                                         final T[] result, final int resultOffset) {

  1744.         final Field<T> field = operand[operandOffset].getField();

  1745.         // create the function value and derivatives
  1746.         T[] function = MathArrays.buildArray(field, 1 + order);
  1747.         function[0] = operand[operandOffset].log();
  1748.         if (order > 0) {
  1749.             T inv = operand[operandOffset].reciprocal();
  1750.             T xk  = inv;
  1751.             for (int i = 1; i <= order; ++i) {
  1752.                 function[i] = xk;
  1753.                 xk = xk.multiply(inv.multiply(-i));
  1754.             }
  1755.         }

  1756.         // apply function composition
  1757.         compose(operand, operandOffset, function, result, resultOffset);

  1758.     }

  1759.     /** Computes shifted logarithm of a derivative structure.
  1760.      * @param operand array holding the operand
  1761.      * @param operandOffset offset of the operand in its array
  1762.      * @param result array where result must be stored (for
  1763.      * shifted logarithm the result array <em>cannot</em> be the input array)
  1764.      * @param resultOffset offset of the result in its array
  1765.      */
  1766.     public void log1p(final double[] operand, final int operandOffset,
  1767.                       final double[] result, final int resultOffset) {

  1768.         // create the function value and derivatives
  1769.         double[] function = new double[1 + order];
  1770.         function[0] = FastMath.log1p(operand[operandOffset]);
  1771.         if (order > 0) {
  1772.             double inv = 1.0 / (1.0 + operand[operandOffset]);
  1773.             double xk  = inv;
  1774.             for (int i = 1; i <= order; ++i) {
  1775.                 function[i] = xk;
  1776.                 xk *= -i * inv;
  1777.             }
  1778.         }

  1779.         // apply function composition
  1780.         compose(operand, operandOffset, function, result, resultOffset);

  1781.     }

  1782.     /** Computes shifted logarithm of a derivative structure.
  1783.      * @param operand array holding the operand
  1784.      * @param operandOffset offset of the operand in its array
  1785.      * @param result array where result must be stored (for
  1786.      * shifted logarithm the result array <em>cannot</em> be the input array)
  1787.      * @param resultOffset offset of the result in its array
  1788.      * @param <T> the type of the function parameters and value
  1789.      */
  1790.     public <T extends CalculusFieldElement<T>> void log1p(final T[] operand, final int operandOffset,
  1791.                                                           final T[] result, final int resultOffset) {

  1792.         final Field<T> field = operand[operandOffset].getField();

  1793.         // create the function value and derivatives
  1794.         T[] function = MathArrays.buildArray(field, 1 + order);
  1795.         function[0] = operand[operandOffset].log1p();
  1796.         if (order > 0) {
  1797.             T inv = operand[operandOffset].add(1).reciprocal();
  1798.             T xk  = inv;
  1799.             for (int i = 1; i <= order; ++i) {
  1800.                 function[i] = xk;
  1801.                 xk = xk.multiply(inv.multiply(-i));
  1802.             }
  1803.         }

  1804.         // apply function composition
  1805.         compose(operand, operandOffset, function, result, resultOffset);

  1806.     }

  1807.     /** Computes base 10 logarithm of a derivative structure.
  1808.      * @param operand array holding the operand
  1809.      * @param operandOffset offset of the operand in its array
  1810.      * @param result array where result must be stored (for
  1811.      * base 10 logarithm the result array <em>cannot</em> be the input array)
  1812.      * @param resultOffset offset of the result in its array
  1813.      */
  1814.     public void log10(final double[] operand, final int operandOffset,
  1815.                       final double[] result, final int resultOffset) {

  1816.         // create the function value and derivatives
  1817.         double[] function = new double[1 + order];
  1818.         function[0] = FastMath.log10(operand[operandOffset]);
  1819.         if (order > 0) {
  1820.             double inv = 1.0 / operand[operandOffset];
  1821.             double xk  = inv / FastMath.log(10.0);
  1822.             for (int i = 1; i <= order; ++i) {
  1823.                 function[i] = xk;
  1824.                 xk *= -i * inv;
  1825.             }
  1826.         }

  1827.         // apply function composition
  1828.         compose(operand, operandOffset, function, result, resultOffset);

  1829.     }

  1830.     /** Computes base 10 logarithm of a derivative structure.
  1831.      * @param operand array holding the operand
  1832.      * @param operandOffset offset of the operand in its array
  1833.      * @param result array where result must be stored (for
  1834.      * base 10 logarithm the result array <em>cannot</em> be the input array)
  1835.      * @param resultOffset offset of the result in its array
  1836.      * @param <T> the type of the function parameters and value
  1837.      */
  1838.     public <T extends CalculusFieldElement<T>> void log10(final T[] operand, final int operandOffset,
  1839.                                                           final T[] result, final int resultOffset) {

  1840.         final Field<T> field = operand[operandOffset].getField();

  1841.         // create the function value and derivatives
  1842.         T[] function = MathArrays.buildArray(field, 1 + order);
  1843.         function[0] = operand[operandOffset].log10();
  1844.         if (order > 0) {
  1845.             T inv = operand[operandOffset].reciprocal();
  1846.             T xk  = inv.multiply(1.0 / FastMath.log(10.0));
  1847.             for (int i = 1; i <= order; ++i) {
  1848.                 function[i] = xk;
  1849.                 xk = xk.multiply(inv.multiply(-i));
  1850.             }
  1851.         }

  1852.         // apply function composition
  1853.         compose(operand, operandOffset, function, result, resultOffset);

  1854.     }

  1855.     /** Compute cosine of a derivative structure.
  1856.      * @param operand array holding the operand
  1857.      * @param operandOffset offset of the operand in its array
  1858.      * @param result array where result must be stored (for
  1859.      * cosine the result array <em>cannot</em> be the input
  1860.      * array)
  1861.      * @param resultOffset offset of the result in its array
  1862.      */
  1863.     public void cos(final double[] operand, final int operandOffset,
  1864.                     final double[] result, final int resultOffset) {

  1865.         // create the function value and derivatives
  1866.         double[] function = new double[1 + order];
  1867.         final SinCos sinCos = FastMath.sinCos(operand[operandOffset]);
  1868.         function[0] = sinCos.cos();
  1869.         if (order > 0) {
  1870.             function[1] = -sinCos.sin();
  1871.             for (int i = 2; i <= order; ++i) {
  1872.                 function[i] = -function[i - 2];
  1873.             }
  1874.         }

  1875.         // apply function composition
  1876.         compose(operand, operandOffset, function, result, resultOffset);

  1877.     }

  1878.     /** Compute cosine of a derivative structure.
  1879.      * @param operand array holding the operand
  1880.      * @param operandOffset offset of the operand in its array
  1881.      * @param result array where result must be stored (for
  1882.      * cosine the result array <em>cannot</em> be the input
  1883.      * array)
  1884.      * @param resultOffset offset of the result in its array
  1885.      * @param <T> the type of the function parameters and value
  1886.      */
  1887.     public <T extends CalculusFieldElement<T>> void cos(final T[] operand, final int operandOffset,
  1888.                                                         final T[] result, final int resultOffset) {

  1889.         final Field<T> field = operand[operandOffset].getField();

  1890.         // create the function value and derivatives
  1891.         T[] function = MathArrays.buildArray(field, 1 + order);
  1892.         final FieldSinCos<T> sinCos = FastMath.sinCos(operand[operandOffset]);
  1893.         function[0] = sinCos.cos();
  1894.         if (order > 0) {
  1895.             function[1] = sinCos.sin().negate();
  1896.             if (order > 1) {
  1897.                 function[2] = sinCos.cos().negate();
  1898.                 if (order > 2) {
  1899.                     function[3] = sinCos.sin();
  1900.                     for (int i = 4; i <= order; ++i) {
  1901.                         function[i] = function[i - 4];
  1902.                     }
  1903.                 }
  1904.             }
  1905.         }

  1906.         // apply function composition
  1907.         compose(operand, operandOffset, function, result, resultOffset);

  1908.     }

  1909.     /** Compute sine of a derivative structure.
  1910.      * @param operand array holding the operand
  1911.      * @param operandOffset offset of the operand in its array
  1912.      * @param result array where result must be stored (for
  1913.      * sine the result array <em>cannot</em> be the input
  1914.      * array)
  1915.      * @param resultOffset offset of the result in its array
  1916.      */
  1917.     public void sin(final double[] operand, final int operandOffset,
  1918.                     final double[] result, final int resultOffset) {

  1919.         // create the function value and derivatives
  1920.         double[] function = new double[1 + order];
  1921.         final SinCos sinCos = FastMath.sinCos(operand[operandOffset]);
  1922.         function[0] = sinCos.sin();
  1923.         if (order > 0) {
  1924.             function[1] = sinCos.cos();
  1925.             for (int i = 2; i <= order; ++i) {
  1926.                 function[i] = -function[i - 2];
  1927.             }
  1928.         }

  1929.         // apply function composition
  1930.         compose(operand, operandOffset, function, result, resultOffset);

  1931.     }

  1932.     /** Compute sine of a derivative structure.
  1933.      * @param operand array holding the operand
  1934.      * @param operandOffset offset of the operand in its array
  1935.      * @param result array where result must be stored (for
  1936.      * sine the result array <em>cannot</em> be the input
  1937.      * array)
  1938.      * @param resultOffset offset of the result in its array
  1939.      * @param <T> the type of the function parameters and value
  1940.      */
  1941.     public <T extends CalculusFieldElement<T>> void sin(final T[] operand, final int operandOffset,
  1942.                                                         final T[] result, final int resultOffset) {

  1943.         final Field<T> field = operand[operandOffset].getField();

  1944.         // create the function value and derivatives
  1945.         T[] function = MathArrays.buildArray(field, 1 + order);
  1946.         final FieldSinCos<T> sinCos = FastMath.sinCos(operand[operandOffset]);
  1947.         function[0] = sinCos.sin();
  1948.         if (order > 0) {
  1949.             function[1] = sinCos.cos();
  1950.             if (order > 1) {
  1951.                 function[2] = sinCos.sin().negate();
  1952.                 if (order > 2) {
  1953.                     function[3] = sinCos.cos().negate();
  1954.                     for (int i = 4; i <= order; ++i) {
  1955.                         function[i] = function[i - 4];
  1956.                     }
  1957.                 }
  1958.             }
  1959.         }

  1960.         // apply function composition
  1961.         compose(operand, operandOffset, function, result, resultOffset);

  1962.     }

  1963.     /** Compute combined sine and cosine of a derivative structure.
  1964.      * @param operand array holding the operand
  1965.      * @param operandOffset offset of the operand in its array
  1966.      * @param sin array where sine must be stored (for
  1967.      * sine the result array <em>cannot</em> be the input
  1968.      * array)
  1969.      * @param sinOffset offset of the result in its array
  1970.      * @param cos array where cosine must be stored (for
  1971.      * cosine the result array <em>cannot</em> be the input
  1972.      * array)
  1973.      * @param cosOffset offset of the result in its array
  1974.      * @since 1.4
  1975.      */
  1976.     public void sinCos(final double[] operand, final int operandOffset,
  1977.                        final double[] sin, final int sinOffset,
  1978.                        final double[] cos, final int cosOffset) {

  1979.         // create the function value and derivatives
  1980.         double[] functionSin = new double[1 + order];
  1981.         double[] functionCos = new double[1 + order];
  1982.         final SinCos sinCos = FastMath.sinCos(operand[operandOffset]);
  1983.         functionSin[0] = sinCos.sin();
  1984.         functionCos[0] = sinCos.cos();
  1985.         if (order > 0) {
  1986.             functionSin[1] =  sinCos.cos();
  1987.             functionCos[1] = -sinCos.sin();
  1988.             for (int i = 2; i <= order; ++i) {
  1989.                 functionSin[i] = -functionSin[i - 2];
  1990.                 functionCos[i] = -functionCos[i - 2];
  1991.             }
  1992.         }

  1993.         // apply function composition
  1994.         compose(operand, operandOffset, functionSin, sin, sinOffset);
  1995.         compose(operand, operandOffset, functionCos, cos, cosOffset);

  1996.     }

  1997.     /** Compute combined sine and cosine of a derivative structure.
  1998.      * @param operand array holding the operand
  1999.      * @param operandOffset offset of the operand in its array
  2000.      * @param sin array where sine must be stored (for
  2001.      * sine the result array <em>cannot</em> be the input
  2002.      * array)
  2003.      * @param sinOffset offset of the result in its array
  2004.      * @param cos array where cosine must be stored (for
  2005.      * cosine the result array <em>cannot</em> be the input
  2006.      * array)
  2007.      * @param cosOffset offset of the result in its array
  2008.      * @param <T> the type of the function parameters and value
  2009.      * @since 1.4
  2010.      */
  2011.     public <T extends CalculusFieldElement<T>> void sinCos(final T[] operand, final int operandOffset,
  2012.                                                            final T[] sin, final int sinOffset,
  2013.                                                            final T[] cos, final int cosOffset) {

  2014.         final Field<T> field = operand[operandOffset].getField();

  2015.         // create the function value and derivatives
  2016.         T[] functionSin = MathArrays.buildArray(field, 1 + order);
  2017.         T[] functionCos = MathArrays.buildArray(field, 1 + order);
  2018.         final FieldSinCos<T> sinCos = FastMath.sinCos(operand[operandOffset]);
  2019.         functionCos[0] = sinCos.cos();
  2020.         if (order > 0) {
  2021.             functionCos[1] = sinCos.sin().negate();
  2022.             if (order > 1) {
  2023.                 functionCos[2] = sinCos.cos().negate();
  2024.                 if (order > 2) {
  2025.                     functionCos[3] = sinCos.sin();
  2026.                     for (int i = 4; i <= order; ++i) {
  2027.                         functionCos[i] = functionCos[i - 4];
  2028.                     }
  2029.                 }
  2030.             }
  2031.         }
  2032.         functionSin[0] = sinCos.sin();
  2033.         System.arraycopy(functionCos, 0, functionSin, 1, order);

  2034.         // apply function composition
  2035.         compose(operand, operandOffset, functionSin, sin, sinOffset);
  2036.         compose(operand, operandOffset, functionCos, cos, cosOffset);

  2037.     }

  2038.     /** Compute tangent of a derivative structure.
  2039.      * @param operand array holding the operand
  2040.      * @param operandOffset offset of the operand in its array
  2041.      * @param result array where result must be stored (for
  2042.      * tangent the result array <em>cannot</em> be the input
  2043.      * array)
  2044.      * @param resultOffset offset of the result in its array
  2045.      */
  2046.     public void tan(final double[] operand, final int operandOffset,
  2047.                     final double[] result, final int resultOffset) {

  2048.         // create the function value and derivatives
  2049.         final double[] function = new double[1 + order];
  2050.         final double t = FastMath.tan(operand[operandOffset]);
  2051.         function[0] = t;

  2052.         if (order > 0) {

  2053.             // the nth order derivative of tan has the form:
  2054.             // dn(tan(x)/dxn = P_n(tan(x))
  2055.             // where P_n(t) is a degree n+1 polynomial with same parity as n+1
  2056.             // P_0(t) = t, P_1(t) = 1 + t^2, P_2(t) = 2 t (1 + t^2) ...
  2057.             // the general recurrence relation for P_n is:
  2058.             // P_n(x) = (1+t^2) P_(n-1)'(t)
  2059.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2060.             final double[] p = new double[order + 2];
  2061.             p[1] = 1;
  2062.             final double t2 = t * t;
  2063.             for (int n = 1; n <= order; ++n) {

  2064.                 // update and evaluate polynomial P_n(t)
  2065.                 double v = 0;
  2066.                 p[n + 1] = n * p[n];
  2067.                 for (int k = n + 1; k >= 0; k -= 2) {
  2068.                     v = v * t2 + p[k];
  2069.                     if (k > 2) {
  2070.                         p[k - 2] = (k - 1) * p[k - 1] + (k - 3) * p[k - 3];
  2071.                     } else if (k == 2) {
  2072.                         p[0] = p[1];
  2073.                     }
  2074.                 }
  2075.                 if ((n & 0x1) == 0) {
  2076.                     v *= t;
  2077.                 }

  2078.                 function[n] = v;

  2079.             }
  2080.         }

  2081.         // apply function composition
  2082.         compose(operand, operandOffset, function, result, resultOffset);

  2083.     }

  2084.     /** Compute tangent of a derivative structure.
  2085.      * @param operand array holding the operand
  2086.      * @param operandOffset offset of the operand in its array
  2087.      * @param result array where result must be stored (for
  2088.      * tangent the result array <em>cannot</em> be the input
  2089.      * array)
  2090.      * @param resultOffset offset of the result in its array
  2091.      * @param <T> the type of the function parameters and value
  2092.      */
  2093.     public <T extends CalculusFieldElement<T>> void tan(final T[] operand, final int operandOffset,
  2094.                                                         final T[] result, final int resultOffset) {

  2095.         final Field<T> field = operand[operandOffset].getField();

  2096.         // create the function value and derivatives
  2097.         T[] function = MathArrays.buildArray(field, 1 + order);
  2098.         final T t = operand[operandOffset].tan();
  2099.         function[0] = t;

  2100.         if (order > 0) {

  2101.             // the nth order derivative of tan has the form:
  2102.             // dn(tan(x)/dxn = P_n(tan(x))
  2103.             // where P_n(t) is a degree n+1 polynomial with same parity as n+1
  2104.             // P_0(t) = t, P_1(t) = 1 + t^2, P_2(t) = 2 t (1 + t^2) ...
  2105.             // the general recurrence relation for P_n is:
  2106.             // P_n(x) = (1+t^2) P_(n-1)'(t)
  2107.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2108.             final T[] p = MathArrays.buildArray(field, order + 2);
  2109.             p[1] = field.getOne();
  2110.             final T t2 = t.multiply(t);
  2111.             for (int n = 1; n <= order; ++n) {

  2112.                 // update and evaluate polynomial P_n(t)
  2113.                 T v = field.getZero();
  2114.                 p[n + 1] = p[n].multiply(n);
  2115.                 for (int k = n + 1; k >= 0; k -= 2) {
  2116.                     v = v.multiply(t2).add(p[k]);
  2117.                     if (k > 2) {
  2118.                         p[k - 2] = p[k - 1].multiply(k - 1).add(p[k - 3].multiply(k - 3));
  2119.                     } else if (k == 2) {
  2120.                         p[0] = p[1];
  2121.                     }
  2122.                 }
  2123.                 if ((n & 0x1) == 0) {
  2124.                     v = v.multiply(t);
  2125.                 }

  2126.                 function[n] = v;

  2127.             }
  2128.         }

  2129.         // apply function composition
  2130.         compose(operand, operandOffset, function, result, resultOffset);

  2131.     }

  2132.     /** Compute arc cosine of a derivative structure.
  2133.      * @param operand array holding the operand
  2134.      * @param operandOffset offset of the operand in its array
  2135.      * @param result array where result must be stored (for
  2136.      * arc cosine the result array <em>cannot</em> be the input
  2137.      * array)
  2138.      * @param resultOffset offset of the result in its array
  2139.      */
  2140.     public void acos(final double[] operand, final int operandOffset,
  2141.                      final double[] result, final int resultOffset) {

  2142.         // create the function value and derivatives
  2143.         double[] function = new double[1 + order];
  2144.         final double x = operand[operandOffset];
  2145.         function[0] = FastMath.acos(x);
  2146.         if (order > 0) {
  2147.             // the nth order derivative of acos has the form:
  2148.             // dn(acos(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
  2149.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  2150.             // P_1(x) = -1, P_2(x) = -x, P_3(x) = -2x^2 - 1 ...
  2151.             // the general recurrence relation for P_n is:
  2152.             // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
  2153.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2154.             final double[] p = new double[order];
  2155.             p[0] = -1;
  2156.             final double x2    = x * x;
  2157.             final double f     = 1.0 / (1 - x2);
  2158.             double coeff = FastMath.sqrt(f);
  2159.             function[1] = coeff * p[0];
  2160.             for (int n = 2; n <= order; ++n) {

  2161.                 // update and evaluate polynomial P_n(x)
  2162.                 double v = 0;
  2163.                 p[n - 1] = (n - 1) * p[n - 2];
  2164.                 for (int k = n - 1; k >= 0; k -= 2) {
  2165.                     v = v * x2 + p[k];
  2166.                     if (k > 2) {
  2167.                         p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3];
  2168.                     } else if (k == 2) {
  2169.                         p[0] = p[1];
  2170.                     }
  2171.                 }
  2172.                 if ((n & 0x1) == 0) {
  2173.                     v *= x;
  2174.                 }

  2175.                 coeff *= f;
  2176.                 function[n] = coeff * v;

  2177.             }
  2178.         }

  2179.         // apply function composition
  2180.         compose(operand, operandOffset, function, result, resultOffset);

  2181.     }

  2182.     /** Compute arc cosine of a derivative structure.
  2183.      * @param operand array holding the operand
  2184.      * @param operandOffset offset of the operand in its array
  2185.      * @param result array where result must be stored (for
  2186.      * arc cosine the result array <em>cannot</em> be the input
  2187.      * array)
  2188.      * @param resultOffset offset of the result in its array
  2189.      * @param <T> the type of the function parameters and value
  2190.      */
  2191.     public <T extends CalculusFieldElement<T>> void acos(final T[] operand, final int operandOffset,
  2192.                                                          final T[] result, final int resultOffset) {

  2193.         final Field<T> field = operand[operandOffset].getField();

  2194.         // create the function value and derivatives
  2195.         T[] function = MathArrays.buildArray(field, 1 + order);
  2196.         final T x = operand[operandOffset];
  2197.         function[0] = x.acos();
  2198.         if (order > 0) {
  2199.             // the nth order derivative of acos has the form:
  2200.             // dn(acos(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
  2201.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  2202.             // P_1(x) = -1, P_2(x) = -x, P_3(x) = -2x^2 - 1 ...
  2203.             // the general recurrence relation for P_n is:
  2204.             // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
  2205.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2206.             final T[] p = MathArrays.buildArray(field, order);
  2207.             p[0] = field.getOne().negate();
  2208.             final T x2    = x.square();
  2209.             final T f     = x2.subtract(1).negate().reciprocal();
  2210.             T coeff = f.sqrt();
  2211.             function[1] = coeff.multiply(p[0]);
  2212.             for (int n = 2; n <= order; ++n) {

  2213.                 // update and evaluate polynomial P_n(x)
  2214.                 T v = field.getZero();
  2215.                 p[n - 1] = p[n - 2].multiply(n - 1);
  2216.                 for (int k = n - 1; k >= 0; k -= 2) {
  2217.                     v = v.multiply(x2).add(p[k]);
  2218.                     if (k > 2) {
  2219.                         p[k - 2] = p[k - 1].multiply(k - 1).add(p[k - 3].multiply(2 * n - k));
  2220.                     } else if (k == 2) {
  2221.                         p[0] = p[1];
  2222.                     }
  2223.                 }
  2224.                 if ((n & 0x1) == 0) {
  2225.                     v = v.multiply(x);
  2226.                 }

  2227.                 coeff = coeff.multiply(f);
  2228.                 function[n] = coeff.multiply(v);

  2229.             }
  2230.         }

  2231.         // apply function composition
  2232.         compose(operand, operandOffset, function, result, resultOffset);

  2233.     }

  2234.     /** Compute arc sine of a derivative structure.
  2235.      * @param operand array holding the operand
  2236.      * @param operandOffset offset of the operand in its array
  2237.      * @param result array where result must be stored (for
  2238.      * arc sine the result array <em>cannot</em> be the input
  2239.      * array)
  2240.      * @param resultOffset offset of the result in its array
  2241.      */
  2242.     public void asin(final double[] operand, final int operandOffset,
  2243.                      final double[] result, final int resultOffset) {

  2244.         // create the function value and derivatives
  2245.         double[] function = new double[1 + order];
  2246.         final double x = operand[operandOffset];
  2247.         function[0] = FastMath.asin(x);
  2248.         if (order > 0) {
  2249.             // the nth order derivative of asin has the form:
  2250.             // dn(asin(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
  2251.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  2252.             // P_1(x) = 1, P_2(x) = x, P_3(x) = 2x^2 + 1 ...
  2253.             // the general recurrence relation for P_n is:
  2254.             // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
  2255.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2256.             final double[] p = new double[order];
  2257.             p[0] = 1;
  2258.             final double x2    = x * x;
  2259.             final double f     = 1.0 / (1 - x2);
  2260.             double coeff = FastMath.sqrt(f);
  2261.             function[1] = coeff * p[0];
  2262.             for (int n = 2; n <= order; ++n) {

  2263.                 // update and evaluate polynomial P_n(x)
  2264.                 double v = 0;
  2265.                 p[n - 1] = (n - 1) * p[n - 2];
  2266.                 for (int k = n - 1; k >= 0; k -= 2) {
  2267.                     v = v * x2 + p[k];
  2268.                     if (k > 2) {
  2269.                         p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3];
  2270.                     } else if (k == 2) {
  2271.                         p[0] = p[1];
  2272.                     }
  2273.                 }
  2274.                 if ((n & 0x1) == 0) {
  2275.                     v *= x;
  2276.                 }

  2277.                 coeff *= f;
  2278.                 function[n] = coeff * v;

  2279.             }
  2280.         }

  2281.         // apply function composition
  2282.         compose(operand, operandOffset, function, result, resultOffset);

  2283.     }

  2284.     /** Compute arc sine of a derivative structure.
  2285.      * @param operand array holding the operand
  2286.      * @param operandOffset offset of the operand in its array
  2287.      * @param result array where result must be stored (for
  2288.      * arc sine the result array <em>cannot</em> be the input
  2289.      * array)
  2290.      * @param resultOffset offset of the result in its array
  2291.      * @param <T> the type of the function parameters and value
  2292.      */
  2293.     public <T extends CalculusFieldElement<T>> void asin(final T[] operand, final int operandOffset,
  2294.                                                          final T[] result, final int resultOffset) {

  2295.         final Field<T> field = operand[operandOffset].getField();

  2296.         // create the function value and derivatives
  2297.         T[] function = MathArrays.buildArray(field, 1 + order);
  2298.         final T x = operand[operandOffset];
  2299.         function[0] = x.asin();
  2300.         if (order > 0) {
  2301.             // the nth order derivative of asin has the form:
  2302.             // dn(asin(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
  2303.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  2304.             // P_1(x) = 1, P_2(x) = x, P_3(x) = 2x^2 + 1 ...
  2305.             // the general recurrence relation for P_n is:
  2306.             // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
  2307.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2308.             final T[] p = MathArrays.buildArray(field, order);
  2309.             p[0] = field.getOne();
  2310.             final T x2    = x.square();
  2311.             final T f     = x2.subtract(1).negate().reciprocal();
  2312.             T coeff = f.sqrt();
  2313.             function[1] = coeff.multiply(p[0]);
  2314.             for (int n = 2; n <= order; ++n) {

  2315.                 // update and evaluate polynomial P_n(x)
  2316.                 T v = field.getZero();
  2317.                 p[n - 1] = p[n - 2].multiply(n - 1);
  2318.                 for (int k = n - 1; k >= 0; k -= 2) {
  2319.                     v = v.multiply(x2).add(p[k]);
  2320.                     if (k > 2) {
  2321.                         p[k - 2] = p[k - 1].multiply(k - 1).add(p[k - 3].multiply(2 * n - k));
  2322.                     } else if (k == 2) {
  2323.                         p[0] = p[1];
  2324.                     }
  2325.                 }
  2326.                 if ((n & 0x1) == 0) {
  2327.                     v = v.multiply(x);
  2328.                 }

  2329.                 coeff = coeff.multiply(f);
  2330.                 function[n] = coeff.multiply(v);

  2331.             }
  2332.         }

  2333.         // apply function composition
  2334.         compose(operand, operandOffset, function, result, resultOffset);

  2335.     }

  2336.     /** Compute arc tangent of a derivative structure.
  2337.      * @param operand array holding the operand
  2338.      * @param operandOffset offset of the operand in its array
  2339.      * @param result array where result must be stored (for
  2340.      * arc tangent the result array <em>cannot</em> be the input
  2341.      * array)
  2342.      * @param resultOffset offset of the result in its array
  2343.      */
  2344.     public void atan(final double[] operand, final int operandOffset,
  2345.                      final double[] result, final int resultOffset) {

  2346.         // create the function value and derivatives
  2347.         double[] function = new double[1 + order];
  2348.         final double x = operand[operandOffset];
  2349.         function[0] = FastMath.atan(x);
  2350.         if (order > 0) {
  2351.             // the nth order derivative of atan has the form:
  2352.             // dn(atan(x)/dxn = Q_n(x) / (1 + x^2)^n
  2353.             // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
  2354.             // Q_1(x) = 1, Q_2(x) = -2x, Q_3(x) = 6x^2 - 2 ...
  2355.             // the general recurrence relation for Q_n is:
  2356.             // Q_n(x) = (1+x^2) Q_(n-1)'(x) - 2(n-1) x Q_(n-1)(x)
  2357.             // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
  2358.             final double[] q = new double[order];
  2359.             q[0] = 1;
  2360.             final double x2    = x * x;
  2361.             final double f     = 1.0 / (1 + x2);
  2362.             double coeff = f;
  2363.             function[1] = coeff * q[0];
  2364.             for (int n = 2; n <= order; ++n) {

  2365.                 // update and evaluate polynomial Q_n(x)
  2366.                 double v = 0;
  2367.                 q[n - 1] = -n * q[n - 2];
  2368.                 for (int k = n - 1; k >= 0; k -= 2) {
  2369.                     v = v * x2 + q[k];
  2370.                     if (k > 2) {
  2371.                         q[k - 2] = (k - 1) * q[k - 1] + (k - 1 - 2 * n) * q[k - 3];
  2372.                     } else if (k == 2) {
  2373.                         q[0] = q[1];
  2374.                     }
  2375.                 }
  2376.                 if ((n & 0x1) == 0) {
  2377.                     v *= x;
  2378.                 }

  2379.                 coeff *= f;
  2380.                 function[n] = coeff * v;

  2381.             }
  2382.         }

  2383.         // apply function composition
  2384.         compose(operand, operandOffset, function, result, resultOffset);

  2385.     }

  2386.     /** Compute arc tangent of a derivative structure.
  2387.      * @param operand array holding the operand
  2388.      * @param operandOffset offset of the operand in its array
  2389.      * @param result array where result must be stored (for
  2390.      * arc tangent the result array <em>cannot</em> be the input
  2391.      * array)
  2392.      * @param resultOffset offset of the result in its array
  2393.      * @param <T> the type of the function parameters and value
  2394.      */
  2395.     public <T extends CalculusFieldElement<T>> void atan(final T[] operand, final int operandOffset,
  2396.                                                          final T[] result, final int resultOffset) {

  2397.         final Field<T> field = operand[operandOffset].getField();

  2398.         // create the function value and derivatives
  2399.         T[] function = MathArrays.buildArray(field, 1 + order);
  2400.         final T x = operand[operandOffset];
  2401.         function[0] = x.atan();
  2402.         if (order > 0) {
  2403.             // the nth order derivative of atan has the form:
  2404.             // dn(atan(x)/dxn = Q_n(x) / (1 + x^2)^n
  2405.             // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
  2406.             // Q_1(x) = 1, Q_2(x) = -2x, Q_3(x) = 6x^2 - 2 ...
  2407.             // the general recurrence relation for Q_n is:
  2408.             // Q_n(x) = (1+x^2) Q_(n-1)'(x) - 2(n-1) x Q_(n-1)(x)
  2409.             // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
  2410.             final T[] q = MathArrays.buildArray(field, order);
  2411.             q[0] = field.getOne();
  2412.             final T x2    = x.square();
  2413.             final T f     = x2.add(1).reciprocal();
  2414.             T coeff = f;
  2415.             function[1] = coeff.multiply(q[0]);
  2416.             for (int n = 2; n <= order; ++n) {

  2417.                 // update and evaluate polynomial Q_n(x)
  2418.                 T v = field.getZero();
  2419.                 q[n - 1] = q[n - 2].multiply(-n);
  2420.                 for (int k = n - 1; k >= 0; k -= 2) {
  2421.                     v = v.multiply(x2).add(q[k]);
  2422.                     if (k > 2) {
  2423.                         q[k - 2] = q[k - 1].multiply(k - 1).add(q[k - 3].multiply(k - 1 - 2 * n));
  2424.                     } else if (k == 2) {
  2425.                         q[0] = q[1];
  2426.                     }
  2427.                 }
  2428.                 if ((n & 0x1) == 0) {
  2429.                     v = v.multiply(x);
  2430.                 }

  2431.                 coeff = coeff.multiply(f);
  2432.                 function[n] = coeff.multiply(v);

  2433.             }
  2434.         }

  2435.         // apply function composition
  2436.         compose(operand, operandOffset, function, result, resultOffset);

  2437.     }

  2438.     /** Compute two arguments arc tangent of a derivative structure.
  2439.      * @param y array holding the first operand
  2440.      * @param yOffset offset of the first operand in its array
  2441.      * @param x array holding the second operand
  2442.      * @param xOffset offset of the second operand in its array
  2443.      * @param result array where result must be stored (for
  2444.      * two arguments arc tangent the result array <em>cannot</em>
  2445.      * be the input array)
  2446.      * @param resultOffset offset of the result in its array
  2447.      */
  2448.     public void atan2(final double[] y, final int yOffset,
  2449.                       final double[] x, final int xOffset,
  2450.                       final double[] result, final int resultOffset) {

  2451.         // compute r = sqrt(x^2+y^2)
  2452.         double[] tmp1 = new double[getSize()];
  2453.         multiply(x, xOffset, x, xOffset, tmp1, 0);      // x^2
  2454.         double[] tmp2 = new double[getSize()];
  2455.         multiply(y, yOffset, y, yOffset, tmp2, 0);      // y^2
  2456.         add(tmp1, 0, tmp2, 0, tmp2, 0);                 // x^2 + y^2
  2457.         rootN(tmp2, 0, 2, tmp1, 0);                     // r = sqrt(x^2 + y^2)

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

  2459.             // compute atan2(y, x) = 2 atan(y / (r + x))
  2460.             add(tmp1, 0, x, xOffset, tmp2, 0);          // r + x
  2461.             divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r + x)
  2462.             atan(tmp1, 0, tmp2, 0);                     // atan(y / (r + x))
  2463.             for (int i = 0; i < tmp2.length; ++i) {
  2464.                 result[resultOffset + i] = 2 * tmp2[i]; // 2 * atan(y / (r + x))
  2465.             }

  2466.         } else {

  2467.             // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x))
  2468.             subtract(tmp1, 0, x, xOffset, tmp2, 0);     // r - x
  2469.             divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r - x)
  2470.             atan(tmp1, 0, tmp2, 0);                     // atan(y / (r - x))
  2471.             result[resultOffset] =
  2472.                     ((tmp2[0] <= 0) ? -FastMath.PI : FastMath.PI) - 2 * tmp2[0]; // +/-pi - 2 * atan(y / (r - x))
  2473.             for (int i = 1; i < tmp2.length; ++i) {
  2474.                 result[resultOffset + i] = -2 * tmp2[i]; // +/-pi - 2 * atan(y / (r - x))
  2475.             }

  2476.         }

  2477.         // fix value to take special cases (+0/+0, +0/-0, -0/+0, -0/-0, +/-infinity) correctly
  2478.         result[resultOffset] = FastMath.atan2(y[yOffset], x[xOffset]);

  2479.     }

  2480.     /** Compute two arguments arc tangent of a derivative structure.
  2481.      * @param y array holding the first operand
  2482.      * @param yOffset offset of the first operand in its array
  2483.      * @param x array holding the second operand
  2484.      * @param xOffset offset of the second operand in its array
  2485.      * @param result array where result must be stored (for
  2486.      * two arguments arc tangent the result array <em>cannot</em>
  2487.      * be the input array)
  2488.      * @param resultOffset offset of the result in its array
  2489.      * @param <T> the type of the function parameters and value
  2490.      */
  2491.     public <T extends CalculusFieldElement<T>> void atan2(final T[] y, final int yOffset,
  2492.                                                           final T[] x, final int xOffset,
  2493.                                                           final T[] result, final int resultOffset) {

  2494.         final Field<T> field = y[yOffset].getField();

  2495.         // compute r = sqrt(x^2+y^2)
  2496.         T[] tmp1 = MathArrays.buildArray(field, getSize());
  2497.         multiply(x, xOffset, x, xOffset, tmp1, 0);      // x^2
  2498.         T[] tmp2 = MathArrays.buildArray(field, getSize());
  2499.         multiply(y, yOffset, y, yOffset, tmp2, 0);      // y^2
  2500.         add(tmp1, 0, tmp2, 0, tmp2, 0);                 // x^2 + y^2
  2501.         rootN(tmp2, 0, 2, tmp1, 0);                     // r = sqrt(x^2 + y^2)

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

  2503.             // compute atan2(y, x) = 2 atan(y / (r + x))
  2504.             add(tmp1, 0, x, xOffset, tmp2, 0);          // r + x
  2505.             divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r + x)
  2506.             atan(tmp1, 0, tmp2, 0);                     // atan(y / (r + x))
  2507.             for (int i = 0; i < tmp2.length; ++i) {
  2508.                 result[resultOffset + i] = tmp2[i].add(tmp2[i]); // 2 * atan(y / (r + x))
  2509.             }

  2510.         } else {

  2511.             // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x))
  2512.             subtract(tmp1, 0, x, xOffset, tmp2, 0);     // r - x
  2513.             divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r - x)
  2514.             atan(tmp1, 0, tmp2, 0);                     // atan(y / (r - x))
  2515.             result[resultOffset] = tmp2[0].add(tmp2[0]).negate().
  2516.                                    add((tmp2[0].getReal() <= 0) ? -FastMath.PI : FastMath.PI); // +/-pi - 2 * atan(y / (r - x))
  2517.             for (int i = 1; i < tmp2.length; ++i) {
  2518.                 result[resultOffset + i] = tmp2[i].add(tmp2[i]).negate(); // +/-pi - 2 * atan(y / (r - x))
  2519.             }

  2520.         }

  2521.         // fix value to take special cases (+0/+0, +0/-0, -0/+0, -0/-0, +/-infinity) correctly
  2522.         result[resultOffset] = y[yOffset].atan2(x[xOffset]);

  2523.     }

  2524.     /** Compute hyperbolic cosine of a derivative structure.
  2525.      * @param operand array holding the operand
  2526.      * @param operandOffset offset of the operand in its array
  2527.      * @param result array where result must be stored (for
  2528.      * hyperbolic cosine the result array <em>cannot</em> be the input
  2529.      * array)
  2530.      * @param resultOffset offset of the result in its array
  2531.      */
  2532.     public void cosh(final double[] operand, final int operandOffset,
  2533.                      final double[] result, final int resultOffset) {

  2534.         // create the function value and derivatives
  2535.         double[] function = new double[1 + order];
  2536.         function[0] = FastMath.cosh(operand[operandOffset]);
  2537.         if (order > 0) {
  2538.             function[1] = FastMath.sinh(operand[operandOffset]);
  2539.             for (int i = 2; i <= order; ++i) {
  2540.                 function[i] = function[i - 2];
  2541.             }
  2542.         }

  2543.         // apply function composition
  2544.         compose(operand, operandOffset, function, result, resultOffset);

  2545.     }

  2546.     /** Compute hyperbolic cosine of a derivative structure.
  2547.      * @param operand array holding the operand
  2548.      * @param operandOffset offset of the operand in its array
  2549.      * @param result array where result must be stored (for
  2550.      * hyperbolic cosine the result array <em>cannot</em> be the input
  2551.      * array)
  2552.      * @param resultOffset offset of the result in its array
  2553.      * @param <T> the type of the function parameters and value
  2554.      */
  2555.     public <T extends CalculusFieldElement<T>> void cosh(final T[] operand, final int operandOffset,
  2556.                                                          final T[] result, final int resultOffset) {

  2557.         final Field<T> field = operand[operandOffset].getField();

  2558.         // create the function value and derivatives
  2559.         T[] function = MathArrays.buildArray(field, 1 + order);
  2560.         function[0] = operand[operandOffset].cosh();
  2561.         if (order > 0) {
  2562.             function[1] = operand[operandOffset].sinh();
  2563.             for (int i = 2; i <= order; ++i) {
  2564.                 function[i] = function[i - 2];
  2565.             }
  2566.         }

  2567.         // apply function composition
  2568.         compose(operand, operandOffset, function, result, resultOffset);

  2569.     }

  2570.     /** Compute hyperbolic sine of a derivative structure.
  2571.      * @param operand array holding the operand
  2572.      * @param operandOffset offset of the operand in its array
  2573.      * @param result array where result must be stored (for
  2574.      * hyperbolic sine the result array <em>cannot</em> be the input
  2575.      * array)
  2576.      * @param resultOffset offset of the result in its array
  2577.      */
  2578.     public void sinh(final double[] operand, final int operandOffset,
  2579.                      final double[] result, final int resultOffset) {

  2580.         // create the function value and derivatives
  2581.         double[] function = new double[1 + order];
  2582.         function[0] = FastMath.sinh(operand[operandOffset]);
  2583.         if (order > 0) {
  2584.             function[1] = FastMath.cosh(operand[operandOffset]);
  2585.             for (int i = 2; i <= order; ++i) {
  2586.                 function[i] = function[i - 2];
  2587.             }
  2588.         }

  2589.         // apply function composition
  2590.         compose(operand, operandOffset, function, result, resultOffset);

  2591.     }

  2592.     /** Compute hyperbolic sine of a derivative structure.
  2593.      * @param operand array holding the operand
  2594.      * @param operandOffset offset of the operand in its array
  2595.      * @param result array where result must be stored (for
  2596.      * hyperbolic sine the result array <em>cannot</em> be the input
  2597.      * array)
  2598.      * @param resultOffset offset of the result in its array
  2599.      * @param <T> the type of the function parameters and value
  2600.      */
  2601.     public <T extends CalculusFieldElement<T>> void sinh(final T[] operand, final int operandOffset,
  2602.                                                          final T[] result, final int resultOffset) {

  2603.         final Field<T> field = operand[operandOffset].getField();

  2604.         // create the function value and derivatives
  2605.         T[] function = MathArrays.buildArray(field, 1 + order);
  2606.         function[0] = operand[operandOffset].sinh();
  2607.         if (order > 0) {
  2608.             function[1] = operand[operandOffset].cosh();
  2609.             for (int i = 2; i <= order; ++i) {
  2610.                 function[i] = function[i - 2];
  2611.             }
  2612.         }

  2613.         // apply function composition
  2614.         compose(operand, operandOffset, function, result, resultOffset);

  2615.     }

  2616.     /** Compute combined hyperbolic sine and cosine of a derivative structure.
  2617.      * @param operand array holding the operand
  2618.      * @param operandOffset offset of the operand in its array
  2619.      * @param sinh array where hyperbolic sine must be stored (for
  2620.      * sine the result array <em>cannot</em> be the input
  2621.      * array)
  2622.      * @param sinhOffset offset of the result in its array
  2623.      * @param cosh array where hyperbolic <em>cannot</em> be the input
  2624.      * array)
  2625.      * @param coshOffset offset of the result in its array
  2626.      * @since 2.0
  2627.      */
  2628.     public void sinhCosh(final double[] operand, final int operandOffset,
  2629.                          final double[] sinh, final int sinhOffset,
  2630.                          final double[] cosh, final int coshOffset) {

  2631.         // create the function value and derivatives
  2632.         double[] functionSinh = new double[1 + order];
  2633.         double[] functionCosh = new double[1 + order];
  2634.         final SinhCosh sinhCosh = FastMath.sinhCosh(operand[operandOffset]);
  2635.         functionSinh[0] = sinhCosh.sinh();
  2636.         functionCosh[0] = sinhCosh.cosh();
  2637.         if (order > 0) {
  2638.             functionSinh[1] = sinhCosh.cosh();
  2639.             functionCosh[1] = sinhCosh.sinh();
  2640.             for (int i = 2; i <= order; ++i) {
  2641.                 functionSinh[i] = functionSinh[i - 2];
  2642.                 functionCosh[i] = functionCosh[i - 2];
  2643.             }
  2644.         }

  2645.         // apply function composition
  2646.         compose(operand, operandOffset, functionSinh, sinh, sinhOffset);
  2647.         compose(operand, operandOffset, functionCosh, cosh, coshOffset);

  2648.     }

  2649.     /** Compute combined hyperbolic sine and cosine of a derivative structure.
  2650.      * @param operand array holding the operand
  2651.      * @param operandOffset offset of the operand in its array
  2652.      * @param sinh array where hyperbolic sine must be stored (for
  2653.      * sine the result array <em>cannot</em> be the input
  2654.      * array)
  2655.      * @param sinhOffset offset of the result in its array
  2656.      * @param cosh array where hyperbolic cosine must be stored (for
  2657.      * cosine the result array <em>cannot</em> be the input
  2658.      * array)
  2659.      * @param coshOffset offset of the result in its array
  2660.      * @param <T> the type of the function parameters and value
  2661.      * @since 1.4
  2662.      */
  2663.     public <T extends CalculusFieldElement<T>> void sinhCosh(final T[] operand, final int operandOffset,
  2664.                                                              final T[] sinh, final int sinhOffset,
  2665.                                                              final T[] cosh, final int coshOffset) {

  2666.         final Field<T> field = operand[operandOffset].getField();

  2667.         // create the function value and derivatives
  2668.         T[] functionSinh = MathArrays.buildArray(field, 1 + order);
  2669.         T[] functionCosh = MathArrays.buildArray(field, 1 + order);
  2670.         final FieldSinhCosh<T> sinhCosh = FastMath.sinhCosh(operand[operandOffset]);
  2671.         functionSinh[0] = sinhCosh.sinh();
  2672.         functionCosh[0] = sinhCosh.cosh();
  2673.         for (int i = 1; i <= order; ++i) {
  2674.             functionSinh[i] = functionCosh[i - 1];
  2675.             functionCosh[i] = functionSinh[i - 1];
  2676.         }

  2677.         // apply function composition
  2678.         compose(operand, operandOffset, functionSinh, sinh, sinhOffset);
  2679.         compose(operand, operandOffset, functionCosh, cosh, coshOffset);

  2680.     }

  2681.     /** Compute hyperbolic tangent of a derivative structure.
  2682.      * @param operand array holding the operand
  2683.      * @param operandOffset offset of the operand in its array
  2684.      * @param result array where result must be stored (for
  2685.      * hyperbolic tangent the result array <em>cannot</em> be the input
  2686.      * array)
  2687.      * @param resultOffset offset of the result in its array
  2688.      */
  2689.     public void tanh(final double[] operand, final int operandOffset,
  2690.                      final double[] result, final int resultOffset) {

  2691.         // create the function value and derivatives
  2692.         final double[] function = new double[1 + order];
  2693.         final double t = FastMath.tanh(operand[operandOffset]);
  2694.         function[0] = t;

  2695.         if (order > 0) {

  2696.             // the nth order derivative of tanh has the form:
  2697.             // dn(tanh(x)/dxn = P_n(tanh(x))
  2698.             // where P_n(t) is a degree n+1 polynomial with same parity as n+1
  2699.             // P_0(t) = t, P_1(t) = 1 - t^2, P_2(t) = -2 t (1 - t^2) ...
  2700.             // the general recurrence relation for P_n is:
  2701.             // P_n(x) = (1-t^2) P_(n-1)'(t)
  2702.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2703.             final double[] p = new double[order + 2];
  2704.             p[1] = 1;
  2705.             final double t2 = t * t;
  2706.             for (int n = 1; n <= order; ++n) {

  2707.                 // update and evaluate polynomial P_n(t)
  2708.                 double v = 0;
  2709.                 p[n + 1] = -n * p[n];
  2710.                 for (int k = n + 1; k >= 0; k -= 2) {
  2711.                     v = v * t2 + p[k];
  2712.                     if (k > 2) {
  2713.                         p[k - 2] = (k - 1) * p[k - 1] - (k - 3) * p[k - 3];
  2714.                     } else if (k == 2) {
  2715.                         p[0] = p[1];
  2716.                     }
  2717.                 }
  2718.                 if ((n & 0x1) == 0) {
  2719.                     v *= t;
  2720.                 }

  2721.                 function[n] = v;

  2722.             }
  2723.         }

  2724.         // apply function composition
  2725.         compose(operand, operandOffset, function, result, resultOffset);

  2726.     }

  2727.     /** Compute hyperbolic tangent of a derivative structure.
  2728.      * @param operand array holding the operand
  2729.      * @param operandOffset offset of the operand in its array
  2730.      * @param result array where result must be stored (for
  2731.      * hyperbolic tangent the result array <em>cannot</em> be the input
  2732.      * array)
  2733.      * @param resultOffset offset of the result in its array
  2734.      * @param <T> the type of the function parameters and value
  2735.      */
  2736.     public <T extends CalculusFieldElement<T>> void tanh(final T[] operand, final int operandOffset,
  2737.                                                          final T[] result, final int resultOffset) {

  2738.         final Field<T> field = operand[operandOffset].getField();

  2739.         // create the function value and derivatives
  2740.         T[] function = MathArrays.buildArray(field, 1 + order);
  2741.         final T t = operand[operandOffset].tanh();
  2742.         function[0] = t;

  2743.         if (order > 0) {

  2744.             // the nth order derivative of tanh has the form:
  2745.             // dn(tanh(x)/dxn = P_n(tanh(x))
  2746.             // where P_n(t) is a degree n+1 polynomial with same parity as n+1
  2747.             // P_0(t) = t, P_1(t) = 1 - t^2, P_2(t) = -2 t (1 - t^2) ...
  2748.             // the general recurrence relation for P_n is:
  2749.             // P_n(x) = (1-t^2) P_(n-1)'(t)
  2750.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2751.             final T[] p = MathArrays.buildArray(field, order + 2);
  2752.             p[1] = field.getOne();
  2753.             final T t2 = t.multiply(t);
  2754.             for (int n = 1; n <= order; ++n) {

  2755.                 // update and evaluate polynomial P_n(t)
  2756.                 T v = field.getZero();
  2757.                 p[n + 1] = p[n].multiply(-n);
  2758.                 for (int k = n + 1; k >= 0; k -= 2) {
  2759.                     v = v.multiply(t2).add(p[k]);
  2760.                     if (k > 2) {
  2761.                         p[k - 2] = p[k - 1].multiply(k - 1).subtract(p[k - 3].multiply(k - 3));
  2762.                     } else if (k == 2) {
  2763.                         p[0] = p[1];
  2764.                     }
  2765.                 }
  2766.                 if ((n & 0x1) == 0) {
  2767.                     v = v.multiply(t);
  2768.                 }

  2769.                 function[n] = v;

  2770.             }
  2771.         }

  2772.         // apply function composition
  2773.         compose(operand, operandOffset, function, result, resultOffset);

  2774.     }

  2775.     /** Compute inverse hyperbolic cosine of a derivative structure.
  2776.      * @param operand array holding the operand
  2777.      * @param operandOffset offset of the operand in its array
  2778.      * @param result array where result must be stored (for
  2779.      * inverse hyperbolic cosine the result array <em>cannot</em> be the input
  2780.      * array)
  2781.      * @param resultOffset offset of the result in its array
  2782.      */
  2783.     public void acosh(final double[] operand, final int operandOffset,
  2784.                       final double[] result, final int resultOffset) {

  2785.         // create the function value and derivatives
  2786.         double[] function = new double[1 + order];
  2787.         final double x = operand[operandOffset];
  2788.         function[0] = FastMath.acosh(x);
  2789.         if (order > 0) {
  2790.             // the nth order derivative of acosh has the form:
  2791.             // dn(acosh(x)/dxn = P_n(x) / [x^2 - 1]^((2n-1)/2)
  2792.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  2793.             // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 + 1 ...
  2794.             // the general recurrence relation for P_n is:
  2795.             // P_n(x) = (x^2-1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
  2796.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2797.             final double[] p = new double[order];
  2798.             p[0] = 1;
  2799.             final double x2  = x * x;
  2800.             final double f   = 1.0 / (x2 - 1);
  2801.             double coeff = FastMath.sqrt(f);
  2802.             function[1] = coeff * p[0];
  2803.             for (int n = 2; n <= order; ++n) {

  2804.                 // update and evaluate polynomial P_n(x)
  2805.                 double v = 0;
  2806.                 p[n - 1] = (1 - n) * p[n - 2];
  2807.                 for (int k = n - 1; k >= 0; k -= 2) {
  2808.                     v = v * x2 + p[k];
  2809.                     if (k > 2) {
  2810.                         p[k - 2] = (1 - k) * p[k - 1] + (k - 2 * n) * p[k - 3];
  2811.                     } else if (k == 2) {
  2812.                         p[0] = -p[1];
  2813.                     }
  2814.                 }
  2815.                 if ((n & 0x1) == 0) {
  2816.                     v *= x;
  2817.                 }

  2818.                 coeff *= f;
  2819.                 function[n] = coeff * v;

  2820.             }
  2821.         }

  2822.         // apply function composition
  2823.         compose(operand, operandOffset, function, result, resultOffset);

  2824.     }

  2825.     /** Compute inverse hyperbolic cosine of a derivative structure.
  2826.      * @param operand array holding the operand
  2827.      * @param operandOffset offset of the operand in its array
  2828.      * @param result array where result must be stored (for
  2829.      * inverse hyperbolic cosine the result array <em>cannot</em> be the input
  2830.      * array)
  2831.      * @param resultOffset offset of the result in its array
  2832.      * @param <T> the type of the function parameters and value
  2833.      */
  2834.     public <T extends CalculusFieldElement<T>> void acosh(final T[] operand, final int operandOffset,
  2835.                                                           final T[] result, final int resultOffset) {

  2836.         final Field<T> field = operand[operandOffset].getField();

  2837.         // create the function value and derivatives
  2838.         T[] function = MathArrays.buildArray(field, 1 + order);
  2839.         final T x = operand[operandOffset];
  2840.         function[0] = x.acosh();
  2841.         if (order > 0) {
  2842.             // the nth order derivative of acosh has the form:
  2843.             // dn(acosh(x)/dxn = P_n(x) / [x^2 - 1]^((2n-1)/2)
  2844.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  2845.             // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 + 1 ...
  2846.             // the general recurrence relation for P_n is:
  2847.             // P_n(x) = (x^2-1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
  2848.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2849.             final T[] p = MathArrays.buildArray(field, order);
  2850.             p[0] = field.getOne();
  2851.             final T x2  = x.square();
  2852.             final T f   = x2.subtract(1).reciprocal();
  2853.             T coeff = f.sqrt();
  2854.             function[1] = coeff.multiply(p[0]);
  2855.             for (int n = 2; n <= order; ++n) {

  2856.                 // update and evaluate polynomial P_n(x)
  2857.                 T v = field.getZero();
  2858.                 p[n - 1] = p[n - 2].multiply(1 - n);
  2859.                 for (int k = n - 1; k >= 0; k -= 2) {
  2860.                     v = v.multiply(x2).add(p[k]);
  2861.                     if (k > 2) {
  2862.                         p[k - 2] = p[k - 1].multiply(1 - k).add(p[k - 3].multiply(k - 2 * n));
  2863.                     } else if (k == 2) {
  2864.                         p[0] = p[1].negate();
  2865.                     }
  2866.                 }
  2867.                 if ((n & 0x1) == 0) {
  2868.                     v = v.multiply(x);
  2869.                 }

  2870.                 coeff = coeff.multiply(f);
  2871.                 function[n] = coeff.multiply(v);

  2872.             }
  2873.         }

  2874.         // apply function composition
  2875.         compose(operand, operandOffset, function, result, resultOffset);

  2876.     }

  2877.     /** Compute inverse hyperbolic sine of a derivative structure.
  2878.      * @param operand array holding the operand
  2879.      * @param operandOffset offset of the operand in its array
  2880.      * @param result array where result must be stored (for
  2881.      * inverse hyperbolic sine the result array <em>cannot</em> be the input
  2882.      * array)
  2883.      * @param resultOffset offset of the result in its array
  2884.      */
  2885.     public void asinh(final double[] operand, final int operandOffset,
  2886.                       final double[] result, final int resultOffset) {

  2887.         // create the function value and derivatives
  2888.         double[] function = new double[1 + order];
  2889.         final double x = operand[operandOffset];
  2890.         function[0] = FastMath.asinh(x);
  2891.         if (order > 0) {
  2892.             // the nth order derivative of asinh has the form:
  2893.             // dn(asinh(x)/dxn = P_n(x) / [x^2 + 1]^((2n-1)/2)
  2894.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  2895.             // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 - 1 ...
  2896.             // the general recurrence relation for P_n is:
  2897.             // P_n(x) = (x^2+1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
  2898.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2899.             final double[] p = new double[order];
  2900.             p[0] = 1;
  2901.             final double x2    = x * x;
  2902.             final double f     = 1.0 / (1 + x2);
  2903.             double coeff = FastMath.sqrt(f);
  2904.             function[1] = coeff * p[0];
  2905.             for (int n = 2; n <= order; ++n) {

  2906.                 // update and evaluate polynomial P_n(x)
  2907.                 double v = 0;
  2908.                 p[n - 1] = (1 - n) * p[n - 2];
  2909.                 for (int k = n - 1; k >= 0; k -= 2) {
  2910.                     v = v * x2 + p[k];
  2911.                     if (k > 2) {
  2912.                         p[k - 2] = (k - 1) * p[k - 1] + (k - 2 * n) * p[k - 3];
  2913.                     } else if (k == 2) {
  2914.                         p[0] = p[1];
  2915.                     }
  2916.                 }
  2917.                 if ((n & 0x1) == 0) {
  2918.                     v *= x;
  2919.                 }

  2920.                 coeff *= f;
  2921.                 function[n] = coeff * v;

  2922.             }
  2923.         }

  2924.         // apply function composition
  2925.         compose(operand, operandOffset, function, result, resultOffset);

  2926.     }

  2927.     /** Compute inverse hyperbolic sine of a derivative structure.
  2928.      * @param operand array holding the operand
  2929.      * @param operandOffset offset of the operand in its array
  2930.      * @param result array where result must be stored (for
  2931.      * inverse hyperbolic sine the result array <em>cannot</em> be the input
  2932.      * array)
  2933.      * @param resultOffset offset of the result in its array
  2934.      * @param <T> the type of the function parameters and value
  2935.      */
  2936.     public <T extends CalculusFieldElement<T>> void asinh(final T[] operand, final int operandOffset,
  2937.                                                           final T[] result, final int resultOffset) {

  2938.         final Field<T> field = operand[operandOffset].getField();

  2939.         // create the function value and derivatives
  2940.         T[] function = MathArrays.buildArray(field, 1 + order);
  2941.         final T x = operand[operandOffset];
  2942.         function[0] = x.asinh();
  2943.         if (order > 0) {
  2944.             // the nth order derivative of asinh has the form:
  2945.             // dn(asinh(x)/dxn = P_n(x) / [x^2 + 1]^((2n-1)/2)
  2946.             // where P_n(x) is a degree n-1 polynomial with same parity as n-1
  2947.             // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 - 1 ...
  2948.             // the general recurrence relation for P_n is:
  2949.             // P_n(x) = (x^2+1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
  2950.             // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
  2951.             final T[] p = MathArrays.buildArray(field, order);
  2952.             p[0] = field.getOne();
  2953.             final T x2    = x.square();
  2954.             final T f     = x2.add(1).reciprocal();
  2955.             T coeff = f.sqrt();
  2956.             function[1] = coeff.multiply(p[0]);
  2957.             for (int n = 2; n <= order; ++n) {

  2958.                 // update and evaluate polynomial P_n(x)
  2959.                 T v = field.getZero();
  2960.                 p[n - 1] = p[n - 2].multiply(1 - n);
  2961.                 for (int k = n - 1; k >= 0; k -= 2) {
  2962.                     v = v.multiply(x2).add(p[k]);
  2963.                     if (k > 2) {
  2964.                         p[k - 2] = p[k - 1].multiply(k - 1).add(p[k - 3].multiply(k - 2 * n));
  2965.                     } else if (k == 2) {
  2966.                         p[0] = p[1];
  2967.                     }
  2968.                 }
  2969.                 if ((n & 0x1) == 0) {
  2970.                     v = v.multiply(x);
  2971.                 }

  2972.                 coeff = coeff.multiply(f);
  2973.                 function[n] = coeff.multiply(v);

  2974.             }
  2975.         }

  2976.         // apply function composition
  2977.         compose(operand, operandOffset, function, result, resultOffset);

  2978.     }

  2979.     /** Compute inverse hyperbolic tangent of a derivative structure.
  2980.      * @param operand array holding the operand
  2981.      * @param operandOffset offset of the operand in its array
  2982.      * @param result array where result must be stored (for
  2983.      * inverse hyperbolic tangent the result array <em>cannot</em> be the input
  2984.      * array)
  2985.      * @param resultOffset offset of the result in its array
  2986.      */
  2987.     public void atanh(final double[] operand, final int operandOffset,
  2988.                       final double[] result, final int resultOffset) {

  2989.         // create the function value and derivatives
  2990.         double[] function = new double[1 + order];
  2991.         final double x = operand[operandOffset];
  2992.         function[0] = FastMath.atanh(x);
  2993.         if (order > 0) {
  2994.             // the nth order derivative of atanh has the form:
  2995.             // dn(atanh(x)/dxn = Q_n(x) / (1 - x^2)^n
  2996.             // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
  2997.             // Q_1(x) = 1, Q_2(x) = 2x, Q_3(x) = 6x^2 + 2 ...
  2998.             // the general recurrence relation for Q_n is:
  2999.             // Q_n(x) = (1-x^2) Q_(n-1)'(x) + 2(n-1) x Q_(n-1)(x)
  3000.             // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
  3001.             final double[] q = new double[order];
  3002.             q[0] = 1;
  3003.             final double x2 = x * x;
  3004.             final double f  = 1.0 / (1 - x2);
  3005.             double coeff = f;
  3006.             function[1] = coeff * q[0];
  3007.             for (int n = 2; n <= order; ++n) {

  3008.                 // update and evaluate polynomial Q_n(x)
  3009.                 double v = 0;
  3010.                 q[n - 1] = n * q[n - 2];
  3011.                 for (int k = n - 1; k >= 0; k -= 2) {
  3012.                     v = v * x2 + q[k];
  3013.                     if (k > 2) {
  3014.                         q[k - 2] = (k - 1) * q[k - 1] + (2 * n - k + 1) * q[k - 3];
  3015.                     } else if (k == 2) {
  3016.                         q[0] = q[1];
  3017.                     }
  3018.                 }
  3019.                 if ((n & 0x1) == 0) {
  3020.                     v *= x;
  3021.                 }

  3022.                 coeff *= f;
  3023.                 function[n] = coeff * v;

  3024.             }
  3025.         }

  3026.         // apply function composition
  3027.         compose(operand, operandOffset, function, result, resultOffset);

  3028.     }

  3029.     /** Compute inverse hyperbolic tangent of a derivative structure.
  3030.      * @param operand array holding the operand
  3031.      * @param operandOffset offset of the operand in its array
  3032.      * @param result array where result must be stored (for
  3033.      * inverse hyperbolic tangent the result array <em>cannot</em> be the input
  3034.      * array)
  3035.      * @param resultOffset offset of the result in its array
  3036.      * @param <T> the type of the function parameters and value
  3037.      */
  3038.     public <T extends CalculusFieldElement<T>> void atanh(final T[] operand, final int operandOffset,
  3039.                                                           final T[] result, final int resultOffset) {

  3040.         final Field<T> field = operand[operandOffset].getField();

  3041.         // create the function value and derivatives
  3042.         T[] function = MathArrays.buildArray(field, 1 + order);
  3043.         final T x = operand[operandOffset];
  3044.         function[0] = x.atanh();
  3045.         if (order > 0) {
  3046.             // the nth order derivative of atanh has the form:
  3047.             // dn(atanh(x)/dxn = Q_n(x) / (1 - x^2)^n
  3048.             // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
  3049.             // Q_1(x) = 1, Q_2(x) = 2x, Q_3(x) = 6x^2 + 2 ...
  3050.             // the general recurrence relation for Q_n is:
  3051.             // Q_n(x) = (1-x^2) Q_(n-1)'(x) + 2(n-1) x Q_(n-1)(x)
  3052.             // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
  3053.             final T[] q = MathArrays.buildArray(field, order);
  3054.             q[0] = field.getOne();
  3055.             final T x2 = x.square();
  3056.             final T f  =x2.subtract(1).negate().reciprocal();
  3057.             T coeff = f;
  3058.             function[1] = coeff.multiply(q[0]);
  3059.             for (int n = 2; n <= order; ++n) {

  3060.                 // update and evaluate polynomial Q_n(x)
  3061.                 T v = field.getZero();
  3062.                 q[n - 1] = q[n - 2].multiply(n);
  3063.                 for (int k = n - 1; k >= 0; k -= 2) {
  3064.                     v = v.multiply(x2).add(q[k]);
  3065.                     if (k > 2) {
  3066.                         q[k - 2] = q[k - 1].multiply(k - 1).add(q[k - 3].multiply(2 * n - k + 1));
  3067.                     } else if (k == 2) {
  3068.                         q[0] = q[1];
  3069.                     }
  3070.                 }
  3071.                 if ((n & 0x1) == 0) {
  3072.                     v = v.multiply(x);
  3073.                 }

  3074.                 coeff = coeff.multiply(f);
  3075.                 function[n] = coeff.multiply(v);

  3076.             }
  3077.         }

  3078.         // apply function composition
  3079.         compose(operand, operandOffset, function, result, resultOffset);

  3080.     }

  3081.     /** Compute composition of a derivative structure by a function.
  3082.      * @param operand array holding the operand
  3083.      * @param operandOffset offset of the operand in its array
  3084.      * @param f array of value and derivatives of the function at
  3085.      * the current point (i.e. at {@code operand[operandOffset]}).
  3086.      * @param result array where result must be stored (for
  3087.      * composition the result array <em>cannot</em> be the input
  3088.      * array)
  3089.      * @param resultOffset offset of the result in its array
  3090.      */
  3091.     public void compose(final double[] operand, final int operandOffset, final double[] f,
  3092.                         final double[] result, final int resultOffset) {
  3093.         for (int i = 0; i < compIndirection.length; ++i) {
  3094.             final UnivariateCompositionMapper[] mappingI = compIndirection[i];
  3095.             double r = 0;
  3096.             for (UnivariateCompositionMapper mapping : mappingI) {
  3097.                 double product = mapping.getCoeff() * f[mapping.fIndex];
  3098.                 for (int k = 0; k < mapping.dsIndices.length; ++k) {
  3099.                     product *= operand[operandOffset + mapping.dsIndices[k]];
  3100.                 }
  3101.                 r += product;
  3102.             }
  3103.             result[resultOffset + i] = r;
  3104.         }
  3105.     }

  3106.     /** Compute composition of a derivative structure by a function.
  3107.      * @param operand array holding the operand
  3108.      * @param operandOffset offset of the operand in its array
  3109.      * @param f array of value and derivatives of the function at
  3110.      * the current point (i.e. at {@code operand[operandOffset]}).
  3111.      * @param result array where result must be stored (for
  3112.      * composition the result array <em>cannot</em> be the input
  3113.      * array)
  3114.      * @param resultOffset offset of the result in its array
  3115.      * @param <T> the type of the function parameters and value
  3116.      */
  3117.     public <T extends CalculusFieldElement<T>> void compose(final T[] operand, final int operandOffset, final T[] f,
  3118.                                                             final T[] result, final int resultOffset) {
  3119.         final T zero = f[0].getField().getZero();
  3120.         for (int i = 0; i < compIndirection.length; ++i) {
  3121.             final UnivariateCompositionMapper[] mappingI = compIndirection[i];
  3122.             T r = zero;
  3123.             for (UnivariateCompositionMapper mapping : mappingI) {
  3124.                 T product = f[mapping.fIndex].multiply(mapping.getCoeff());
  3125.                 for (int k = 0; k < mapping.dsIndices.length; ++k) {
  3126.                     product = product.multiply(operand[operandOffset + mapping.dsIndices[k]]);
  3127.                 }
  3128.                 r = r.add(product);
  3129.             }
  3130.             result[resultOffset + i] = r;
  3131.         }
  3132.     }

  3133.     /** Compute composition of a derivative structure by a function.
  3134.      * @param operand array holding the operand
  3135.      * @param operandOffset offset of the operand in its array
  3136.      * @param f array of value and derivatives of the function at
  3137.      * the current point (i.e. at {@code operand[operandOffset]}).
  3138.      * @param result array where result must be stored (for
  3139.      * composition the result array <em>cannot</em> be the input
  3140.      * array)
  3141.      * @param resultOffset offset of the result in its array
  3142.      * @param <T> the type of the function parameters and value
  3143.      */
  3144.     public <T extends CalculusFieldElement<T>> void compose(final T[] operand, final int operandOffset, final double[] f,
  3145.                                                             final T[] result, final int resultOffset) {
  3146.         final T zero = operand[operandOffset].getField().getZero();
  3147.         for (int i = 0; i < compIndirection.length; ++i) {
  3148.             final UnivariateCompositionMapper[] mappingI = compIndirection[i];
  3149.             T r = zero;
  3150.             for (UnivariateCompositionMapper mapping : mappingI) {
  3151.                 T product = zero.add(f[mapping.fIndex] * mapping.getCoeff());
  3152.                 for (int k = 0; k < mapping.dsIndices.length; ++k) {
  3153.                     product = product.multiply(operand[operandOffset + mapping.dsIndices[k]]);
  3154.                 }
  3155.                 r = r.add(product);
  3156.             }
  3157.             result[resultOffset + i] = r;
  3158.         }
  3159.     }

  3160.     /** Evaluate Taylor expansion of a derivative structure.
  3161.      * @param ds array holding the derivative structure
  3162.      * @param dsOffset offset of the derivative structure in its array
  3163.      * @param delta parameters offsets (&Delta;x, &Delta;y, ...)
  3164.      * @return value of the Taylor expansion at x + &Delta;x, y + &Delta;y, ...
  3165.      * @throws MathRuntimeException if factorials becomes too large
  3166.      */
  3167.     public double taylor(final double[] ds, final int dsOffset, final double ... delta)
  3168.        throws MathRuntimeException {
  3169.         double value = 0;
  3170.         for (int i = getSize() - 1; i >= 0; --i) {
  3171.             final int[] orders = derivativesOrders[i];
  3172.             double term = ds[dsOffset + i];
  3173.             for (int k = 0; k < orders.length; ++k) {
  3174.                 if (orders[k] > 0) {
  3175.                     term *= FastMath.pow(delta[k], orders[k]) /
  3176.                             CombinatoricsUtils.factorial(orders[k]);
  3177.                 }
  3178.             }
  3179.             value += term;
  3180.         }
  3181.         return value;
  3182.     }

  3183.     /** Evaluate Taylor expansion of a derivative structure.
  3184.      * @param ds array holding the derivative structure
  3185.      * @param dsOffset offset of the derivative structure in its array
  3186.      * @param delta parameters offsets (&Delta;x, &Delta;y, ...)
  3187.      * @return value of the Taylor expansion at x + &Delta;x, y + &Delta;y, ...
  3188.      * @throws MathRuntimeException if factorials becomes too large
  3189.      * @param <T> the type of the function parameters and value
  3190.      */
  3191.     @SafeVarargs
  3192.     public final <T extends CalculusFieldElement<T>> T taylor(final T[] ds, final int dsOffset,
  3193.                                                               final T ... delta)
  3194.        throws MathRuntimeException {
  3195.         final Field<T> field = ds[dsOffset].getField();
  3196.         T value = field.getZero();
  3197.         for (int i = getSize() - 1; i >= 0; --i) {
  3198.             final int[] orders = derivativesOrders[i];
  3199.             T term = ds[dsOffset + i];
  3200.             for (int k = 0; k < orders.length; ++k) {
  3201.                 if (orders[k] > 0) {
  3202.                     term = term.multiply(delta[k].pow(orders[k]).
  3203.                                          divide(CombinatoricsUtils.factorial(orders[k])));
  3204.                 }
  3205.             }
  3206.             value = value.add(term);
  3207.         }
  3208.         return value;
  3209.     }

  3210.     /** Evaluate Taylor expansion of a derivative structure.
  3211.      * @param ds array holding the derivative structure
  3212.      * @param dsOffset offset of the derivative structure in its array
  3213.      * @param delta parameters offsets (&Delta;x, &Delta;y, ...)
  3214.      * @return value of the Taylor expansion at x + &Delta;x, y + &Delta;y, ...
  3215.      * @throws MathRuntimeException if factorials becomes too large
  3216.      * @param <T> the type of the function parameters and value
  3217.      */
  3218.     public <T extends CalculusFieldElement<T>> T taylor(final T[] ds, final int dsOffset,
  3219.                                                         final double ... delta)
  3220.        throws MathRuntimeException {
  3221.         final Field<T> field = ds[dsOffset].getField();
  3222.         T value = field.getZero();
  3223.         for (int i = getSize() - 1; i >= 0; --i) {
  3224.             final int[] orders = derivativesOrders[i];
  3225.             T term = ds[dsOffset + i];
  3226.             for (int k = 0; k < orders.length; ++k) {
  3227.                 if (orders[k] > 0) {
  3228.                     term = term.multiply(field.getZero().newInstance(delta[k]).pow(orders[k]).
  3229.                                          divide(CombinatoricsUtils.factorial(orders[k])));
  3230.                 }
  3231.             }
  3232.             value = value.add(term);
  3233.         }
  3234.         return value;
  3235.     }

  3236.     /** Rebase derivative structure with respect to low level parameter functions.
  3237.      * @param ds array holding the derivative structure
  3238.      * @param dsOffset offset of the derivative structure in its array
  3239.      * @param baseCompiler compiler associated with the low level parameter functions
  3240.      * @param p array holding the low level parameter functions (one flat array)
  3241.      * @param result array where result must be stored (for
  3242.      * composition the result array <em>cannot</em> be the input
  3243.      * @param resultOffset offset of the result in its array
  3244.      * @since 2.2
  3245.      */
  3246.     public void rebase(final double[] ds, final int dsOffset,
  3247.                        final DSCompiler baseCompiler, double[] p,
  3248.                        final double[] result, final int resultOffset) {
  3249.         final MultivariateCompositionMapper[][] rebaser = getRebaser(baseCompiler);
  3250.         for (int i = 0; i < rebaser.length; ++i) {
  3251.             final MultivariateCompositionMapper[] mappingI = rebaser[i];
  3252.             double r = 0;
  3253.             for (MultivariateCompositionMapper mapping : mappingI) {
  3254.                 double product = mapping.getCoeff() * ds[dsOffset + mapping.dsIndex];
  3255.                 for (int k = 0; k < mapping.productIndices.length; ++k) {
  3256.                     product *= p[mapping.productIndices[k]];
  3257.                 }
  3258.                 r += product;
  3259.             }
  3260.             result[resultOffset + i] = r;
  3261.         }
  3262.     }

  3263.     /** Rebase derivative structure with respect to low level parameter functions.
  3264.      * @param <T> type of the field elements
  3265.      * @param ds array holding the derivative structure
  3266.      * @param dsOffset offset of the derivative structure in its array
  3267.      * @param baseCompiler compiler associated with the low level parameter functions
  3268.      * @param p array holding the low level parameter functions (one flat array)
  3269.      * @param result array where result must be stored (for
  3270.      * composition the result array <em>cannot</em> be the input
  3271.      * @param resultOffset offset of the result in its array
  3272.      * @since 2.2
  3273.      */
  3274.     public <T extends CalculusFieldElement<T>> void rebase(final T[] ds, final int dsOffset,
  3275.                                                            final DSCompiler baseCompiler, T[] p,
  3276.                                                            final T[] result, final int resultOffset) {
  3277.         final MultivariateCompositionMapper[][] rebaser = getRebaser(baseCompiler);
  3278.         for (int i = 0; i < rebaser.length; ++i) {
  3279.             final MultivariateCompositionMapper[] mappingI = rebaser[i];
  3280.             T r = ds[0].getField().getZero();
  3281.             for (MultivariateCompositionMapper mapping : mappingI) {
  3282.                 T product =  ds[dsOffset + mapping.dsIndex].multiply(mapping.getCoeff());
  3283.                 for (int k = 0; k < mapping.productIndices.length; ++k) {
  3284.                     product = product.multiply(p[mapping.productIndices[k]]);
  3285.                 }
  3286.                 r = r.add(product);
  3287.             }
  3288.             result[resultOffset + i] = r;
  3289.         }
  3290.     }

  3291.     /** Check rules set compatibility.
  3292.      * @param compiler other compiler to check against instance
  3293.      * @exception MathIllegalArgumentException if number of free parameters or orders are inconsistent
  3294.      */
  3295.     public void checkCompatibility(final DSCompiler compiler)
  3296.         throws MathIllegalArgumentException {
  3297.         MathUtils.checkDimension(parameters, compiler.parameters);
  3298.         MathUtils.checkDimension(order, compiler.order);
  3299.     }

  3300.     /** Combine terms with similar derivation orders.
  3301.      * @param <T> type of the field elements
  3302.      * @param terms list of terms
  3303.      * @return combined array
  3304.      */
  3305.     @SuppressWarnings("unchecked")
  3306.     private static <T extends AbstractMapper<T>> T[] combineSimilarTerms(final List<T> terms) {

  3307.         final List<T> combined = new ArrayList<>(terms.size());

  3308.         for (int j = 0; j < terms.size(); ++j) {
  3309.             final T termJ = terms.get(j);
  3310.             if (termJ.getCoeff() > 0) {
  3311.                 for (int k = j + 1; k < terms.size(); ++k) {
  3312.                     final T termK = terms.get(k);
  3313.                     if (termJ.isSimilar(termK)) {
  3314.                         // combine terms
  3315.                         termJ.setCoeff(termJ.getCoeff() + termK.getCoeff());
  3316.                         // make sure we will skip other term later on in the outer loop
  3317.                         termK.setCoeff(0);
  3318.                     }
  3319.                 }
  3320.                 combined.add(termJ);
  3321.             }
  3322.         }

  3323.         return combined.toArray((T[]) Array.newInstance(terms.get(0).getClass(), combined.size()));

  3324.     }

  3325.     /** Base mapper.
  3326.      * @param <T> type of the field elements
  3327.      * @since 2.2
  3328.      */
  3329.     private abstract static class AbstractMapper<T extends AbstractMapper<T>> {

  3330.         /** Multiplication coefficient. */
  3331.         private int coeff;

  3332.         /** Simple constructor.
  3333.          * @param coeff multiplication coefficient
  3334.          */
  3335.         AbstractMapper(final int coeff) {
  3336.             this.coeff    = coeff;
  3337.         }

  3338.         /** Set the multiplication coefficient.
  3339.          * @param coeff new coefficient
  3340.          */
  3341.         public void setCoeff(final int coeff) {
  3342.             this.coeff = coeff;
  3343.         }

  3344.         /** Get the multiplication coefficient.
  3345.          * @return multiplication coefficient
  3346.          */
  3347.         public int getCoeff() {
  3348.             return coeff;
  3349.         }

  3350.         /** Check if another instance if correspond to term with similar derivation orders.
  3351.          * @param other other instance to check
  3352.          * @return true if instances are similar
  3353.          */
  3354.         protected abstract boolean isSimilar(T other);

  3355.     }

  3356.     /** Multiplication mapper.
  3357.      * @since 2.2
  3358.      */
  3359.     private static class MultiplicationMapper extends AbstractMapper<MultiplicationMapper> {

  3360.         /** Left hand side index. */
  3361.         private final int lhsIndex;

  3362.         /** Right hand side index. */
  3363.         private final int rhsIndex;

  3364.         /** Simple constructor.
  3365.          * @param coeff multiplication coefficient
  3366.          * @param lhsIndex left hand side index
  3367.          * @param rhsIndex right hand side index
  3368.          */
  3369.         MultiplicationMapper(final int coeff, final int lhsIndex, final int rhsIndex) {
  3370.             super(coeff);
  3371.             this.lhsIndex = lhsIndex;
  3372.             this.rhsIndex = rhsIndex;
  3373.         }

  3374.         /** {@inheritDoc} */
  3375.         @Override
  3376.         public boolean isSimilar(final MultiplicationMapper other) {
  3377.             return lhsIndex == other.lhsIndex && rhsIndex == other.rhsIndex;
  3378.         }

  3379.     }

  3380.     /** Univariate composition mapper.
  3381.      * @since 2.2
  3382.      */
  3383.     private static class UnivariateCompositionMapper extends AbstractMapper<UnivariateCompositionMapper> {

  3384.         /** Univariate derivative index. */
  3385.         private final int fIndex;

  3386.         /** Derivative structure indices. */
  3387.         private final int[] dsIndices;

  3388.         /** Simple constructor.
  3389.          * @param coeff multiplication coefficient
  3390.          * @param fIndex univariate derivative index
  3391.          * @param dsIndices derivative structure indices
  3392.          */
  3393.         UnivariateCompositionMapper(final int coeff, final int fIndex, final int[] dsIndices) {
  3394.             super(coeff);
  3395.             this.fIndex    = fIndex;
  3396.             this.dsIndices = dsIndices.clone();
  3397.         }

  3398.         /** Sort the derivatives structures indices.
  3399.          */
  3400.         public void sort() {
  3401.             Arrays.sort(dsIndices);
  3402.         }

  3403.         /** {@inheritDoc} */
  3404.         @Override
  3405.         public boolean isSimilar(final UnivariateCompositionMapper other) {

  3406.             if (fIndex == other.fIndex && dsIndices.length == other.dsIndices.length) {

  3407.                 for (int j = 0; j < dsIndices.length; ++j) {
  3408.                     if (dsIndices[j] != other.dsIndices[j]) {
  3409.                         return false;
  3410.                     }
  3411.                 }

  3412.                 return true;

  3413.             }

  3414.             return false;

  3415.         }

  3416.     }

  3417.     /** Multivariate composition mapper.
  3418.      * @since 2.2
  3419.      */
  3420.     private static class MultivariateCompositionMapper extends AbstractMapper<MultivariateCompositionMapper> {

  3421.         /** Multivariate derivative index. */
  3422.         private final int dsIndex;

  3423.         /** Indices of the intermediate variables derivatives products. */
  3424.         private final int[] productIndices;

  3425.         /** Simple constructor.
  3426.          * @param coeff multiplication coefficient
  3427.          * @param dsIndex multivariate derivative index of ∂ₘf/∂pᵢ⋯∂pⱼ
  3428.          * @param productIndices indices of intermediate partial derivatives ∂pᵢ/∂qₘ⋯∂qₙ
  3429.          */
  3430.         MultivariateCompositionMapper(final int coeff, final int dsIndex, final int[] productIndices) {
  3431.             super(coeff);
  3432.             this.dsIndex        = dsIndex;
  3433.             this.productIndices = productIndices.clone();
  3434.         }

  3435.         /** Sort the indices of the intermediate variables derivatives products.
  3436.          */
  3437.         public void sort() {
  3438.             Arrays.sort(productIndices);
  3439.         }

  3440.         /** {@inheritDoc} */
  3441.         @Override
  3442.         public boolean isSimilar(final MultivariateCompositionMapper other) {

  3443.             if (dsIndex == other.dsIndex && productIndices.length == other.productIndices.length) {

  3444.                 for (int j = 0; j < productIndices.length; ++j) {
  3445.                     if (productIndices[j] != other.productIndices[j]) {
  3446.                         return false;
  3447.                     }
  3448.                 }

  3449.                 return true;

  3450.             }

  3451.             return false;

  3452.         }

  3453.     }

  3454. }