1 /* 2 * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com) 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.irurueta.algebra; 17 18 /** 19 * This class contains a series of helper statistics methods that can be used 20 * to perform most typical operations in matrix algebra. 21 * Note: Depending on the situation it might be more computationally efficient 22 * to use methods implemented in Decomposer subclasses. 23 */ 24 @SuppressWarnings("DuplicatedCode") 25 public class Utils { 26 27 /** 28 * Constant defining the default threshold to compare the different content 29 * of one matrix to check whether is symmetric. 30 */ 31 public static final double DEFAULT_SYMMETRIC_THRESHOLD = 1e-12; 32 33 /** 34 * Constant defining the default threshold to determine whether a matrix is 35 * orthonormal or not. 36 */ 37 public static final double DEFAULT_ORTHOGONAL_THRESHOLD = 1e-12; 38 39 /** 40 * Constant defining machine precision 41 */ 42 public static final double EPSILON = 1e-12; 43 44 /** 45 * Constant defining minimum allowed threshold 46 */ 47 public static final double MIN_THRESHOLD = 0.0; 48 49 /** 50 * Constructor. 51 */ 52 private Utils() { 53 } 54 55 /** 56 * Computes trace of provided matrix. 57 * Trace is the sum of the elements located on the diagonal of a given 58 * matrix. 59 * 60 * @param m Input matrix. 61 * @return Trace of provided matrix 62 */ 63 public static double trace(final Matrix m) { 64 final var minSize = Math.min(m.getRows(), m.getColumns()); 65 var trace = 0.0; 66 for (var i = 0; i < minSize; i++) { 67 trace += m.getElementAt(i, i); 68 } 69 return trace; 70 } 71 72 /** 73 * Computes condition number of provided matrix. 74 * Condition number determines how well-behaved a matrix is for solving 75 * a linear system of equations 76 * 77 * @param m Input matrix. 78 * @return Condition number of provided matrix. 79 * @throws DecomposerException Exception thrown if decomposition fails for 80 * any reason. 81 * @see SingularValueDecomposer 82 */ 83 public static double cond(final Matrix m) throws DecomposerException { 84 final var decomposer = new SingularValueDecomposer(m); 85 try { 86 decomposer.decompose(); 87 return decomposer.getConditionNumber(); 88 } catch (final DecomposerException e) { 89 throw e; 90 } catch (final Exception e) { 91 throw new DecomposerException(e); 92 } 93 } 94 95 /** 96 * Computes rank of provided matrix. 97 * Rank indicates the number of linearly independent columns or rows on 98 * a matrix. 99 * 100 * @param m Input matrix. 101 * @return Rank of provided matrix. 102 * @throws DecomposerException Exception thrown if decomposition fails for 103 * any reason. 104 */ 105 public static int rank(final Matrix m) throws DecomposerException { 106 final var decomposer = new SingularValueDecomposer(m); 107 try { 108 decomposer.decompose(); 109 return decomposer.getRank(); 110 } catch (final DecomposerException e) { 111 throw e; 112 } catch (final Exception e) { 113 throw new DecomposerException(e); 114 } 115 } 116 117 /** 118 * Computes determinant of provided matrix. 119 * Determinant can be seen geometrically as a measure of hyper-volume 120 * generated by the row/column vector contained within a given squared 121 * matrix. 122 * 123 * @param m Input matrix. 124 * @return Determinant of provided matrix. 125 * @throws WrongSizeException Exception thrown if provided matrix is not 126 * squared. 127 * @throws DecomposerException Exception thrown if decomposition fails for 128 * any reason. 129 */ 130 public static double det(final Matrix m) throws WrongSizeException, DecomposerException { 131 if (m.getRows() != m.getColumns()) { 132 throw new WrongSizeException(); 133 } 134 135 final var decomposer = new LUDecomposer(m); 136 try { 137 decomposer.decompose(); 138 return decomposer.determinant(); 139 } catch (final DecomposerException e) { 140 throw e; 141 } catch (final Exception e) { 142 throw new DecomposerException(e); 143 } 144 } 145 146 /** 147 * Solves a linear system of equations of the form: m * x = b. 148 * Where x is the returned matrix containing the solution, m is the matrix 149 * of the linear system of equations and b is the parameters matrix. 150 * 151 * @param m Matrix of the linear system of equations. 152 * @param b Parameters matrix. 153 * @param result Matrix where solution of the linear system of equations 154 * will be stored. Provided matrix will be resized if needed 155 * @throws WrongSizeException Exception thrown if provided matrix m has less 156 * rows than columns, or if provided matrix b has a number of rows different 157 * from the number of rows of m. 158 * @throws RankDeficientMatrixException Exception thrown if provided matrix 159 * m is rank deficient. 160 * @throws DecomposerException Exception thrown if decomposition fails for 161 * any reason. 162 */ 163 public static void solve(final Matrix m, final Matrix b, final Matrix result) 164 throws WrongSizeException, RankDeficientMatrixException, DecomposerException { 165 if (m.getRows() == m.getColumns()) { 166 final var decomposer = new LUDecomposer(m); 167 try { 168 decomposer.decompose(); 169 decomposer.solve(b, result); 170 } catch (final WrongSizeException | DecomposerException e) { 171 throw e; 172 } catch (final SingularMatrixException e) { 173 throw new RankDeficientMatrixException(e); 174 } catch (final Exception e) { 175 throw new DecomposerException(e); 176 } 177 } else { 178 final var decomposer = new EconomyQRDecomposer(m); 179 try { 180 decomposer.decompose(); 181 decomposer.solve(b, result); 182 } catch (final WrongSizeException | RankDeficientMatrixException e) { 183 throw e; 184 } catch (final Exception e) { 185 throw new DecomposerException(e); 186 } 187 } 188 } 189 190 /** 191 * Solves a linear system of equations of the form: m * x = b. 192 * Where x is the returned matrix containing the solution, m is the matrix 193 * of the linear system of equations and b is the parameter matrix. 194 * 195 * @param m Matrix of the linear system of equations. 196 * @param b Parameters matrix. 197 * @return Solution of the linear system of equations. 198 * @throws WrongSizeException Exception thrown if provided matrix m has fewer 199 * rows than columns, or if provided matrix b has a number of rows different 200 * from the number of rows of m. 201 * @throws RankDeficientMatrixException Exception thrown if provided matrix 202 * m is rank deficient. 203 * @throws DecomposerException Exception thrown if decomposition fails for 204 * any reason. 205 */ 206 public static Matrix solve(final Matrix m, final Matrix b) throws WrongSizeException, RankDeficientMatrixException, 207 DecomposerException { 208 if (m.getRows() == m.getColumns()) { 209 final var decomposer = new LUDecomposer(m); 210 try { 211 decomposer.decompose(); 212 return decomposer.solve(b); 213 } catch (final WrongSizeException | DecomposerException e) { 214 throw e; 215 } catch (final SingularMatrixException e) { 216 throw new RankDeficientMatrixException(e); 217 } catch (final Exception e) { 218 throw new DecomposerException(e); 219 } 220 } else { 221 final var decomposer = new EconomyQRDecomposer(m); 222 try { 223 decomposer.decompose(); 224 return decomposer.solve(b); 225 } catch (final WrongSizeException | RankDeficientMatrixException e) { 226 throw e; 227 } catch (final Exception e) { 228 throw new DecomposerException(e); 229 } 230 } 231 } 232 233 /** 234 * Solves a linear system of equations of the form m * x = b, where b is 235 * assumed to be the parameters column vector, x is the returned matrix 236 * containing the solution and m is the matrix of the linear system of 237 * equations. 238 * 239 * @param m Matrix of the linear system of equations. 240 * @param b Parameters column vector. 241 * @param result Solution of the linear system of equations. 242 * m is rank deficient. 243 * @throws DecomposerException Exception thrown if decomposition fails for 244 * any reason. 245 */ 246 public static void solve(final Matrix m, final double[] b, final double[] result) throws DecomposerException { 247 if (m.getRows() == m.getColumns()) { 248 final var decomposer = new LUDecomposer(m); 249 try { 250 decomposer.decompose(); 251 System.arraycopy(decomposer.solve(Matrix.newFromArray(b, true)).toArray(true), 252 0, result, 0, result.length); 253 } catch (final DecomposerException e) { 254 throw e; 255 } catch (final Exception e) { 256 throw new DecomposerException(e); 257 } 258 } else { 259 final var decomposer = new EconomyQRDecomposer(m); 260 try { 261 decomposer.decompose(); 262 System.arraycopy(decomposer.solve(Matrix.newFromArray(b, true)).toArray(true), 263 0, result, 0, result.length); 264 } catch (final Exception e) { 265 throw new DecomposerException(e); 266 } 267 } 268 } 269 270 /** 271 * Solves a linear system of equations of the form: m * x = b. 272 * Where x is the returned array containing the solution, m is the matrix 273 * of the linear system of equations and b is the parameters array. 274 * 275 * @param m Matrix of the linear system of equations. 276 * @param b Parameters array. 277 * @return Solution of the linear system of equations. 278 * m is rank deficient. 279 * @throws DecomposerException Exception thrown if decomposition fails for 280 * any reason. 281 */ 282 public static double[] solve(final Matrix m, double[] b) throws DecomposerException { 283 if (m.getRows() == m.getColumns()) { 284 final var decomposer = new LUDecomposer(m); 285 try { 286 decomposer.decompose(); 287 return decomposer.solve(Matrix.newFromArray(b, true)).toArray(true); 288 } catch (final DecomposerException e) { 289 throw e; 290 } catch (final Exception e) { 291 throw new DecomposerException(e); 292 } 293 } else { 294 final var decomposer = new EconomyQRDecomposer(m); 295 try { 296 decomposer.decompose(); 297 return decomposer.solve(Matrix.newFromArray(b, true)).toArray(true); 298 } catch (final Exception e) { 299 throw new DecomposerException(e); 300 } 301 } 302 } 303 304 /** 305 * Computes Frobenius norm of provided input matrix. 306 * 307 * @param m Input matrix. 308 * @return Frobenius norm 309 */ 310 public static double normF(final Matrix m) { 311 return FrobeniusNormComputer.norm(m); 312 } 313 314 /** 315 * Computes Frobenius norm of provided array. 316 * 317 * @param array Input array. 318 * @return Frobenius norm. 319 */ 320 public static double normF(final double[] array) { 321 return FrobeniusNormComputer.norm(array); 322 } 323 324 /** 325 * Computes infinity norm of provided input matrix. 326 * 327 * @param m Input matrix. 328 * @return Infinity norm. 329 */ 330 public static double normInf(final Matrix m) { 331 return InfinityNormComputer.norm(m); 332 } 333 334 /** 335 * Computes infinity norm of provided input matrix. 336 * 337 * @param array Input array. 338 * @return Infinity norm. 339 */ 340 public static double normInf(final double[] array) { 341 return InfinityNormComputer.norm(array); 342 } 343 344 /** 345 * Computes two norm of provided input matrix. 346 * 347 * @param m Input matrix. 348 * @return Two norm. 349 * @throws DecomposerException Exception thrown if decomposition fails 350 * for any reason. 351 */ 352 public static double norm2(final Matrix m) throws DecomposerException { 353 final var decomposer = new SingularValueDecomposer(m); 354 try { 355 decomposer.decompose(); 356 return decomposer.getNorm2(); 357 } catch (final DecomposerException e) { 358 throw e; 359 } catch (final Exception e) { 360 throw new DecomposerException(e); 361 } 362 } 363 364 /** 365 * Computes two norm of provided input array. 366 * 367 * @param array Input array. 368 * @return Two norm 369 * @throws DecomposerException Exception thrown if decomposition fails 370 * for any reason. 371 */ 372 public static double norm2(final double[] array) throws DecomposerException { 373 final var decomposer = new SingularValueDecomposer(Matrix.newFromArray(array, true)); 374 try { 375 decomposer.decompose(); 376 return decomposer.getNorm2(); 377 } catch (final DecomposerException e) { 378 throw e; 379 } catch (final Exception e) { 380 throw new DecomposerException(e); 381 } 382 } 383 384 /** 385 * Computes one norm of provided input matrix. 386 * 387 * @param m Input matrix. 388 * @return One norm. 389 */ 390 public static double norm1(final Matrix m) { 391 return OneNormComputer.norm(m); 392 } 393 394 /** 395 * Computes one norm of provided input matrix. 396 * 397 * @param array Input array. 398 * @return One norm. 399 */ 400 public static double norm1(final double[] array) { 401 return OneNormComputer.norm(array); 402 } 403 404 /** 405 * Computes matrix inverse if provided matrix is squared, or pseudo-inverse 406 * otherwise and stores the result in provided result matrix. Result matrix 407 * will be resized if needed 408 * 409 * @param m Matrix to be inverted. 410 * @param result Matrix where matrix inverse is stored. 411 * @throws WrongSizeException Exception thrown if provided matrix m has less 412 * rows than columns. 413 * @throws RankDeficientMatrixException Exception thrown if provided matrix 414 * m is rank deficient. 415 * @throws DecomposerException Exception thrown if decomposition to 416 * compute inverse fails for any other reason. 417 */ 418 public static void inverse(final Matrix m, final Matrix result) 419 throws WrongSizeException, RankDeficientMatrixException, DecomposerException { 420 final int rows = m.getRows(); 421 final int columns = m.getColumns(); 422 if (result.getRows() != columns || result.getColumns() != rows) { 423 // resize result matrix 424 result.resize(columns, rows); 425 } 426 Utils.solve(m, Matrix.identity(rows, rows), result); 427 } 428 429 /** 430 * Computes matrix inverse if provided matrix is squared, or pseudo-inverse 431 * otherwise. 432 * 433 * @param m Matrix to be inverted. 434 * @return Matrix inverse. 435 * @throws WrongSizeException Exception thrown if provided matrix m has less 436 * rows than columns. 437 * @throws RankDeficientMatrixException Exception thrown if provided matrix 438 * m is rank deficient. 439 * @throws DecomposerException Exception thrown if decomposition to 440 * compute inverse fails for any other reason. 441 */ 442 public static Matrix inverse(final Matrix m) throws WrongSizeException, RankDeficientMatrixException, 443 DecomposerException { 444 final var rows = m.getRows(); 445 return Utils.solve(m, Matrix.identity(rows, rows)); 446 } 447 448 /** 449 * Computes array pseudo-inverse considering it as a column matrix and 450 * stores the result in provided result matrix. Result matrix will be 451 * resized if needed 452 * 453 * @param array array to be inverted 454 * @param result Array pseudo inverse 455 * @throws WrongSizeException never thrown 456 * @throws RankDeficientMatrixException Exception thrown if provided array 457 * is rank deficient. 458 * @throws DecomposerException Exception thrown if decomposition to compute 459 * inverse fails for any other reason. 460 */ 461 public static void inverse(final double[] array, final Matrix result) throws WrongSizeException, 462 RankDeficientMatrixException, DecomposerException { 463 if (result.getRows() != array.length || result.getColumns() != array.length) { 464 // resize result matrix 465 result.resize(array.length, array.length); 466 } 467 Utils.solve(Matrix.newFromArray(array, true), Matrix.identity(array.length, array.length), result); 468 } 469 470 /** 471 * Computes array pseudo-inverse considering it as a column matrix. 472 * 473 * @param array array to be inverted 474 * @return Array pseudo inverse 475 * @throws WrongSizeException never thrown 476 * @throws RankDeficientMatrixException Exception thrown if provided array 477 * is rank deficient. 478 * @throws DecomposerException Exception thrown if decomposition to compute 479 * inverse fails for any other reason. 480 */ 481 public static Matrix inverse(final double[] array) throws WrongSizeException, 482 RankDeficientMatrixException, DecomposerException { 483 final var length = array.length; 484 final var identity = Matrix.identity(length, length); 485 return Utils.solve(Matrix.newFromArray(array, true), identity); 486 } 487 488 /** 489 * Computes Moore-Penrose pseudo-inverse of provided matrix. 490 * Moore-Penrose pseudo-inverse always exists for any matrix regardless of 491 * its size, and can be computed as: pinv(A) = inv(A'*A)*A' in Matlab 492 * notation. 493 * However, for computation efficiency, Singular Value Decomposition is 494 * used instead, obtaining the following expression: pinv(A) = 495 * V * pinv(W) * U'. Where W is easily invertible since it is a diagonal 496 * matrix taking into account that the inverse for singular values close to 497 * zero (for a given tolerance) is zero, whereas for the rest is their 498 * reciprocal. 499 * In other words pinv(W)(i,i) = 1./W(i,i) for W(i,i) != 0 or zero 500 * otherwise. 501 * Notice that for invertible squared matrices, the pseudo-inverse is equal 502 * to the inverse. 503 * Also notice that pseudo-inverse can be used to solve overdetermined 504 * systems of linear equations with least minimum square error (LMSE). 505 * Finally, notice that Utils.inverse(Matrix) also can return a 506 * pseudo-inverse, however, this method since it uses SVD decomposition is 507 * numerically more stable at the expense of being computationally more 508 * expensive. 509 * 510 * @param m Input matrix. 511 * @return Moore-Penrose matrix pseudo-inverse. 512 * @throws DecomposerException Exception thrown if matrix cannot be 513 * inverted, usually because matrix contains numerically unstable values 514 * such as NaN or inf. 515 * @see SingularValueDecomposer#solve(double[]) to 516 * solve systems of linear equations, as it can be more efficient specially 517 * when several systems need to be solved using the same input system 518 * matrix. 519 */ 520 public static Matrix pseudoInverse(final Matrix m) throws DecomposerException { 521 final var decomposer = new SingularValueDecomposer(m); 522 try { 523 decomposer.decompose(); 524 final var u = decomposer.getU(); 525 final var v = decomposer.getV(); 526 final var s = decomposer.getSingularValues(); 527 final var rows = m.getRows(); 528 final var cols = m.getColumns(); 529 final var threshold = EPSILON * Math.max(rows, cols) * s[0]; 530 final var invW = new Matrix(cols, cols); 531 invW.initialize(0.0); 532 double value; 533 for (var n = 0; n < cols; n++) { 534 value = s[n]; 535 if (value > threshold) { 536 invW.setElementAt(n, n, 1.0 / value); 537 } 538 } 539 // V * invW * U', where U' is transposed of U 540 u.transpose(); 541 return v.multiplyAndReturnNew(invW.multiplyAndReturnNew(u)); 542 } catch (final DecomposerException e) { 543 throw e; 544 } catch (final Exception e) { 545 throw new DecomposerException(e); 546 } 547 } 548 549 /** 550 * Computes Moore-Penrose pseudo-inverse of provided array considering it as 551 * a column matrix. 552 * 553 * @param array Input array 554 * @return More-Penrose pseudo-inverse 555 * @throws DecomposerException Exception thrown if matrix cannot be 556 * inverted, usually because matrix contains numerically unstable values 557 * such as NaN or inf. 558 * @see #pseudoInverse(Matrix) 559 */ 560 public static Matrix pseudoInverse(final double[] array) throws DecomposerException { 561 final var decomposer = new SingularValueDecomposer(Matrix.newFromArray(array, true)); 562 try { 563 decomposer.decompose(); 564 final var u = decomposer.getU(); 565 final var v = decomposer.getV(); 566 final var s = decomposer.getSingularValues(); 567 final var rows = array.length; 568 final var cols = 1; 569 final var threshold = EPSILON * Math.max(rows, cols) * s[0]; 570 final var invW = new Matrix(cols, cols); 571 invW.initialize(0.0); 572 double value; 573 for (var n = 0; n < cols; n++) { 574 value = s[n]; 575 if (value > threshold) { 576 invW.setElementAt(n, n, 1.0 / value); 577 } 578 } 579 // V * invW * U', where U' is transposed of U 580 u.transpose(); 581 return v.multiplyAndReturnNew(invW.multiplyAndReturnNew(u)); 582 } catch (final DecomposerException e) { 583 throw e; 584 } catch (final Exception e) { 585 throw new DecomposerException(e); 586 } 587 } 588 589 /** 590 * Computes the skew-symmetric matrix of provided vector of length 3 and 591 * stores the result in provided matrix. 592 * Skew-symmetric matrices are specially useful if several cross-products 593 * need to be done for the same first vector over different vectors. 594 * That is, having a vector a and vectors b1...bn of size 3x1: 595 * c1 = a x b1 596 * c2 = a x b2 597 * ... 598 * cn = a x bn 599 * Instead of computing each product separately, the second factor can be 600 * converted in a matrix where each row contains one of the vectors on the 601 * second factor: 602 * B = [b1, b2, ..., bn]; 603 * And all the cross products shown above can be computed as a single matrix 604 * multiplication in the form: 605 * C = skewMatrix(a) * B; 606 * 607 * @param array Array of length 3 that will be used to compute the skew-symmetric 608 * matrix. 609 * @param result Matrix where skew matrix is stored. Provided matrix will be 610 * resized if needed 611 * @throws WrongSizeException Exception thrown if provided array doesn't 612 * have length equal to 3. 613 */ 614 public static void skewMatrix(final double[] array, final Matrix result) throws WrongSizeException { 615 if (array.length != 3) { 616 throw new WrongSizeException("array length must be 3"); 617 } 618 619 if (result.getRows() != 3 || result.getColumns() != 3) { 620 result.resize(3, 3); 621 } 622 result.initialize(0.0); 623 624 result.setElementAt(0, 1, -array[2]); 625 result.setElementAt(0, 2, array[1]); 626 result.setElementAt(1, 0, array[2]); 627 result.setElementAt(1, 2, -array[0]); 628 result.setElementAt(2, 0, -array[1]); 629 result.setElementAt(2, 1, array[0]); 630 } 631 632 /** 633 * Computes the skew-symmetric matrix of provided vector of length 3 and 634 * stores the result in provided matrix. 635 * If provided, jacobian is also set. 636 * Skew-symmetric matrices are specially useful if several cross-products 637 * need to be done for the same first vector over different vectors. 638 * That is, having a vector a and vectors b1...bn of size 3x1: 639 * c1 = a x b1 640 * c2 = a x b2 641 * ... 642 * cn = a x bn 643 * Instead of computing each product separately, the second factor can be 644 * converted in a matrix where each row contains one of the vectors on the 645 * second factor: 646 * B = [b1, b2, ..., bn]; 647 * And all the cross products shown above can be computed as a single matrix 648 * multiplication in the form: 649 * C = skewMatrix(a) * B; 650 * 651 * @param array Array of length 3 that will be used to compute the skew-symmetric 652 * matrix. 653 * @param result Matrix where skew matrix is stored. Provided matrix will be 654 * resized if needed 655 * @param jacobian matrix where jacobian will be stored. Must be 9x3. 656 * @throws WrongSizeException if provided array doesn't have length 3 or 657 * if jacobian is not 9x3 (if provided). 658 */ 659 public static void skewMatrix(final double[] array, final Matrix result, final Matrix jacobian) 660 throws WrongSizeException { 661 if (jacobian != null && (jacobian.getRows() != 9 || jacobian.getColumns() != 3)) { 662 throw new WrongSizeException("jacobian must be 9x3"); 663 } 664 665 skewMatrix(array, result); 666 667 if (jacobian != null) { 668 jacobian.initialize(0.0); 669 jacobian.setElementAt(5, 0, 1.0); 670 jacobian.setElementAt(7, 0, -1.0); 671 672 jacobian.setElementAt(2, 1, -1.0); 673 jacobian.setElementAt(6, 1, 1.0); 674 675 jacobian.setElementAt(1, 2, 1.0); 676 jacobian.setElementAt(3, 2, -1.0); 677 } 678 } 679 680 /** 681 * Computes the skew-symmetric matrix of provided vector of length 3. 682 * Skew-symmetric matrices are specially useful if several cross-products 683 * need to be done for the same first vector over different vectors. 684 * That is, having a vector a and vectors b1...bn of size 3x1: 685 * c1 = a x b1 686 * c2 = a x b2 687 * ... 688 * cn = a x bn 689 * Instead of computing each product separately, the second factor can be 690 * converted in a matrix where each row contains one of the vectors on the 691 * second factor: 692 * B = [b1, b2, ..., bn]; 693 * And all the cross products shown above can be computed as a single matrix 694 * multiplication in the form: 695 * C = skewMatrix(a) * B; 696 * 697 * @param array Array of length 3 that will be used to compute the skew-symmetric 698 * matrix. 699 * @return Skew matrix. 700 * @throws WrongSizeException Exception thrown if provided array doesn't 701 * have length equal to 3. 702 */ 703 public static Matrix skewMatrix(final double[] array) throws WrongSizeException { 704 final var sk = new Matrix(3, 3); 705 skewMatrix(array, sk); 706 return sk; 707 } 708 709 /** 710 * Internal method to compute skew matrix 711 * 712 * @param m Matrix of size 3x1 or 1x3 that will be used to compute the 713 * skew-symmetric matrix. 714 * @param result Matrix where skew matrix is stored 715 * @param columnwise boolean indicating whether m is column-wise 716 * @throws WrongSizeException Exception thrown if provided m matrix doesn't 717 * have proper size 718 */ 719 private static void internalSkewMatrix( 720 final Matrix m, final Matrix result, final boolean columnwise) throws WrongSizeException { 721 if (result.getRows() != 3 || result.getColumns() != 3) { 722 // resize result 723 result.resize(3, 3); 724 } 725 726 result.initialize(0.0); 727 728 result.setElementAt(0, 1, -m.getElementAtIndex(2, columnwise)); 729 result.setElementAt(0, 2, m.getElementAtIndex(1, columnwise)); 730 result.setElementAt(1, 0, m.getElementAtIndex(2, columnwise)); 731 result.setElementAt(1, 2, -m.getElementAtIndex(0, columnwise)); 732 result.setElementAt(2, 0, -m.getElementAtIndex(1, columnwise)); 733 result.setElementAt(2, 1, m.getElementAtIndex(0, columnwise)); 734 } 735 736 /** 737 * Computes the skew-symmetric matrix of provided matrix of size 3x1 or 738 * 13. 739 * Skew-symmetric matrices are specially useful if several cross-products 740 * need to be done for the same first vector over different vectors. 741 * That is, having a vector a and vectors b1...bn of size 3x1: 742 * c1 = a x b1 743 * c2 = a x b2 744 * ... 745 * cn = a x bn 746 * Instead of computing each product separately, the second factor can be 747 * converted in a matrix where each row contains one of the vectors on the 748 * second factor: 749 * B = [b1, b2, ..., bn]; 750 * And all the cross products shown above can be computed as a single matrix 751 * multiplication in the form: 752 * C = skewMatrix(a) * B; 753 * Result is stored into provided result matrix, which will be resized if 754 * needed 755 * 756 * @param m Matrix of size 3x1 or 1x3 that will be used to compute the 757 * skew-symmetric matrix. 758 * @param result Matrix where skew matrix will be stored. 759 * @throws WrongSizeException Exception thrown if provided matrix is not 3x1 760 * or 1x3. 761 */ 762 public static void skewMatrix(final Matrix m, final Matrix result) throws WrongSizeException { 763 boolean columnwise; 764 if (m.getRows() == 3 && m.getColumns() == 1) { 765 columnwise = false; 766 } else if (m.getRows() == 1 && m.getColumns() == 3) { 767 columnwise = true; 768 } else { 769 throw new WrongSizeException("m matrix must be 3x1 or 1x3"); 770 } 771 internalSkewMatrix(m, result, columnwise); 772 } 773 774 /** 775 * Computes the skew-symmetric matrix of provided matrix of size 3x1 or 776 * 13. 777 * If provided, jacobian is also set. 778 * Skew-symmetric matrices are specially useful if several cross-products 779 * need to be done for the same first vector over different vectors. 780 * That is, having a vector a and vectors b1...bn of size 3x1: 781 * c1 = a x b1 782 * c2 = a x b2 783 * ... 784 * cn = a x bn 785 * Instead of computing each product separately, the second factor can be 786 * converted in a matrix where each row contains one of the vectors on the 787 * second factor: 788 * B = [b1, b2, ..., bn]; 789 * And all the cross products shown above can be computed as a single matrix 790 * multiplication in the form: 791 * C = skewMatrix(a) * B; 792 * Result is stored into provided result matrix, which will be resized if 793 * needed 794 * 795 * @param m Matrix of size 3x1 or 1x3 that will be used to compute the 796 * skew-symmetric matrix. 797 * @param result Matrix where skew matrix will be stored. 798 * @param jacobian matrix where jacobian will be stored. Must be 9x3. 799 * @throws WrongSizeException if provided matrix is not 3x1 or 1x3 or 800 * if jacobian is not 9x3 (if provided). 801 */ 802 public static void skewMatrix(final Matrix m, final Matrix result, final Matrix jacobian) 803 throws WrongSizeException { 804 if (jacobian != null && (jacobian.getRows() != 9 || jacobian.getColumns() != 3)) { 805 throw new WrongSizeException("jacobian must be 9x3"); 806 } 807 808 skewMatrix(m, result); 809 810 if (jacobian != null) { 811 jacobian.initialize(0.0); 812 jacobian.setElementAt(5, 0, 1.0); 813 jacobian.setElementAt(7, 0, -1.0); 814 815 jacobian.setElementAt(2, 1, -1.0); 816 jacobian.setElementAt(6, 1, 1.0); 817 818 jacobian.setElementAt(1, 2, 1.0); 819 jacobian.setElementAt(3, 2, -1.0); 820 } 821 } 822 823 /** 824 * Computes the skew-symmetric matrix of provided matrix of size 3x1 or 825 * 13. 826 * Skew-symmetric matrices are specially useful if several cross-products 827 * need to be done for the same first vector over different vectors. 828 * That is, having a vector a and vectors b1...bn of size 3x1: 829 * c1 = a x b1 830 * c2 = a x b2 831 * ... 832 * cn = a x bn 833 * Instead of computing each product separately, the second factor can be 834 * converted in a matrix where each row contains one of the vectors on the 835 * second factor: 836 * B = [b1, b2, ..., bn]; 837 * And all the cross products shown above can be computed as a single matrix 838 * multiplication in the form: 839 * C = skewMatrix(a) * B; 840 * 841 * @param m Matrix of size 3x1 or 1x3 that will be used to compute the 842 * skew-symmetric matrix. 843 * @return Skew matrix. 844 * @throws WrongSizeException Exception thrown if provided array doesn't 845 * have length equal to 3. 846 */ 847 public static Matrix skewMatrix(final Matrix m) throws WrongSizeException { 848 boolean columnwise; 849 if (m.getRows() == 3 && m.getColumns() == 1) { 850 columnwise = false; 851 } else if (m.getRows() == 1 && m.getColumns() == 3) { 852 columnwise = true; 853 } else { 854 throw new WrongSizeException(); 855 } 856 857 final var sk = new Matrix(3, 3); 858 internalSkewMatrix(m, sk, columnwise); 859 return sk; 860 } 861 862 /** 863 * Computes the cross product of two vectors of length 3 864 * The cross product of two vectors a and b is denoted as 'axb' or 'a^b', 865 * resulting in a perpendicular vector to both a and b vectors. This implies 866 * the following relation: 867 * c·a = 0 868 * c·b = 0 869 * A cross product can be calculated as the determinant of this matrix 870 * |i j k | 871 * |a0 a1 a2| 872 * |b0 b1 b2| 873 * Resulting: 874 * a x b = (a1b2 - a2b1) i + (a2b0 - a0b2) j + (a0b1 - a1b0) k = 875 * = (a1b2 - a2b1, a2b0 - a0b2, a0b1 - a1b0) 876 * This implementation does not use the determinant to compute the cross 877 * product. A symmetric-skew matrix will be computed and used instead. 878 * 879 * @param v1 Left vector in the crossProduct operation (A in axb) 880 * @param v2 Right vector in the crossProduct operation (b in axb) 881 * @param result array where the cross product of vectors v1 and b2 will be 882 * stored. 883 * @param jacobian1 jacobian of v1. Must be 3x3. 884 * @param jacobian2 jacobian of v2. Must be 3x3. 885 * @throws WrongSizeException Exception thrown if provided vectors don't 886 * have length 3, or if jacobians are not 3x3. 887 * @see #Utils for more information. 888 */ 889 public static void crossProduct( 890 final double[] v1, final double[] v2, final double[] result, 891 final Matrix jacobian1, final Matrix jacobian2) throws WrongSizeException { 892 893 if (jacobian1 != null && (jacobian1.getRows() != 3 || jacobian1.getColumns() != 3)) { 894 throw new WrongSizeException("if provided jacobian of v1 is not 3x3"); 895 } 896 if (jacobian2 != null && (jacobian2.getRows() != 3 || jacobian2.getColumns() != 3)) { 897 throw new WrongSizeException("if provided jacobian of v2 is not 3x3"); 898 } 899 900 crossProduct(v1, v2, result); 901 902 if (jacobian1 != null) { 903 skewMatrix(v1, jacobian1); 904 jacobian1.multiplyByScalar(-1.0); 905 } 906 907 if (jacobian2 != null) { 908 skewMatrix(v2, jacobian2); 909 } 910 } 911 912 /** 913 * Computes the cross product of two vectors of length 3 914 * The cross product of two vectors a and b is denoted as 'axb' or 'a^b', 915 * resulting in a perpendicular vector to both a and b vectors. This implies 916 * the following relation: 917 * c·a = 0 918 * c·b = 0 919 * A cross product can be calculated as the determinant of this matrix 920 * |i j k | 921 * |a0 a1 a2| 922 * |b0 b1 b2| 923 * Resulting: 924 * a x b = (a1b2 - a2b1) i + (a2b0 - a0b2) j + (a0b1 - a1b0) k = 925 * = (a1b2 - a2b1, a2b0 - a0b2, a0b1 - a1b0) 926 * This implementation does not use the determinant to compute the cross 927 * product. A symmetric-skew matrix will be computed and used instead. 928 * 929 * @param v1 Left vector in the crossProduct operation (A in axb) 930 * @param v2 Right vector in the crossProduct operation (b in axb) 931 * @param result array where the cross product of vectors v1 and b2 will be 932 * stored. 933 * @throws WrongSizeException Exception thrown if provided vectors don't 934 * have length 3. 935 * @see #Utils for more information. 936 */ 937 public static void crossProduct(final double[] v1, final double[] v2, final double[] result) 938 throws WrongSizeException { 939 if (v1.length != 3 || v2.length != 3 || result.length != 3) { 940 throw new WrongSizeException("v1, v2 and result must have length 3"); 941 } 942 943 final var skm = Utils.skewMatrix(v1); 944 skm.multiply(Matrix.newFromArray(v2)); 945 946 final var buffer = skm.getBuffer(); 947 System.arraycopy(buffer, 0, result, 0, buffer.length); 948 } 949 950 /** 951 * Computes the cross product of two vectors of length 3 952 * The cross product of two vectors a and b is denoted as 'axb' or 'a^b', 953 * resulting in a perpendicular vector to both a and b vectors. This implies 954 * the following relation: 955 * c·a = 0 956 * c·b = 0 957 * A cross product can be calculated as the determinant of this matrix 958 * |i j k | 959 * |a0 a1 a2| 960 * |b0 b1 b2| 961 * Resulting: 962 * a x b = (a1b2 - a2b1) i + (a2b0 - a0b2) j + (a0b1 - a1b0) k = 963 * = (a1b2 - a2b1, a2b0 - a0b2, a0b1 - a1b0) 964 * This implementation does not use the determinant to compute the cross 965 * product. A symmetric-skew matrix will be computed and used instead. 966 * 967 * @param v1 Left vector in the crossProduct operation (A in axb) 968 * @param v2 Right vector in the crossProduct operation (b in axb) 969 * @return Vector containing the cross product of vectors v1 and b2. 970 * @throws WrongSizeException Exception thrown if provided vectors don't 971 * have length 3. 972 * @see #Utils for more information. 973 */ 974 public static double[] crossProduct(final double[] v1, final double[] v2) throws WrongSizeException { 975 if (v1.length != 3 || v2.length != 3) { 976 throw new WrongSizeException("v1, v2 must have length 3"); 977 } 978 979 final var skm = Utils.skewMatrix(v1); 980 skm.multiply(Matrix.newFromArray(v2)); 981 982 return skm.toArray(); 983 } 984 985 /** 986 * Computes the cross product of two vectors of length 3 987 * The cross product of two vectors a and b is denoted as 'axb' or 'a^b', 988 * resulting in a perpendicular vector to both a and b vectors. This implies 989 * the following relation: 990 * c·a = 0 991 * c·b = 0 992 * A cross product can be calculated as the determinant of this matrix 993 * |i j k | 994 * |a0 a1 a2| 995 * |b0 b1 b2| 996 * Resulting: 997 * a x b = (a1b2 - a2b1) i + (a2b0 - a0b2) j + (a0b1 - a1b0) k = 998 * = (a1b2 - a2b1, a2b0 - a0b2, a0b1 - a1b0) 999 * This implementation does not use the determinant to compute the cross 1000 * product. A symmetric-skew matrix will be computed and used instead. 1001 * 1002 * @param v1 Left vector in the crossProduct operation (A in axb) 1003 * @param v2 Right vector in the crossProduct operation (b in axb) 1004 * @param jacobian1 jacobian of v1. Must be 3x3. 1005 * @param jacobian2 jacobian of v2. Must be 3x3. 1006 * @return Vector containing the cross product of vectors v1 and b2. 1007 * @throws WrongSizeException Exception thrown if provided vectors don't 1008 * have length 3, or if jacobians are not 3x3. 1009 */ 1010 public static double[] crossProduct( 1011 final double[] v1, final double[] v2, final Matrix jacobian1, final Matrix jacobian2) 1012 throws WrongSizeException { 1013 1014 if (jacobian1 != null && (jacobian1.getRows() != 3 || jacobian1.getColumns() != 3)) { 1015 throw new WrongSizeException("if provided jacobian of v1 is not 3x3"); 1016 } 1017 if (jacobian2 != null && (jacobian2.getRows() != 3 || jacobian2.getColumns() != 3)) { 1018 throw new WrongSizeException("if provided jacobian of v2 is not 3x3"); 1019 } 1020 1021 if (jacobian1 != null) { 1022 skewMatrix(v1, jacobian1); 1023 jacobian1.multiplyByScalar(-1.0); 1024 } 1025 1026 if (jacobian2 != null) { 1027 skewMatrix(v2, jacobian2); 1028 } 1029 1030 return crossProduct(v1, v2); 1031 } 1032 1033 /** 1034 * Computes the cross product of one vector of length 3 and N vectors of 1035 * length 3. 1036 * The cross product of two vectors a and b is denoted as 'axb' or 'a^b', 1037 * resulting in a perpendicular vector to both a and b vectors. This implies 1038 * the following relation: 1039 * c·a = 0 1040 * c·b = 0 1041 * A cross product can be calculated as the determinant of this matrix 1042 * |i j k | 1043 * |a0 a1 a2| 1044 * |b0 b1 b2| 1045 * Resulting: 1046 * a x b = (a1b2 - a2b1) i + (a2b0 - a0b2) j + (a0b1 - a1b0) k = 1047 * = (a1b2 - a2b1, a2b0 - a0b2, a0b1 - a1b0) 1048 * This implementation does not use the determinant to compute the cross 1049 * product. A symmetric-skew matrix will be computed and used instead. 1050 * The result is stored into provided result matrix, which will be resized 1051 * if needed 1052 * 1053 * @param v Left operand in crossProduct operation 1054 * @param m Right operand in crossProduct operation, matrix containing a 1055 * vector in every row. 1056 * @param result Matrix where the cross products of vector v and the vectors 1057 * contained in matrix m will be stored. 1058 * @throws WrongSizeException Exception thrown if provided vectors don't 1059 * have length 3 or if number of rows of provided matrix isn't 3. 1060 * @see Utils#skewMatrix(Matrix) for more information. 1061 */ 1062 public static void crossProduct(final double[] v, final Matrix m, final Matrix result) throws WrongSizeException { 1063 if (v.length != 3 || m.getColumns() != 3) { 1064 throw new WrongSizeException(); 1065 } 1066 Utils.skewMatrix(v).multiply(m, result); 1067 } 1068 1069 /** 1070 * Computes the cross product of one vector of length 3 and N vectors of 1071 * length 3. 1072 * The cross product of two vectors a and b is denoted as 'axb' or 'a^b', 1073 * resulting in a perpendicular vector to both a and b vectors. This implies 1074 * the following relation: 1075 * c·a = 0 1076 * c·b = 0 1077 * A cross product can be calculated as the determinant of this matrix 1078 * |i j k | 1079 * |a0 a1 a2| 1080 * |b0 b1 b2| 1081 * Resulting: 1082 * a x b = (a1b2 - a2b1) i + (a2b0 - a0b2) j + (a0b1 - a1b0) k = 1083 * = (a1b2 - a2b1, a2b0 - a0b2, a0b1 - a1b0) 1084 * This implementation does not use the determinant to compute the cross 1085 * product. A symmetric-skew matrix will be computed and used instead. 1086 * 1087 * @param v Left operand in crossProduct operation 1088 * @param m Right operand in crossProduct operation, matrix containing a 1089 * vector in every row. 1090 * @return Matrix containing the cross products of vector v and the vectors 1091 * contained in matrix m. 1092 * @throws WrongSizeException Exception thrown if provided vectors don't 1093 * have length 3 or if number of rows of provided matrix isn't 3. 1094 * @see Utils#skewMatrix(Matrix) for more information. 1095 */ 1096 public static Matrix crossProduct(final double[] v, final Matrix m) throws WrongSizeException { 1097 if (v.length != 3 || m.getColumns() != 3) { 1098 throw new WrongSizeException(); 1099 } 1100 1101 final var skm = Utils.skewMatrix(v); 1102 skm.multiply(m); 1103 1104 return skm; 1105 } 1106 1107 /** 1108 * Check if the matrix is symmetric. 1109 * 1110 * @param m Input matrix. 1111 * @param threshold Threshold value to determine if a matrix is symmetric. 1112 * This value is typically zero or close to zero. 1113 * @return Boolean relation whether the matrix is symmetric or not. 1114 * @throws IllegalArgumentException Raised if provided threshold is negative 1115 */ 1116 public static boolean isSymmetric(final Matrix m, final double threshold) { 1117 1118 if (threshold < MIN_THRESHOLD) { 1119 throw new IllegalArgumentException(); 1120 } 1121 1122 final var length = m.getRows(); 1123 if (length != m.getColumns()) { 1124 return false; 1125 } 1126 1127 for (var j = 0; j < length; j++) { 1128 for (var i = j + 1; i < length; i++) { 1129 if (Math.abs(m.getElementAt(j, i) - m.getElementAt(i, j)) > threshold) { 1130 return false; 1131 } 1132 } 1133 } 1134 return true; 1135 } 1136 1137 /** 1138 * Check if the matrix is symmetric. DEFAULT_SYMMETRIC_THRESHOLD is used 1139 * as threshold. 1140 * 1141 * @param m Input matrix. 1142 * @return Boolean relation whether the matrix is symmetric or not. 1143 */ 1144 public static boolean isSymmetric(final Matrix m) { 1145 return isSymmetric(m, DEFAULT_SYMMETRIC_THRESHOLD); 1146 } 1147 1148 /** 1149 * Checks if the matrix is orthogonal (its transpose is its inverse). 1150 * Orthogonal matrices must be square and non-singular 1151 * 1152 * @param m Input matrix 1153 * @param threshold Threshold value to determine if a matrix is orthogonal. 1154 * This value is typically zero or close to zero. 1155 * @return True if matrix is orthogonal, false otherwise. 1156 * @throws IllegalArgumentException Raised if provided threshold is negative 1157 */ 1158 public static boolean isOrthogonal(final Matrix m, final double threshold) { 1159 1160 if (threshold < MIN_THRESHOLD) { 1161 throw new IllegalArgumentException(); 1162 } 1163 1164 final var length = m.getRows(); 1165 if (length != m.getColumns()) { 1166 return false; 1167 } 1168 1169 try { 1170 // to get faster computation it is better to try m' * m 1171 final var tmp = m.transposeAndReturnNew(); 1172 tmp.multiply(m); 1173 1174 for (var j = 0; j < length; j++) { 1175 for (var i = j + 1; i < length; i++) { 1176 if (Math.abs(tmp.getElementAt(i, j)) > threshold) { 1177 return false; 1178 } 1179 } 1180 } 1181 return true; 1182 1183 } catch (final WrongSizeException e) { 1184 return false; 1185 } 1186 } 1187 1188 /** 1189 * Checks if the matrix is orthogonal (its transpose is its inverse). 1190 * DEFAULT_ORTHOGONAL_THRESHOLD is used as threshold. Orthogonal matrices 1191 * must be square and non-singular 1192 * 1193 * @param m Input matrix. 1194 * @return True if matrix is orthogonal, false otherwise. 1195 */ 1196 public static boolean isOrthogonal(final Matrix m) { 1197 return isOrthogonal(m, DEFAULT_ORTHOGONAL_THRESHOLD); 1198 } 1199 1200 /** 1201 * Checks if the matrix is orthonormal (it is orthogonal and its Frobenius 1202 * norm is one) 1203 * 1204 * @param m Input matrix 1205 * @param threshold Threshold value to determine if a matrix is orthonormal. 1206 * This value is typically zero or close to zero. 1207 * @return True if matrix is orthonormal, false otherwise. 1208 * @throws IllegalArgumentException Raised if provided threshold is negative 1209 */ 1210 public static boolean isOrthonormal(final Matrix m, final double threshold) { 1211 return isOrthogonal(m, threshold) && (Math.abs(Utils.normF(m) - 1.0) < threshold); 1212 } 1213 1214 /** 1215 * Checks if the matrix is orthonormal up to DEFAULT_ORTHOGONAL_THRESHOLD 1216 * (it is orthogonal and its Frobenius norm is one) 1217 * 1218 * @param m Input matrix 1219 * @return True if matrix is orthonormal, false otherwise. 1220 */ 1221 public static boolean isOrthonormal(final Matrix m) { 1222 return isOrthonormal(m, DEFAULT_ORTHOGONAL_THRESHOLD); 1223 } 1224 1225 /** 1226 * Computes the dot product of provided arrays as the sum of the product of 1227 * the elements of both arrays. 1228 * 1229 * @param firstOperand first operand. 1230 * @param secondOperand second operand. 1231 * @return dot product. 1232 * @throws IllegalArgumentException if first and second operands arrays 1233 * don't have the same length. 1234 */ 1235 public static double dotProduct(final double[] firstOperand, final double[] secondOperand) { 1236 return ArrayUtils.dotProduct(firstOperand, secondOperand); 1237 } 1238 1239 /** 1240 * Computes the dot product of provided arrays as the sum of the product of 1241 * the elements of both arrays. 1242 * 1243 * @param firstOperand first operand. 1244 * @param secondOperand second operand. 1245 * @param jacobianFirst matrix where jacobian of first operand will be 1246 * stored. Must be a column matrix having the same number of rows as the 1247 * first operand length. 1248 * @param jacobianSecond matrix where jacobian of second operand will be 1249 * stored. Must be a column matrix having the same number of rows as the 1250 * second operand length. 1251 * @return dot product. 1252 * @throws IllegalArgumentException if first and second operands don't have 1253 * the same length or if jacobian matrices are not column vectors having the 1254 * same length as their respective operands. 1255 */ 1256 public static double dotProduct( 1257 final double[] firstOperand, final double[] secondOperand, final Matrix jacobianFirst, 1258 final Matrix jacobianSecond) { 1259 return ArrayUtils.dotProduct(firstOperand, secondOperand, jacobianFirst, jacobianSecond); 1260 } 1261 1262 /** 1263 * Computes the dot product of provided matrices, as the sum of the product 1264 * of the elements on both matrices, assuming that both represent column 1265 * vectors. 1266 * 1267 * @param firstOperand first operand. 1268 * @param secondOperand second operand. 1269 * @return dot product. 1270 * @throws WrongSizeException if first operand columns are not equal to 1271 * second operand rows, or if first operand rows is not one, or if second 1272 * operand columns is not one. 1273 */ 1274 public static double dotProduct(final Matrix firstOperand, final Matrix secondOperand) throws WrongSizeException { 1275 if (firstOperand.getColumns() != secondOperand.getRows() || firstOperand.getRows() != 1 1276 || secondOperand.getColumns() != 1) { 1277 throw new WrongSizeException("first operand must be 1xN, and second operand must be Nx1"); 1278 } 1279 1280 return dotProduct(firstOperand.getBuffer(), secondOperand.getBuffer()); 1281 } 1282 1283 /** 1284 * Computes the dot product of provided matrices, as the sum of the product 1285 * of the elements on both matrices, assuming that both represent column 1286 * vectors. 1287 * 1288 * @param firstOperand first operand. Must be 1xN. 1289 * @param secondOperand second operand. Must be Nx1. 1290 * @param jacobianFirst matrix where jacobian of first operand will be 1291 * stored. Must be a column matrix having the same number of rows as the 1292 * first operand length. Must be Nx1. 1293 * @param jacobianSecond matrix where jacobian of second operand will be 1294 * stored. Must be a column matrix having the same number of rows as the 1295 * second operand length. Must be Nx1. 1296 * @return dot product. 1297 * @throws WrongSizeException if first operand columns are not equal to 1298 * second operand rows, or if first operand rows is not one, or if second 1299 * operand columns is not one. 1300 * @throws IllegalArgumentException if jacobian matrices are not column 1301 * vectors having the same length as their respective operands. 1302 */ 1303 public static double dotProduct( 1304 final Matrix firstOperand, final Matrix secondOperand, final Matrix jacobianFirst, 1305 final Matrix jacobianSecond) throws WrongSizeException { 1306 if (firstOperand.getColumns() != secondOperand.getRows() || firstOperand.getRows() != 1 1307 || secondOperand.getColumns() != 1) { 1308 throw new WrongSizeException("first operand must be 1xN, and second operand must be Nx1"); 1309 } 1310 1311 return dotProduct(firstOperand.getBuffer(), secondOperand.getBuffer(), jacobianFirst, jacobianSecond); 1312 } 1313 1314 1315 /** 1316 * Computes the Schur complement of a symmetric matrix. 1317 * For a matrix M of size sxs having the typical partition 1318 * M = [A B ; B' C] 1319 * where A is posxpos, B is posx(s - pos) and C is (s-pos)x(s-pos) 1320 * Then pos indicates the position that delimits the separation between 1321 * these sub-matrices in the diagonal. 1322 * If fromStart is true, then Schur complement of A is computed and result 1323 * will have size (s-pos)x(s-pos). 1324 * If fromStart is false, then Schur complement of C is computed and result 1325 * will have size posxpos. 1326 * <p> 1327 * Definitions and rationale: 1328 * If M is a symmetrical matrix partitioned as: 1329 * M = [M_11 M_12 ; M_12' M_22] 1330 * then the Schur complement of M_11 is 1331 * S = M_22 - M_12 ¡ * M_11^-1 * M_12 1332 * The optionally returned inverse block is just iA = M_11^-1 1333 * whose name comes from 'inverse of A', A given by the popular partition 1334 * of M = [A B ; B' C], therefore A = M_11. 1335 * If M is symmetrical and positive, then using the Cholesky decomposition 1336 * M = [R_11' 0 ; R_12' R_22] * [R_11 R_12 ; 0 R_22] 1337 * leads to 1338 * S = R_22' * R_22 1339 * iA = (R_11' * R_11)^-1 = R_11^-1 * R_11^-T 1340 * which constitutes and efficient and stable way to compute the Schur 1341 * complement. The square root factors R_22 and R_11^_1 can be obtained by 1342 * setting the optional flag sqrt. 1343 * The Schur complement has applications for solving linear equations, and 1344 * applications to probability theory to compute conditional covariances. 1345 * 1346 * @param m matrix to compute the Schur complement from 1347 * @param pos position to delimit the Schur complement. 1348 * @param fromStart true to compute Schur complement of A, false to compute 1349 * Schur complement of C. 1350 * @param sqrt true to return the square root of the Schur complement, which 1351 * is an upper triangular matrix, false to return the full Schur complement, 1352 * which is S'*S. 1353 * @param result instance where the Schur complement will be stored. If 1354 * needed this instance will be resized. 1355 * @param iA instance where the inverse block will be stored, if provided. 1356 * @throws IllegalArgumentException if m is not square, pos is greater or 1357 * equal than matrix size, or pos is zero. 1358 * @throws DecomposerException if m is numerically unstable. 1359 * @throws RankDeficientMatrixException if iA matrix is singular or 1360 * numerically unstable. 1361 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1362 */ 1363 public static void schurc(final Matrix m, int pos, final boolean fromStart, final boolean sqrt, final Matrix result, 1364 final Matrix iA) throws DecomposerException, RankDeficientMatrixException { 1365 final var rows = m.getRows(); 1366 final var cols = m.getColumns(); 1367 if (rows != cols) { 1368 throw new IllegalArgumentException("matrix must be square"); 1369 } 1370 if (pos >= rows) { 1371 throw new IllegalArgumentException("pos must be lower than rows"); 1372 } 1373 if (pos == 0) { 1374 throw new IllegalArgumentException("pos must be greater than 0"); 1375 } 1376 1377 try { 1378 final Matrix m2; 1379 if (fromStart) { 1380 // if position is taken from origin, no reordering is needed 1381 m2 = m; 1382 } else { 1383 // if position is taken from pos to end, then reordering is needed 1384 1385 // create index positions to reorder matrix to decompose using 1386 // Cholesky 1387 final var index = new int[rows]; 1388 final var complSize = rows - pos; 1389 for (int i = 0, value = pos; i < complSize; i++, value++) { 1390 index[i] = value; 1391 } 1392 for (int i = complSize, j = 0; i < rows; i++, j++) { 1393 index[i] = j; 1394 } 1395 1396 m2 = new Matrix(rows, rows); 1397 for (var j = 0; j < rows; j++) { 1398 for (var i = 0; i < rows; i++) { 1399 m2.setElementAt(i, j, m.getElementAt(index[i], 1400 index[j])); 1401 } 1402 } 1403 1404 pos = rows - pos; 1405 } 1406 1407 final var decomposer = new CholeskyDecomposer(m2); 1408 decomposer.decompose(); 1409 1410 // pick the upper right matrix of cholesky 1411 final var r = decomposer.getR(); 1412 1413 // s, which is the square root of Schur complement and is the result, 1414 // is an upper right matrix because it is a sub-matrix of r 1415 final var sizeMinus1 = rows - 1; 1416 r.getSubmatrix(pos, pos, sizeMinus1, sizeMinus1, result); 1417 1418 if (!sqrt) { 1419 // if the full version of the Schur complement is required 1420 // we multiply by its transpose S = S'*S 1421 final var transS = result.transposeAndReturnNew(); 1422 transS.multiply(result); //transS is now S'*S 1423 result.copyFrom(transS); 1424 } 1425 1426 if (iA != null) { 1427 // minor of r is the square root of inverse block 1428 final var posMinus1 = pos - 1; 1429 r.getSubmatrix(0, 0, posMinus1, posMinus1, iA); 1430 Utils.inverse(iA, iA); 1431 // to obtain full inverse block iA we need to multiply it by its 1432 // transpose iA = iA * iA' 1433 final var transiA = iA.transposeAndReturnNew(); 1434 iA.multiply(transiA); 1435 } 1436 } catch (final DecomposerException | RankDeficientMatrixException e) { 1437 // if matrix is numerically unstable or singular 1438 throw e; 1439 } catch (final AlgebraException ignore) { 1440 // Cholesky will not raise any other exception 1441 } 1442 } 1443 1444 /** 1445 * Computes the Schur complement of a symmetric matrix, returning always the 1446 * full Schur complement. 1447 * 1448 * @param m matrix to compute the Schur complement from 1449 * @param pos position to delimit the Schur complement. 1450 * @param fromStart true to compute Schur complement of A, false to compute 1451 * Schur complement of C. 1452 * @param result instance where the Schur complement will be stored. If 1453 * needed this instance will be resized. 1454 * @param iA instance where the inverse block will be stored, if provided. 1455 * @throws IllegalArgumentException if m is not square, pos is greater or 1456 * equal than matrix size, or pos is zero. 1457 * @throws DecomposerException if m is numerically unstable. 1458 * @throws RankDeficientMatrixException if iA matrix is singular or 1459 * numerically unstable. 1460 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1461 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1462 */ 1463 public static void schurc( 1464 final Matrix m, final int pos, final boolean fromStart, final Matrix result, final Matrix iA) 1465 throws DecomposerException, RankDeficientMatrixException { 1466 schurc(m, pos, fromStart, false, result, iA); 1467 } 1468 1469 /** 1470 * Computes the Schur complement of the sub-matrix A within a symmetric 1471 * matrix, returning always the full Schur complement. 1472 * 1473 * @param m matrix to compute the Schur complement from 1474 * @param pos position to delimit the Schur complement. 1475 * @param result instance where the Schur complement will be stored. If 1476 * needed this instance will be resized. 1477 * @param iA instance where the inverse block will be stored, if provided. 1478 * @throws IllegalArgumentException if m is not square, pos is greater or 1479 * equal than matrix size, or pos is zero. 1480 * @throws DecomposerException if m is numerically unstable. 1481 * @throws RankDeficientMatrixException if iA matrix is singular or 1482 * numerically unstable. 1483 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1484 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1485 */ 1486 public static void schurc(final Matrix m, final int pos, final Matrix result, final Matrix iA) 1487 throws DecomposerException, RankDeficientMatrixException { 1488 schurc(m, pos, true, result, iA); 1489 } 1490 1491 /** 1492 * Computes the Schur complement of a symmetric matrix. 1493 * 1494 * @param m matrix to compute the Schur complement from. 1495 * @param pos position to delimit the Schur complement. 1496 * @param fromStart true to compute Schur complement of A, false to compute 1497 * Schur complement of C. 1498 * @param sqrt true to return the square root of the Schur complement, which 1499 * is an upper triangular matrix, false to return the full Schur complement, 1500 * which is S'*S. 1501 * @param iA instance where the inverse block will be stored, if provided. 1502 * @return a new matrix containing the Schur complement. 1503 * @throws IllegalArgumentException if m is not square, pos is greater or 1504 * equal than matrix size, or pos is zero. 1505 * @throws DecomposerException if m is numerically unstable. 1506 * @throws RankDeficientMatrixException if iA matrix is singular or 1507 * numerically unstable. 1508 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1509 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1510 */ 1511 public static Matrix schurcAndReturnNew( 1512 final Matrix m, final int pos, final boolean fromStart, final boolean sqrt, final Matrix iA) 1513 throws DecomposerException, RankDeficientMatrixException { 1514 final var rows = m.getRows(); 1515 final var cols = m.getColumns(); 1516 if (rows != cols) { 1517 throw new IllegalArgumentException("matrix must be square"); 1518 } 1519 if (pos >= rows) { 1520 throw new IllegalArgumentException("pos must be lower than rows"); 1521 } 1522 if (pos == 0) { 1523 throw new IllegalArgumentException("pos must be greater than 0"); 1524 } 1525 1526 final var size = fromStart ? pos : rows - pos; 1527 Matrix result = null; 1528 try { 1529 result = new Matrix(size, size); 1530 } catch (final WrongSizeException ignore) { 1531 // never happens 1532 } 1533 1534 schurc(m, pos, fromStart, sqrt, result, iA); 1535 return result; 1536 } 1537 1538 /** 1539 * Computes the Schur complement of a symmetric matrix, returning always the 1540 * full Schur complement. 1541 * 1542 * @param m matrix to compute the Schur complement from 1543 * @param pos position to delimit the Schur complement. 1544 * @param fromStart true to compute Schur complement of A, false to compute 1545 * Schur complement of C. 1546 * @param iA instance where the inverse block will be stored, if provided. 1547 * @return a new matrix containing the Schur complement. 1548 * @throws IllegalArgumentException if m is not square, pos is greater or 1549 * equal than matrix size, or pos is zero. 1550 * @throws DecomposerException if m is numerically unstable. 1551 * @throws RankDeficientMatrixException if iA matrix is singular or 1552 * numerically unstable. 1553 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1554 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrice">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1555 */ 1556 public static Matrix schurcAndReturnNew( 1557 final Matrix m, final int pos, final boolean fromStart, final Matrix iA) 1558 throws DecomposerException, RankDeficientMatrixException { 1559 return schurcAndReturnNew(m, pos, fromStart, false, iA); 1560 } 1561 1562 /** 1563 * Computes the Schur complement of the sub-matrix A within a symmetric 1564 * matrix, returning always the full Schur complement. 1565 * 1566 * @param m matrix to compute the Schur complement from 1567 * @param pos position to delimit the Schur complement. 1568 * @param iA instance where the inverse block will be stored, if provided. 1569 * @return a new matrix containing the Schur complement. 1570 * @throws IllegalArgumentException if m is not square, pos is greater or 1571 * equal than matrix size, or pos is zero. 1572 * @throws DecomposerException if m is numerically unstable. 1573 * @throws RankDeficientMatrixException if iA matrix is singular or 1574 * numerically unstable. 1575 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1576 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1577 */ 1578 public static Matrix schurcAndReturnNew(final Matrix m, final int pos, final Matrix iA) 1579 throws DecomposerException, RankDeficientMatrixException { 1580 return schurcAndReturnNew(m, pos, true, iA); 1581 } 1582 1583 /** 1584 * Computes the Schur complement of a symmetric matrix. 1585 * 1586 * @param m matrix to compute the Schur complement from. 1587 * @param pos position to delimit the Schur complement. 1588 * @param fromStart true to compute Schur complement of A, false to compute 1589 * Schur complement of C. 1590 * @param sqrt true to return the square root of the Schur complement, which 1591 * is an upper triangular matrix, false to return the full Schur complement, 1592 * which is S'*S. 1593 * @param result instance where the Schur complement will be stored. If 1594 * needed this instance will be resized. 1595 * @throws IllegalArgumentException if m is not square, pos is greater or 1596 * equal than matrix size, or pos is zero. 1597 * @throws DecomposerException if m is numerically unstable. 1598 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1599 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1600 */ 1601 public static void schurc(final Matrix m, final int pos, final boolean fromStart, final boolean sqrt, 1602 final Matrix result) throws DecomposerException { 1603 try { 1604 schurc(m, pos, fromStart, sqrt, result, null); 1605 } catch (final RankDeficientMatrixException ignore) { 1606 // if no iA is provided, this exception is never raised. 1607 } 1608 } 1609 1610 /** 1611 * Computes the Schur complement of a symmetric matrix, returning always the 1612 * full Schur complement. 1613 * 1614 * @param m matrix to compute the Schur complement from 1615 * @param pos position to delimit the Schur complement. 1616 * @param fromStart true to compute Schur complement of A, false to compute 1617 * Schur complement of C. 1618 * @param result instance where the Schur complement will be stored. If 1619 * needed this instance will be resized. 1620 * @throws IllegalArgumentException if m is not square, pos is greater or 1621 * equal than matrix size, or pos is zero. 1622 * @throws DecomposerException if m is numerically unstable. 1623 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1624 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1625 */ 1626 public static void schurc( 1627 final Matrix m, final int pos, final boolean fromStart, final Matrix result) throws DecomposerException { 1628 try { 1629 schurc(m, pos, fromStart, result, null); 1630 } catch (final RankDeficientMatrixException ignore) { 1631 // if no iA is provided, this exception is never raised. 1632 } 1633 } 1634 1635 /** 1636 * Computes the Schur complement of the sub-matrix A within a symmetric 1637 * matrix, returning always the full Schur complement. 1638 * 1639 * @param m matrix to compute the Schur complement from 1640 * @param pos position to delimit the Schur complement. 1641 * @param result instance where the Schur complement will be stored. If 1642 * needed this instance will be resized. 1643 * @throws IllegalArgumentException if m is not square, pos is greater or 1644 * equal than matrix size, or pos is zero. 1645 * @throws DecomposerException if m is numerically unstable. 1646 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1647 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1648 */ 1649 public static void schurc(final Matrix m, final int pos, final Matrix result) throws DecomposerException { 1650 try { 1651 schurc(m, pos, result, null); 1652 } catch (final RankDeficientMatrixException ignore) { 1653 // if no iA is provided, this exception is never raised. 1654 } 1655 } 1656 1657 /** 1658 * Computes the Schur complement of a symmetric matrix. 1659 * 1660 * @param m matrix to compute the Schur complement from. 1661 * @param pos position to delimit the Schur complement. 1662 * @param fromStart true to compute Schur complement of A, false to compute 1663 * Schur complement of C. 1664 * @param sqrt true to return the square root of the Schur complement, which 1665 * is an upper triangular matrix, false to return the full Schur complement, 1666 * which is S'*S. 1667 * @return a new matrix containing the Schur complement. 1668 * @throws IllegalArgumentException if m is not square, pos is greater or 1669 * equal than matrix size, or pos is zero. 1670 * @throws DecomposerException if m is numerically unstable. 1671 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1672 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1673 */ 1674 public static Matrix schurcAndReturnNew( 1675 final Matrix m, final int pos, final boolean fromStart, final boolean sqrt) throws DecomposerException { 1676 Matrix result = null; 1677 try { 1678 result = schurcAndReturnNew(m, pos, fromStart, sqrt, null); 1679 } catch (final RankDeficientMatrixException ignore) { 1680 // if no iA is provided, this exception is never raised. 1681 } 1682 1683 return result; 1684 } 1685 1686 /** 1687 * Computes the Schur complement of a symmetric matrix, returning always the 1688 * full Schur complement. 1689 * 1690 * @param m matrix to compute the Schur complement from 1691 * @param pos position to delimit the Schur complement. 1692 * @param fromStart true to compute Schur complement of A, false to compute 1693 * Schur complement of C. 1694 * @return a new matrix containing the Schur complement. 1695 * @throws IllegalArgumentException if m is not square, pos is greater or 1696 * equal than matrix size, or pos is zero. 1697 * @throws DecomposerException if m is numerically unstable. 1698 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1699 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1700 */ 1701 public static Matrix schurcAndReturnNew( 1702 final Matrix m, final int pos, final boolean fromStart) throws DecomposerException { 1703 Matrix result = null; 1704 try { 1705 result = schurcAndReturnNew(m, pos, fromStart, null); 1706 } catch (final RankDeficientMatrixException ignore) { 1707 // if no iA is provided, this exception is never raised. 1708 } 1709 1710 return result; 1711 } 1712 1713 /** 1714 * Computes the Schur complement of the sub-matrix A within a symmetric 1715 * matrix, returning always the full Schur complement. 1716 * 1717 * @param m matrix to compute the Schur complement from 1718 * @param pos position to delimit the Schur complement. 1719 * @return a new matrix containing the Schur complement. 1720 * @throws IllegalArgumentException if m is not square, pos is greater or 1721 * equal than matrix size, or pos is zero. 1722 * @throws DecomposerException if m is numerically unstable. 1723 * @see #schurc(com.irurueta.algebra.Matrix, int, boolean, boolean, com.irurueta.algebra.Matrix, com.irurueta.algebra.Matrix) 1724 * @see <a href="http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices">http://scicomp.stackexchange.com/questions/5050/cholesky-factorization-of-block-matrices</a> 1725 */ 1726 public static Matrix schurcAndReturnNew(final Matrix m, final int pos) throws DecomposerException { 1727 Matrix result = null; 1728 try { 1729 result = schurcAndReturnNew(m, pos, null); 1730 } catch (final RankDeficientMatrixException ignore) { 1731 // if no iA is provided, this exception is never raised. 1732 } 1733 1734 return result; 1735 } 1736 }