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