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