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 decomposer computes economy QR decomposition, which is faster than
20   * typical QR decomposition.
21   */
22  @SuppressWarnings("DuplicatedCode")
23  public class EconomyQRDecomposer extends Decomposer {
24  
25      /**
26       * Constant defining default round error when determining full rank of
27       * matrices. This value is zero by default
28       */
29      public static final double DEFAULT_ROUND_ERROR = 0.0;
30  
31      /**
32       * Constant defining minimum allowed round error value when determining full
33       * rank of matrices.
34       */
35      public static final double MIN_ROUND_ERROR = 0.0;
36  
37      /**
38       * Internal matrix containing results of decomposition.
39       */
40      private Matrix qr;
41  
42      /**
43       * Internal array containing diagonal of R.
44       */
45      private double[] rDiag;
46  
47      /**
48       * Constructor of this class.
49       */
50      public EconomyQRDecomposer() {
51          super();
52          qr = null;
53          rDiag = null;
54      }
55  
56      /**
57       * Constructor of this class.
58       *
59       * @param inputMatrix Reference to input matrix to be decomposed.
60       */
61      public EconomyQRDecomposer(final Matrix inputMatrix) {
62          super(inputMatrix);
63          qr = null;
64          rDiag = null;
65      }
66  
67      /**
68       * Returns decomposer type corresponding to Economy QR decomposition.
69       *
70       * @return Decomposer type.
71       */
72      @Override
73      public DecomposerType getDecomposerType() {
74          return DecomposerType.QR_ECONOMY_DECOMPOSITION;
75      }
76  
77      /**
78       * Sets reference to input matrix to be decomposed.
79       *
80       * @param inputMatrix Reference to input matrix to be decomposed.
81       * @throws LockedException Exception thrown if attempting to call this
82       *                         method while this instance remains locked.
83       */
84      @Override
85      public void setInputMatrix(final Matrix inputMatrix) throws LockedException {
86          super.setInputMatrix(inputMatrix);
87          qr = null;
88          rDiag = null;
89      }
90  
91      /**
92       * Returns boolean indicating whether decomposition has been computed and
93       * results can be retrieved.
94       * Attempting to retrieve decomposition results when not available, will
95       * probably raise a NotAvailableException.
96       *
97       * @return Boolean indicating whether decomposition has been computed and
98       * results can be retrieved.
99       */
100     @Override
101     public boolean isDecompositionAvailable() {
102         return qr != null;
103     }
104 
105     /**
106      * This method computes QR matrix decomposition, which consists on factoring
107      * provided input matrix into an orthogonal matrix (Q) and an upper
108      * triangular matrix (R).
109      * In other words, if input matrix is A, then: A = Q * R
110      * Note: During execution of this method, this instance will be locked.
111      * Note: After execution of this method, QR decomposition will be available
112      * and operations such as retrieving Q and R matrices or solving systems of
113      * linear equations will be able to be done. Attempting to call any of such
114      * operations before calling this method will raise a NotAvailableException
115      * because they require computation of QR decomposition first.
116      *
117      * @throws NotReadyException Exception thrown if attempting to call this
118      *                           method when this instance is not ready (i.e. no input matrix has been
119      *                           provided).
120      * @throws LockedException   Exception thrown if this decomposer is already
121      *                           locked before calling this method. Notice that this method will actually
122      *                           lock this instance while it is being executed.
123      */
124     @Override
125     public void decompose() throws NotReadyException, LockedException {
126 
127         if (!isReady()) {
128             throw new NotReadyException();
129         }
130         if (isLocked()) {
131             throw new LockedException();
132         }
133 
134         locked = true;
135 
136         final var rows = inputMatrix.getRows();
137         final var columns = inputMatrix.getColumns();
138         qr = new Matrix(inputMatrix);
139 
140         rDiag = new double[columns];
141         double nrm;
142         double s;
143 
144         // Main loop
145         for (var k = 0; k < columns; k++) {
146             // Compute 2-norm of k-th column without under/overflow
147             nrm = 0.0;
148 
149             for (var i = k; i < rows; i++) {
150                 nrm = Math.sqrt(nrm * nrm + Math.pow(qr.getElementAt(i, k), 2.0));
151             }
152 
153             if (nrm != 0.0) {
154                 // Form k-th Householder vector
155                 if (qr.getElementAt(k, k) < 0.0) {
156                     nrm = -nrm;
157                 }
158 
159                 for (var i = k; i < rows; i++) {
160                     qr.setElementAt(i, k, qr.getElementAt(i, k) / nrm);
161                 }
162                 qr.setElementAt(k, k, qr.getElementAt(k, k) + 1.0);
163 
164                 // Apply transformation to remaining columns
165                 for (var j = k + 1; j < columns; j++) {
166                     s = 0.0;
167                     for (var i = k; i < rows; i++) {
168                         s += qr.getElementAt(i, k) * qr.getElementAt(i, j);
169                     }
170                     s = -s / qr.getElementAt(k, k);
171                     for (var i = k; i < rows; i++) {
172                         qr.setElementAt(i, j, qr.getElementAt(i, j) + s * qr.getElementAt(i, k));
173                     }
174                 }
175             }
176 
177             rDiag[k] = -nrm;
178         }
179 
180         locked = false;
181     }
182 
183     /**
184      * Returns boolean indicating whether provided input matrix has full rank or
185      * not.
186      * Squared matrices having full rank also have determinant different from
187      * zero.
188      * Note: Because of rounding errors, testing whether a matrix has full rank
189      * or not, might obtain unreliable results especially for non-square
190      * matrices. In such cases matrices usually tend to be considered as full
191      * rank even when they are not.
192      *
193      * @return Boolean indicating whether provided input matrix has full rank or
194      * not.
195      * @throws NotAvailableException Exception thrown if attempting to call this
196      *                               method before computing QR decomposition. To avoid this exception call
197      *                               decompose() method first.
198      * @throws WrongSizeException    Exception thrown if provided rounding error is
199      *                               lower than minimum allowed value (MIN_ROUND_ERROR).
200      * @see #decompose()
201      */
202     public boolean isFullRank() throws NotAvailableException, WrongSizeException {
203         return isFullRank(DEFAULT_ROUND_ERROR);
204     }
205 
206     /**
207      * Returns boolean indicating whether provided input matrix has full rank or
208      * not.
209      * Squared matrices having full rank also have determinant different from
210      * zero.
211      * Note: Because of rounding errors, testing whether a matrix has full rank
212      * or not, might obtain unreliable results especially for non-square
213      * matrices. In such cases matrices usually tend to be considered as full
214      * rank even when they are not.
215      *
216      * @param roundingError Determines the amount of margin given to determine
217      *                      whether a matrix has full rank or not due to rounding errors. If not
218      *                      provided, by default rounding error is set to zero, but this value can
219      *                      be relaxed if needed.
220      * @return Boolean indicating whether provided input matrix has full rank or
221      * not.
222      * @throws NotAvailableException    Exception thrown if attempting to call this
223      *                                  method before computing QR decomposition. To avoid this exception call
224      *                                  decompose() method first.
225      * @throws WrongSizeException       Exception thrown if provided input matrix has
226      *                                  less rows than columns.
227      * @throws IllegalArgumentException Exception thrown if provided rounding
228      *                                  error is lower than minimum allowed value (MIN_ROUND_ERROR).
229      * @see #decompose()
230      */
231     public boolean isFullRank(final double roundingError) throws NotAvailableException, WrongSizeException {
232 
233         if (!isDecompositionAvailable()) {
234             throw new NotAvailableException();
235         }
236         if (roundingError < MIN_ROUND_ERROR) {
237             throw new IllegalArgumentException();
238         }
239 
240         final var rows = inputMatrix.getRows();
241         final var columns = inputMatrix.getColumns();
242 
243         if (rows < columns) {
244             throw new WrongSizeException();
245         }
246 
247         for (var j = 0; j < columns; j++) {
248             if (Math.abs(rDiag[j]) < roundingError) {
249                 return false;
250             }
251         }
252         return true;
253     }
254 
255     /**
256      * Computes the Householder vectors and store them in provided matrix.
257      * Provided matrix will be resized if needed
258      *
259      * @param h Matrix where Householder vectors will be stored
260      * @throws NotAvailableException Exception thrown if attempting to call this
261      *                               method before computing QR decomposition. To avoid this exception call
262      *                               decompose() method first.
263      * @see #decompose()
264      */
265     public void getH(final Matrix h) throws NotAvailableException {
266         if (!isDecompositionAvailable()) {
267             throw new NotAvailableException();
268         }
269 
270         final var rows = inputMatrix.getRows();
271         final var columns = inputMatrix.getColumns();
272         if (h.getRows() != rows || h.getColumns() != columns) {
273             try {
274                 h.resize(rows, columns);
275             } catch (final WrongSizeException ignore) {
276                 // never happens
277             }
278         }
279 
280         for (var i = 0; i < rows; i++) {
281             for (var j = 0; j < columns; j++) {
282                 if (i >= j) {
283                     h.setElementAt(i, j, qr.getElementAt(i, j));
284                 } else {
285                     h.setElementAt(i, j, 0.0);
286                 }
287             }
288         }
289     }
290 
291     /**
292      * Returns the Householder vectors.
293      *
294      * @return Lower trapezoidal matrix whose columns define the reflections.
295      * @throws NotAvailableException Exception thrown if attempting to call this
296      *                               method before computing QR decomposition. To avoid this exception call
297      *                               decompose() method first.
298      * @see #decompose()
299      */
300     public Matrix getH() throws NotAvailableException {
301         final var rows = inputMatrix.getRows();
302         final var columns = inputMatrix.getColumns();
303         Matrix out = null;
304         try {
305             out = new Matrix(rows, columns);
306         } catch (final WrongSizeException ignore) {
307             // never happens
308         }
309         getH(out);
310         return out;
311     }
312 
313     /**
314      * Computes upper triangular factor matrix and stores it into provided
315      * matrix.
316      * QR decomposition decomposes input matrix into Q (orthogonal matrix) and
317      * R, which is an upper triangular matrix.
318      *
319      * @param r Upper triangular factor matrix
320      * @throws NotAvailableException Exception thrown if attempting to call this
321      *                               method before computing QR decomposition. To avoid this exception call
322      *                               decompose() method first.
323      * @see #decompose()
324      */
325     public void getR(final Matrix r) throws NotAvailableException {
326         if (!isDecompositionAvailable()) {
327             throw new NotAvailableException();
328         }
329         final var columns = inputMatrix.getColumns();
330         final var rows = inputMatrix.getRows();
331 
332         if (r.getRows() != columns || r.getColumns() != columns) {
333             try {
334                 r.resize(columns, columns);
335             } catch (final WrongSizeException ignore) {
336                 // never happens
337             }
338         }
339 
340         for (var i = 0; i < columns; i++) {
341             if (i < rows) {
342                 for (var j = 0; j < columns; j++) {
343                     if (i < j) {
344                         r.setElementAt(i, j, qr.getElementAt(i, j));
345                     } else if (i == j) {
346                         r.setElementAt(i, j, rDiag[i]);
347                     } else {
348                         r.setElementAt(i, j, 0.0);
349                     }
350                 }
351             } else {
352                 for (var j = 0; j < columns; j++) {
353                     r.setElementAt(i, j, 0.0);
354                 }
355             }
356         }
357     }
358 
359     /**
360      * Return upper triangular factor matrix.
361      * QR decomposition decomposes input matrix into Q (orthogonal matrix) and
362      * R, which is an upper triangular matrix.
363      *
364      * @return Upper triangular factor matrix
365      * @throws NotAvailableException Exception thrown if attempting to call this
366      *                               method before computing QR decomposition. To avoid this exception call
367      *                               decompose() method first.
368      * @see #decompose()
369      */
370     public Matrix getR() throws NotAvailableException {
371         final var columns = inputMatrix.getColumns();
372         Matrix out = null;
373         try {
374             out = new Matrix(columns, columns);
375         } catch (final WrongSizeException ignore) {
376             // never happens
377         }
378         getR(out);
379         return out;
380     }
381 
382     /**
383      * Computes the economy-sized orthogonal factor matrix and stores it into
384      * provided matrix.
385      * QR decomposition decomposes input matrix into Q, which is an orthogonal
386      * matrix and R (upper triangular matrix).
387      *
388      * @param q Orthogonal factor matrix.
389      * @throws NotAvailableException Exception thrown if attempting to call
390      * @throws WrongSizeException    Exception thrown if provided input matrix has
391      *                               less rows than columns.
392      * @see #decompose()
393      */
394     public void getQ(final Matrix q) throws NotAvailableException, WrongSizeException {
395         if (!isDecompositionAvailable()) {
396             throw new NotAvailableException();
397         }
398 
399         final var rows = inputMatrix.getRows();
400         final var columns = inputMatrix.getColumns();
401         double s;
402 
403         if (rows < columns) {
404             throw new WrongSizeException();
405         }
406 
407         if (q.getRows() != rows || q.getColumns() != columns) {
408             try {
409                 q.resize(rows, columns);
410             } catch (final WrongSizeException ignore) {
411                 // never happens
412             }
413         }
414 
415         for (var k = columns - 1; k >= 0; k--) {
416             for (var i = 0; i < rows; i++) {
417                 q.setElementAt(i, k, 0.0);
418             }
419             q.setElementAt(k, k, 1.0);
420             for (var j = k; j < columns; j++) {
421                 if (qr.getElementAt(k, k) != 0) {
422                     s = 0.0;
423                     for (var i = k; i < rows; i++) {
424                         s += qr.getElementAt(i, k) * q.getElementAt(i, j);
425                     }
426 
427                     s = -s / qr.getElementAt(k, k);
428                     for (var i = k; i < rows; i++) {
429                         q.setElementAt(i, j, q.getElementAt(i, j) + s * qr.getElementAt(i, k));
430                     }
431                 }
432             }
433         }
434     }
435 
436     /**
437      * Return the economy-sized orthogonal factor matrix.
438      * QR decomposition decomposes input matrix into Q, which is an orthogonal
439      * matrix and R (upper triangular matrix).
440      *
441      * @return Orthogonal factor matrix.
442      * @throws NotAvailableException Exception thrown if attempting to call
443      * @throws WrongSizeException    Exception thrown if provided input matrix has
444      *                               fewer rows than columns.
445      * @see #decompose()
446      */
447     public Matrix getQ() throws NotAvailableException, WrongSizeException {
448         final var rows = inputMatrix.getRows();
449         final var columns = inputMatrix.getColumns();
450 
451         final var out = new Matrix(rows, columns);
452         getQ(out);
453         return out;
454     }
455 
456     /**
457      * Solves a linear system of equations of the following form:
458      * A * X = B, where A is the input matrix provided for QR decomposition,
459      * X is the solution to the system of equations, and B is the parameters
460      * vector/matrix.
461      * Note: This method can be reused for different b vector/matrices without
462      * having to recompute QR decomposition on the same input matrix.
463      * Note: Provided b matrix must have the same number of rows as provided
464      * input matrix A, otherwise a WrongSizeException will be raised.
465      * Note: Provided input matrix "A" must have at least as many rows as columns,
466      * otherwise a WrongSizeException will be raised as well. For input matrices
467      * having a higher number of rows than columns, the system of equations will
468      * be overdetermined and the least squares solution will be found.
469      * Note: If provided input matrix A is rank deficient, a
470      * RankDeficientMatrixException will be thrown.
471      * Note: In order to execute this method, a QR decomposition must be
472      * available, otherwise a NotAvailableException will be raised. In order to
473      * avoid this exception call decompose() method first.
474      *
475      * @param b      Parameters matrix that determine a linear system of equations.
476      *               Provided matrix must have the same number of rows as provided input
477      *               matrix for QR decomposition. Besides, each column on parameters matrix
478      *               will represent a new system of equations, whose solution will be returned
479      *               on appropriate column as an output of this method.
480      * @param result Matrix containing solution of linear system of equations on
481      *               each column for each column of provided matrix of parameters b. Provided
482      *               matrix will be resized if needed
483      * @throws NotAvailableException        Exception thrown if attempting to call this
484      *                                      method before computing QR decomposition. To avoid this exception call
485      *                                      decompose() method first.
486      * @throws WrongSizeException           Exception thrown if attempting to call this
487      *                                      method using an input matrix with fewer rows than columns; or if provided
488      *                                      parameters matrix (b) does not have the same number of rows as input
489      *                                      matrix being QR decomposed.
490      * @throws RankDeficientMatrixException Exception thrown if provided input
491      *                                      matrix to be QR decomposed is rank deficient. In this case linear system
492      *                                      of equations cannot be solved.
493      */
494     public void solve(final Matrix b, final Matrix result) throws NotAvailableException, WrongSizeException,
495             RankDeficientMatrixException {
496         solve(b, DEFAULT_ROUND_ERROR, result);
497     }
498 
499     /**
500      * Solves a linear system of equations of the following form:
501      * A * X = B, where A is the input matrix provided for QR decomposition,
502      * X is the solution to the system of equations, and B is the parameters
503      * vector/matrix.
504      * Note: This method can be reused for different b vector/matrices without
505      * having to recompute QR decomposition on the same input matrix.
506      * Note: Provided b matrix must have the same number of rows as provided
507      * input matrix A, otherwise a WrongSizeException will be raised.
508      * Note: Provided input matrix "A" must have at least as many rows as columns,
509      * otherwise a WrongSizeException will be raised as well. For input matrices
510      * having a higher number of rows than columns, the system of equations will
511      * be overdetermined and the least squares solution will be found.
512      * Note: If provided input matrix A is rank deficient, a
513      * RankDeficientMatrixException will be thrown.
514      * Note: In order to execute this method, a QR decomposition must be
515      * available, otherwise a NotAvailableException will be raised. In order to
516      * avoid this exception call decompose() method first.
517      *
518      * @param b             Parameters matrix that determine a linear system of equations.
519      *                      Provided matrix must have the same number of rows as provided input
520      *                      matrix for QR decomposition. Besides, each column on parameters matrix
521      *                      will represent a new system of equations, whose solution will be returned
522      *                      on appropriate column as an output of this method.
523      * @param roundingError threshold to determine whether matrix b has full rank or not.
524      *                      By default, this is typically a tiny value close to zero.
525      * @param result        Matrix containing solution of linear system of equations on
526      *                      each column for each column of provided matrix of parameters b. Provided
527      *                      matrix will be resized if needed
528      * @throws NotAvailableException        Exception thrown if attempting to call this
529      *                                      method before computing QR decomposition. To avoid this exception call
530      *                                      decompose() method first.
531      * @throws WrongSizeException           Exception thrown if attempting to call this
532      *                                      method using an input matrix with fewer rows than columns; or if provided
533      *                                      parameters matrix (b) does not have the same number of rows as input
534      *                                      matrix being QR decomposed.
535      * @throws RankDeficientMatrixException Exception thrown if provided input
536      *                                      matrix to be QR decomposed is rank deficient. In this case linear system
537      *                                      of equations cannot be solved.
538      * @throws IllegalArgumentException     if provided rounding error is negative.
539      */
540     public void solve(final Matrix b, final double roundingError, final Matrix result)
541             throws NotAvailableException, WrongSizeException, RankDeficientMatrixException {
542 
543         if (!isDecompositionAvailable()) {
544             throw new NotAvailableException();
545         }
546 
547         final var rows = inputMatrix.getRows();
548         final var columns = inputMatrix.getColumns();
549         final var rowsB = b.getRows();
550         final var colsB = b.getColumns();
551         double s;
552 
553         if (rowsB != rows) {
554             throw new WrongSizeException();
555         }
556 
557         if (roundingError < MIN_ROUND_ERROR) {
558             throw new IllegalArgumentException();
559         }
560 
561         if (rows < columns) {
562             throw new WrongSizeException();
563         }
564         if (!isFullRank(roundingError)) {
565             throw new RankDeficientMatrixException();
566         }
567 
568         // Copy b into X
569         final var x = new Matrix(b);
570 
571         // Compute Y = transpose(Q) * B
572         for (var k = 0; k < columns; k++) {
573             for (var j = 0; j < colsB; j++) {
574                 s = 0.0;
575                 for (var i = k; i < rows; i++) {
576                     s += qr.getElementAt(i, k) * x.getElementAt(i, j);
577                 }
578                 s = -s / qr.getElementAt(k, k);
579                 for (var i = k; i < rows; i++) {
580                     x.setElementAt(i, j, x.getElementAt(i, j) + s * qr.getElementAt(i, k));
581                 }
582             }
583         }
584 
585         // Solve R * X = Y
586         for (var k = columns - 1; k >= 0; k--) {
587             for (var j = 0; j < colsB; j++) {
588                 x.setElementAt(k, j, x.getElementAt(k, j) / rDiag[k]);
589             }
590             for (var i = 0; i < k; i++) {
591                 for (var j = 0; j < colsB; j++) {
592                     x.setElementAt(i, j, x.getElementAt(i, j) - x.getElementAt(k, j) * qr.getElementAt(i, k));
593                 }
594             }
595         }
596 
597         // Pick only first columns rows of X in case of overdetermined systems
598         // (where rows > columns), otherwise rows == columns and we pick them all
599         if (result.getRows() != columns || result.getColumns() != colsB) {
600             // resize result
601             result.resize(columns, colsB);
602         }
603         result.setSubmatrix(0, 0, columns - 1, colsB - 1, x,
604                 0, 0, columns - 1,
605                 colsB - 1);
606     }
607 
608     /**
609      * Solves a linear system of equations of the following form:
610      * A * X = B, where A is the input matrix provided for QR decomposition,
611      * X is the solution to the system of equations, and B is the parameters
612      * vector/matrix.
613      * Note: This method can be reused for different b vector/matrices without
614      * having to recompute QR decomposition on the same input matrix.
615      * Note: Provided b matrix must have the same number of rows as provided
616      * input matrix A, otherwise a WrongSizeException will be raised.
617      * Note: Provided input matrix "A" must have at least as many rows as columns,
618      * otherwise a WrongSizeException will be raised as well. For input matrices
619      * having a higher number of rows than columns, the system of equations will
620      * be overdetermined and the least squares solution will be found.
621      * Note: If provided input matrix A is rank deficient, a
622      * RankDeficientMatrixException will be thrown.
623      * Note: In order to execute this method, a QR decomposition must be
624      * available, otherwise a NotAvailableException will be raised. In order to
625      * avoid this exception call decompose() method first.
626      *
627      * @param b Parameters matrix that determine a linear system of equations.
628      *          Provided matrix must have the same number of rows as provided input
629      *          matrix for QR decomposition. Besides, each column on parameters matrix
630      *          will represent a new system of equations, whose solution will be returned
631      *          on appropriate column as an output of this method.
632      * @return Matrix containing solution of linear system of equations on each
633      * column for each column of provided matrix of parameters b.
634      * @throws NotAvailableException        Exception thrown if attempting to call this
635      *                                      method before computing QR decomposition. To avoid this exception call
636      *                                      decompose() method first.
637      * @throws WrongSizeException           Exception thrown if attempting to call this
638      *                                      method using an input matrix with fewer rows than columns; or if provided
639      *                                      parameters matrix (b) does not have the same number of rows as input
640      *                                      matrix being QR decomposed.
641      * @throws RankDeficientMatrixException Exception thrown if provided input
642      *                                      matrix to be QR decomposed is rank deficient. In this case linear system
643      *                                      of equations cannot be solved.
644      */
645     public Matrix solve(final Matrix b) throws NotAvailableException, WrongSizeException, RankDeficientMatrixException {
646         return solve(b, DEFAULT_ROUND_ERROR);
647     }
648 
649     /**
650      * Solves a linear system of equations of the following form:
651      * A * X = B, where A is the input matrix provided for QR decomposition,
652      * X is the solution to the system of equations, and B is the parameters
653      * vector/matrix.
654      * Note: This method can be reused for different b vector/matrices without
655      * having to recompute QR decomposition on the same input matrix.
656      * Note: Provided b matrix must have the same number of rows as provided
657      * input matrix A, otherwise a WrongSizeException will be raised.
658      * Note: Provided input matrix "A" must have at least as many rows as columns,
659      * otherwise a WrongSizeException will be raised as well. For input matrices
660      * having a higher number of rows than columns, the system of equations will
661      * be overdetermined and the least squares solution will be found.
662      * Note: If provided input matrix A is rank deficient, a
663      * RankDeficientMatrixException will be thrown.
664      * Note: In order to execute this method, a QR decomposition must be
665      * available, otherwise a NotAvailableException will be raised. In order to
666      * avoid this exception call decompose() method first.
667      *
668      * @param b             Parameters matrix that determine a linear system of equations.
669      *                      Provided matrix must have the same number of rows as provided input
670      *                      matrix for QR decomposition. Besides, each column on parameters matrix
671      *                      will represent a new system of equations, whose solution will be returned
672      *                      on appropriate column as an output of this method.
673      * @param roundingError Determines the amount of margin given to determine
674      *                      whether a matrix has full rank or not due to rounding errors. If not
675      *                      provided, by default rounding error is set to zero, but this value can be
676      *                      relaxed if needed.
677      * @return Matrix containing solution of linear system of equations on each
678      * column for each column of provided matrix of parameters b.
679      * @throws NotAvailableException        Exception thrown if attempting to call this
680      *                                      method before computing QR decomposition. To avoid this exception call
681      *                                      decompose() method first.
682      * @throws WrongSizeException           Exception thrown if attempting to call this
683      *                                      method using an input matrix with less rows than columns; or if provided
684      *                                      parameters matrix (b) does not have the same number of rows as input
685      *                                      matrix being QR decomposed.
686      * @throws RankDeficientMatrixException Exception thrown if provided input
687      *                                      matrix to be QR decomposed is rank deficient. In this case linear system
688      *                                      of equations cannot be solved.
689      * @throws IllegalArgumentException     Exception thrown if provided rounding
690      *                                      error is lower than minimum allowed value (MIN_ROUND_ERROR)
691      * @see #decompose()
692      */
693     public Matrix solve(final Matrix b, final double roundingError) throws NotAvailableException, WrongSizeException,
694             RankDeficientMatrixException {
695 
696         final var columns = inputMatrix.getColumns();
697         final var colsB = b.getColumns();
698         final var out = new Matrix(columns, colsB);
699         solve(b, roundingError, out);
700         return out;
701     }
702 }