View Javadoc
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 }