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 allows decomposition of matrices using LU decomposition, which
20   * consists on retrieving two triangular matrices (lower triangular and upper
21   * triangular) as a decomposition of provided input matrix.
22   * In other words, if input matrix is A, then: A = L * U, where L is lower
23   * triangular matrix and U is upper triangular matrix.
24   * LU decomposition is a useful and fast way of solving systems of linear
25   * equations, computing determinants or finding whether a matrix is singular.
26   */
27  @SuppressWarnings("DuplicatedCode")
28  public class LUDecomposer extends Decomposer {
29  
30      /**
31       * Constant defining default round error when determining singularity of
32       * matrices. This value is zero by default.
33       */
34      public static final double DEFAULT_ROUND_ERROR = 0.0;
35  
36      /**
37       * Constant defining minimum allowed round error value when determining
38       * singularity of matrices.
39       */
40      public static final double MIN_ROUND_ERROR = 0.0;
41  
42      /**
43       * Internal matrix containing results of decomposition.
44       */
45      private Matrix lu;
46  
47      /**
48       * Internal array containing pivotings after decomposition.
49       */
50      int[] piv;
51  
52      /**
53       * Member containing pivot sign after decomposition.
54       */
55      int pivSign;
56  
57      /**
58       * Constructor of this class.
59       */
60      public LUDecomposer() {
61          super();
62          lu = null;
63          piv = null;
64      }
65  
66      /**
67       * Constructor of this class.
68       *
69       * @param inputMatrix Reference to input matrix to be decomposed
70       */
71      public LUDecomposer(final Matrix inputMatrix) {
72          super(inputMatrix);
73          lu = null;
74          piv = null;
75      }
76  
77      /**
78       * Returns decomposer type corresponding to LU decomposition
79       *
80       * @return Decomposer type
81       */
82      @Override
83      public DecomposerType getDecomposerType() {
84          return DecomposerType.LU_DECOMPOSITION;
85      }
86  
87      /**
88       * Sets reference to input matrix to be decomposed.
89       *
90       * @param inputMatrix Reference to input matrix to be decomposed.
91       * @throws LockedException Exception thrown if attempting to call this
92       *                         method while this instance remains locked.
93       */
94      @Override
95      public void setInputMatrix(final Matrix inputMatrix) throws LockedException {
96          super.setInputMatrix(inputMatrix);
97          lu = null;
98          piv = null;
99      }
100 
101     /**
102      * Returns boolean indicating whether decomposition has been computed and
103      * results can be retrieved.
104      * Attempting to retrieve decomposition results when not available, will
105      * probably raise a NotAvailableException
106      *
107      * @return Boolean indicating whether decomposition has been computed and
108      * results can be retrieved.
109      */
110     @Override
111     public boolean isDecompositionAvailable() {
112         return lu != null;
113     }
114 
115     /**
116      * This method computes LU matrix decomposition, which consists on
117      * retrieving two triangular matrices (Lower triangular and Upper
118      * triangular) as a decomposition of provided input matrix.
119      * In other words, if input matrix is A, then: A = L * U
120      * Note: During execution of this method, this instance will be locked,
121      * and hence attempting to set some parameters might raise a
122      * LockedException.
123      * Note: After execution of this method, LU decomposition will be
124      * available and operations such as retrieving L and U matrices or
125      * computing determinants among others will be able to be done.
126      * Attempting to call any of such operations before calling this method
127      * will raise a NotAvailableException because they require computation of
128      * LU decomposition first.
129      *
130      * @throws NotReadyException   Exception thrown if attempting to call this
131      *                             method when this instance is not ready (i.e. no input matrix has been
132      *                             provided).
133      * @throws LockedException     Exception thrown if attempting to call this
134      *                             method when this instance is not ready (i.e. no input matrix has been
135      *                             provided).
136      * @throws DecomposerException Exception thrown if for any reason
137      *                             decomposition fails while executing, like when convergence of results
138      *                             can not be obtained, etc.
139      */
140     @Override
141     public void decompose() throws NotReadyException, LockedException, DecomposerException {
142 
143         if (!isReady()) {
144             throw new NotReadyException();
145         }
146         if (isLocked()) {
147             throw new LockedException();
148         }
149 
150         final var rows = inputMatrix.getRows();
151         final var columns = inputMatrix.getColumns();
152 
153         if (columns > rows) {
154             throw new DecomposerException();
155         }
156 
157         locked = true;
158 
159         // copy matrix contents
160         lu = new Matrix(inputMatrix);
161 
162         piv = new int[rows];
163         for (var i = 0; i < rows; i++) {
164             piv[i] = i;
165         }
166 
167         pivSign = 1;
168 
169         // Main loop
170         for (var k = 0; k < columns; k++) {
171             // Find pivot
172             var p = k;
173             for (var i = k + 1; i < rows; i++) {
174                 p = Math.abs(lu.getElementAt(i, k)) > Math.abs(lu.getElementAt(p, k)) ? i : p;
175             }
176             // Exchange if necessary
177             if (p != k) {
178                 for (var j = 0; j < columns; j++) {
179                     final var t = lu.getElementAt(p, j);
180                     lu.setElementAt(p, j, lu.getElementAt(k, j));
181                     lu.setElementAt(k, j, t);
182                 }
183                 final var t = piv[p];
184                 piv[p] = piv[k];
185                 piv[k] = t;
186                 pivSign = -pivSign;
187             }
188             // Compute multipliers and eliminate k-th column
189             if (lu.getElementAt(k, k) != 0.0) {
190                 for (var i = k + 1; i < rows; i++) {
191                     lu.setElementAt(i, k, lu.getElementAt(i, k) / lu.getElementAt(k, k));
192                     for (var j = k + 1; j < columns; j++) {
193                         lu.setElementAt(i, j, lu.getElementAt(i, j)
194                                 - lu.getElementAt(i, k) * lu.getElementAt(k, j));
195                     }
196                 }
197             }
198         }
199 
200         locked = false;
201     }
202 
203     /**
204      * Return boolean indicating whether provided input matrix is singular
205      * or not after computing LU decomposition. Returns true if singular and
206      * false otherwise.
207      * A matrix is defined as singular if its determinant is zero, hence
208      * provided input matrix must be square (even though LU decomposition can be
209      * computed for non-square matrices), otherwise a WrongSizeException will
210      * be raised when calling this method. LU decomposition can be used to avoid
211      * determinant computation by means of pivoting, because LU decomposition
212      * obtains two triangular matrices, and the determinant of a triangular
213      * matrix is just the product of the diagonal elements. Hence, if any
214      * element on the diagonal of LU decomposition is zero, determinant will be
215      * zero and input matrix will be singular.
216      *
217      * @return Boolean indicating whether provided input matrix is singular
218      * or not.
219      * @throws NotAvailableException    Exception thrown if attempting to call this
220      *                                  method before computing LU decomposition. To avoid this exception call
221      *                                  decompose() method first.
222      * @throws WrongSizeException       Exception thrown if attempting to call this
223      *                                  method using a non-square input matrix.
224      * @throws IllegalArgumentException Exception thrown if provided rounding
225      *                                  error is lower than minimum allowed value (MIN_ROUND_ERROR)
226      * @see #decompose()
227      */
228     public boolean isSingular() throws NotAvailableException, WrongSizeException {
229         return isSingular(DEFAULT_ROUND_ERROR);
230     }
231 
232     /**
233      * Return boolean indicating whether provided input matrix is singular
234      * or not after computing LU decomposition. Returns true if singular and
235      * false otherwise.
236      * A matrix is defined as singular if its determinant is zero, hence
237      * provided input matrix must be square (even though LU decomposition can be
238      * computed for non-square matrices), otherwise a WrongSizeException will
239      * be raised when calling this method. LU decomposition can be used to avoid
240      * determinant computation by means of pivoting, because LU decomposition
241      * obtains two triangular matrices, and the determinant of a triangular
242      * matrix is just the product of the diagonal elements. Hence, if any
243      * element on the diagonal of LU decomposition is zero, determinant will be
244      * zero and input matrix will be singular.
245      *
246      * @param roundingError Determines the amount of margin given to determine
247      *                      whether a matrix is singular or not due to rounding errors. If not
248      *                      provided, by default rounding error is set to zero, but this value can be
249      *                      relaxed if needed.
250      * @return Boolean indicating whether provided input matrix is singular
251      * or not.
252      * @throws NotAvailableException    Exception thrown if attempting to call this
253      *                                  method before computing LU decomposition. To avoid this exception call
254      *                                  decompose() method first.
255      * @throws WrongSizeException       Exception thrown if attempting to call this
256      *                                  method using a non-square input matrix.
257      * @throws IllegalArgumentException Exception thrown if provided rounding
258      *                                  error is lower than minimum allowed value (MIN_ROUND_ERROR)
259      * @see #decompose()
260      */
261     public boolean isSingular(final double roundingError) throws NotAvailableException, WrongSizeException {
262 
263         if (!isDecompositionAvailable()) {
264             throw new NotAvailableException();
265         }
266         if (roundingError < MIN_ROUND_ERROR) {
267             throw new IllegalArgumentException();
268         }
269 
270         // A matrix is singular when its determinant is zero. Hence, in order to
271         // compute singularity matrix must be square
272         final var rows = inputMatrix.getRows();
273         final var columns = inputMatrix.getColumns();
274         if (rows != columns) {
275             throw new WrongSizeException();
276         }
277 
278         // Since we have computed LU decomposition into triangular matrices. The
279         // determinant of a triangular matrix is the product of its diagonal
280         // elements. Hence, if any element in the diagonal is zero, input matrix
281         // will be singular.
282         for (var j = 0; j < columns; j++) {
283             if (lu.getElementAt(j, j) == 0.0) {
284                 return true;
285             }
286         }
287         return false;
288     }
289 
290     /**
291      * Fills provided matrix instance with the Lower triangular matrix
292      * resulting from LU decomposition before correcting any possible pivots.
293      * Hence, this matrix is only ensured to be Lower triangular.
294      * In other words, this matrix does not ensure the product A = L * U, to
295      * achieve this, we need to apply pivot correction.
296      * A pivot corrected version of this matrix can be obtained by calling
297      * method getL().
298      *
299      * @param pivottedL Lower triangular matrix.
300      * @throws NotAvailableException Exception thrown if attempting to call
301      *                               this method before computing LU decomposition. To avoid this exception
302      *                               call decompose() method first.
303      * @see #getL()
304      * @see #decompose()
305      */
306     public void getPivottedL(final Matrix pivottedL) throws NotAvailableException {
307 
308         if (!isDecompositionAvailable()) {
309             throw new NotAvailableException();
310         }
311 
312         final var rows = lu.getRows();
313         final var columns = lu.getColumns();
314 
315         if (pivottedL.getRows() != rows || pivottedL.getColumns() != columns) {
316             try {
317                 pivottedL.resize(rows, columns);
318             } catch (final WrongSizeException ignore) {
319                 // never happens
320             }
321         }
322 
323         for (var i = 0; i < rows; i++) {
324             for (var j = 0; j < columns; j++) {
325                 if (i > j) {
326                     pivottedL.setElementAt(i, j, lu.getElementAt(i, j));
327                 } else if (i == j) {
328                     pivottedL.setElementAt(i, j, 1.0);
329                 } else {
330                     pivottedL.setElementAt(i, j, 0.0);
331                 }
332             }
333         }
334     }
335 
336     /**
337      * Returns a new matrix instance containing the Lower triangular matrix
338      * resulting from LU decomposition before correcting any possible pivots.
339      * Hence, this matrix is only ensured to be Lower triangular.
340      * In other words, this matrix does not ensure the product A = L * U, to
341      * achieve this, we need to apply pivot correction.
342      * A pivot corrected version of this matrix can be obtained by calling
343      * method getL().
344      *
345      * @return Lower triangular matrix.
346      * @throws NotAvailableException Exception thrown if attempting to call
347      *                               this method before computing LU decomposition. To avoid this exception
348      *                               call decompose() method first.
349      * @see #getL()
350      * @see #decompose()
351      */
352     public Matrix getPivottedL() throws NotAvailableException {
353 
354         if (!isDecompositionAvailable()) {
355             throw new NotAvailableException();
356         }
357 
358         final var rows = lu.getRows();
359         final var columns = lu.getColumns();
360 
361         Matrix out = null;
362         try {
363             out = new Matrix(rows, columns);
364         } catch (final WrongSizeException ignore) {
365             // never happens
366         }
367         getPivottedL(out);
368         return out;
369     }
370 
371     /**
372      * Fills provided matrix instance with the pivot corrected Lower
373      * triangular matrix resulting from LU decomposition for provided input
374      * matrix.
375      * Since this matrix is pivot corrected, it might not be completely
376      * triangular, except for some row pivotting.
377      * Notice that LU decomposition obtains matrices in the form of
378      * A = L * U, where A is provided input matrix, L is lower triangular
379      * matrix and U is upper triangular matrix.
380      *
381      * @param l the Lower triangular matrix resulting from LU
382      *          decomposition for provided input matrix.
383      * @throws NotAvailableException Exception thrown if attempting to call
384      *                               this method before computing LU decomposition. To avoid this exception
385      *                               call decompose() method first.
386      * @see #decompose()
387      */
388     public void getL(final Matrix l) throws NotAvailableException {
389         if (!isDecompositionAvailable()) {
390             throw new NotAvailableException();
391         }
392 
393         final var rows = lu.getRows();
394         final var columns = lu.getColumns();
395 
396         if (l.getRows() != rows || l.getColumns() != columns) {
397             try {
398                 l.resize(rows, columns);
399             } catch (final WrongSizeException ignore) {
400                 // never happens
401             }
402         }
403 
404         for (var i = 0; i < rows; i++) {
405             for (var j = 0; j < columns; j++) {
406                 if (i > j) {
407                     l.setElementAt(piv[i], j, lu.getElementAt(i, j));
408                 } else if (i == j) {
409                     l.setElementAt(piv[i], j, 1.0);
410                 } else {
411                     l.setElementAt(piv[i], j, 0.0);
412                 }
413             }
414         }
415     }
416 
417     /**
418      * Returns a new matrix instance containing the pivot corrected Lower
419      * triangular matrix resulting from LU decomposition for provided input
420      * matrix.
421      * Since this matrix is pivot corrected, it might not be completely
422      * triangular, except for some row pivotting.
423      * Notice that LU decomposition obtains matrices in the form of
424      * A = L * U, where A is provided input matrix, L is lower triangular
425      * matrix and U is upper triangular matrix.
426      *
427      * @return Returns the Lower triangular matrix resulting from LU
428      * decomposition for provided input matrix.
429      * @throws NotAvailableException Exception thrown if attempting to call
430      *                               this method before computing LU decomposition. To avoid this exception
431      *                               call decompose() method first.
432      * @see #decompose()
433      */
434     public Matrix getL() throws NotAvailableException {
435         if (!isDecompositionAvailable()) {
436             throw new NotAvailableException();
437         }
438 
439         final var rows = lu.getRows();
440         final var columns = lu.getColumns();
441         Matrix out = null;
442         try {
443             out = new Matrix(rows, columns);
444         } catch (final WrongSizeException ignore) {
445             //never happens
446         }
447         getL(out);
448         return out;
449     }
450 
451     /**
452      * Fills provided matrix instance with the Upper triangular matrix
453      * resulting from LU decomposition for provided input matrix.
454      * Notice that LU decomposition obtains matrices in the form A = L * U,
455      * where A is provided input matrix, L is lower triangular matrix and U
456      * is upper triangular matrix.
457      *
458      * @param u Returns the Upper triangular matrix resulting from LU
459      *          decomposition for provided input matrix.
460      * @throws NotAvailableException Exception thrown if attempting to call
461      *                               this method before computing LU decomposition. To avoid this exception
462      *                               call decompose() method first.
463      * @see #decompose()
464      */
465     public void getU(final Matrix u) throws NotAvailableException {
466         if (!isDecompositionAvailable()) {
467             throw new NotAvailableException();
468         }
469 
470         final var columns = lu.getColumns();
471 
472         if (u.getRows() != columns || u.getColumns() != columns) {
473             try {
474                 u.resize(columns, columns);
475             } catch (final WrongSizeException ignore) {
476                 // never happens
477             }
478         }
479         for (var i = 0; i < columns; i++) {
480             for (var j = 0; j < columns; j++) {
481                 if (i <= j) {
482                     u.setElementAt(i, j, lu.getElementAt(i, j));
483                 } else {
484                     u.setElementAt(i, j, 0.0);
485                 }
486             }
487         }
488     }
489 
490     /**
491      * Returns a new matrix instance containing the Upper triangular matrix
492      * resulting from LU decomposition for provided input matrix.
493      * Notice that LU decomposition obtains matrices in the form A = L * U,
494      * where A is provided input matrix, L is lower triangular matrix and U
495      * is upper triangular matrix.
496      *
497      * @return Returns the Upper triangular matrix resulting from LU
498      * decomposition for provided input matrix.
499      * @throws NotAvailableException Exception thrown if attempting to call
500      *                               this method before computing LU decomposition. To avoid this exception
501      *                               call decompose() method first.
502      * @see #decompose()
503      */
504     public Matrix getU() throws NotAvailableException {
505         if (!isDecompositionAvailable()) {
506             throw new NotAvailableException();
507         }
508 
509         final var columns = lu.getColumns();
510         Matrix out = null;
511         try {
512             out = new Matrix(columns, columns);
513         } catch (final WrongSizeException ignore) {
514             // never happens
515         }
516         getU(out);
517         return out;
518     }
519 
520     /**
521      * Returns pivot permutation vector.
522      *
523      * @return Pivot permutation vector.
524      * @throws NotAvailableException Exception thrown if attempting to call
525      *                               this method before computing LU decomposition. To avoid this exception
526      *                               call decompose() method first.
527      */
528     public int[] getPivot() throws NotAvailableException {
529 
530         if (!this.isDecompositionAvailable()) {
531             throw new NotAvailableException();
532         }
533         return piv;
534     }
535 
536     /**
537      * Returns determinant of provided input matrix using LU decomposition as
538      * means to obtain it.
539      * Provided input matrix must be square (even though LU decomposition can
540      * be computed for non-square matrices), otherwise a WrongSizeException will
541      * be raised when calling this method.
542      * LU decomposition can be used to avoid determinant computation using other
543      * slow methods, because LU decomposition obtains two triangular matrices,
544      * and the determinant of a triangular matrix is just the product of the
545      * diagonal elements.
546      * Since the determinant of a matrix product is the product of determinants,
547      * then determinant of input matrix can be computed as the product of
548      * determinants of L and U.
549      * Finally, since L has ones on its diagonal, its determinant will be +-1,
550      * depending on the amount of pivots done on L, and determinant of U will be
551      * just the product of its diagonal elements.
552      *
553      * @return Determinant of provided input matrix.
554      * @throws NotAvailableException Exception thrown if attempting to call
555      *                               this method before computing LU decomposition. To avoid this exception
556      *                               call decompose() method first.
557      * @throws WrongSizeException    Exception thrown if attempting to call this
558      *                               method using a non-square input matrix.
559      * @see #decompose()
560      */
561     public double determinant() throws NotAvailableException, WrongSizeException {
562 
563         if (!isDecompositionAvailable()) {
564             throw new NotAvailableException();
565         }
566 
567         // Determinants can only be computed on squared matrices
568         final var rows = inputMatrix.getRows();
569         final var columns = inputMatrix.getColumns();
570 
571         if (rows != columns) {
572             throw new WrongSizeException();
573         }
574 
575         double d = pivSign;
576         for (int j = 0; j < columns; j++) {
577             d *= lu.getElementAt(j, j);
578         }
579 
580         return d;
581     }
582 
583 
584     /**
585      * Solves a linear system of equations of the following form:
586      * A * X = B.
587      * Where A is the input matrix provided for LU decomposition, X is the
588      * solution to the system of equations, and B is the parameters
589      * vector/matrix.
590      * Note: This method can be reused for different b vectors/matrices without
591      * having to recompute LU decomposition on the same input matrix.
592      * Note: Provided b matrix must have the same number of rows as provided
593      * input matrix A, otherwise a WrongSizeException will be raised.
594      * Note: Provided input matrix A must be square, otherwise a
595      * WrongSizeException will be raised as well.
596      * Note: If provided input matrix A is singular, a SingularMatrixException
597      * will be thrown.
598      * Note: In order to execute this method, an LU decomposition must be
599      * available, otherwise a NotAvailableException will be raised. In order
600      * to avoid this exception call decompose() method first.
601      * Note: DEFAULT_ROUND_ERROR is used as rounding error
602      * Note: Solution of linear system of equations is stored in provided result
603      * matrix
604      *
605      * @param b      Parameters matrix that determine a linear system of equations.
606      *               Provided matrix must have the same number of rows as provided input
607      *               matrix for LU decomposition. Besides, each column on parameters matrix
608      *               will represent a new system of equations, whose solution will be returned
609      *               on appropriate column as an output of this method.
610      * @param result Matrix containing solution of linear system of equations on
611      *               each column for each column of provided parameters matrix b.
612      * @throws NotAvailableException    Exception thrown if attempting to call this
613      *                                  method before computing LU decomposition. To avoid this exception call
614      *                                  decompose() method first.
615      * @throws WrongSizeException       Exception thrown if attempting to call this
616      *                                  method using a non-square input matrix; or if provided parameters matrix
617      *                                  (b) does not have the same number of rows as input matrix being LU
618      *                                  decomposed.
619      * @throws SingularMatrixException  Exception thrown if provided input matrix
620      *                                  to be LU decomposed is singular. In this case linear system of equations
621      *                                  cannot be solved.
622      * @throws IllegalArgumentException Exception thrown if provided rounding
623      *                                  error is lower than minimum allowed value (MIN_ROUND_ERROR).
624      * @see #decompose()
625      */
626     public void solve(final Matrix b, final Matrix result) throws NotAvailableException, WrongSizeException,
627             SingularMatrixException {
628         solve(b, DEFAULT_ROUND_ERROR, result);
629     }
630 
631     /**
632      * Solves a linear system of equations of the following form:
633      * A * X = B.
634      * Where A is the input matrix provided for LU decomposition, X is the
635      * solution to the system of equations, and B is the parameters
636      * vector/matrix.
637      * Note: This method can be reused for different b vectors/matrices without
638      * having to recompute LU decomposition on the same input matrix.
639      * Note: Provided b matrix must have the same number of rows as provided
640      * input matrix A, otherwise a WrongSizeException will be raised.
641      * Note: Provided input matrix A must be square, otherwise a
642      * WrongSizeException will be raised as well.
643      * Note: If provided input matrix A is singular, a SingularMatrixException
644      * will be thrown.
645      * Note: In order to execute this method, an LU decomposition must be
646      * available, otherwise a NotAvailableException will be raised. In order
647      * to avoid this exception call decompose() method first.
648      * Note: Solution of linear system of equations is stored in provided result
649      * matrix
650      *
651      * @param b             Parameters matrix that determine a linear system of equations.
652      *                      Provided matrix must have the same number of rows as provided input
653      *                      matrix for LU decomposition. Besides, each column on parameters matrix
654      *                      will represent a new system of equations, whose solution will be returned
655      *                      on appropriate column as an output of this method.
656      * @param roundingError Determines the amount of margin given to determine
657      *                      whether a matrix is singular or not due to rounding errors. If not
658      *                      provided, by default rounding error is set to zero, but this value can
659      *                      be relaxed if needed.
660      * @param result        Matrix containing solution of linear system of equations on
661      *                      each column for each column of provided parameters matrix b.
662      * @throws NotAvailableException    Exception thrown if attempting to call this
663      *                                  method before computing LU decomposition. To avoid this exception call
664      *                                  decompose() method first.
665      * @throws WrongSizeException       Exception thrown if attempting to call this
666      *                                  method using a non-square input matrix; or if provided parameters matrix
667      *                                  (b) does not have the same number of rows as input matrix being LU
668      *                                  decomposed.
669      * @throws SingularMatrixException  Exception thrown if provided input matrix
670      *                                  to be LU decomposed is singular. In this case linear system of equations
671      *                                  cannot be solved.
672      * @throws IllegalArgumentException Exception thrown if provided rounding
673      *                                  error is lower than minimum allowed value (MIN_ROUND_ERROR).
674      * @see #decompose()
675      */
676     public void solve(final Matrix b, final double roundingError, final Matrix result)
677             throws NotAvailableException, WrongSizeException, SingularMatrixException {
678 
679         if (!isDecompositionAvailable()) {
680             throw new NotAvailableException();
681         }
682 
683         if (b.getRows() != inputMatrix.getRows()) {
684             throw new WrongSizeException();
685         }
686 
687         if (roundingError < MIN_ROUND_ERROR) {
688             throw new IllegalArgumentException();
689         }
690 
691         // Copy right hand side with pivoting
692         final var rows = lu.getRows();
693         final var columns = lu.getColumns();
694         final var colsB = b.getColumns();
695 
696         if (rows != columns) {
697             throw new WrongSizeException();
698         }
699         if (isSingular(roundingError)) {
700             throw new SingularMatrixException();
701         }
702 
703         // resize result matrix if needed
704         if (result.getRows() != columns || result.getColumns() != colsB) {
705             result.resize(columns, colsB);
706         }
707         result.initialize(0.0);
708 
709         for (var i = 0; i < columns; i++) {
710             for (var j = 0; j < colsB; j++) {
711                 result.setElementAt(i, j, b.getElementAt(piv[i], j));
712             }
713         }
714 
715         // Solve L * Y = b(piv, :)
716         for (var k = 0; k < columns; k++) {
717             for (var i = k + 1; i < columns; i++) {
718                 for (var j = 0; j < colsB; j++) {
719                     result.setElementAt(i, j, result.getElementAt(i, j)
720                             - result.getElementAt(k, j) * lu.getElementAt(i, k));
721                 }
722             }
723         }
724 
725         // Solve U * X = Y
726         for (var k = columns - 1; k >= 0; k--) {
727             for (var j = 0; j < colsB; j++) {
728                 result.setElementAt(k, j, result.getElementAt(k, j) / lu.getElementAt(k, k));
729             }
730             for (var i = 0; i < k; i++) {
731                 for (var j = 0; j < colsB; j++) {
732                     result.setElementAt(i, j, result.getElementAt(i, j)
733                             - result.getElementAt(k, j) * lu.getElementAt(i, k));
734                 }
735             }
736         }
737     }
738 
739     /**
740      * Solves a linear system of equations of the following form:
741      * A * X = B.
742      * Where A is the input matrix provided for LU decomposition, X is the
743      * solution to the system of equations, and B is the parameters
744      * vector/matrix.
745      * Note: This method can be reused for different b vectors/matrices without
746      * having to recompute LU decomposition on the same input matrix.
747      * Note: Provided b matrix must have the same number of rows as provided
748      * input matrix A, otherwise a WrongSizeException will be raised.
749      * Note: Provided input matrix A must be square, otherwise a
750      * WrongSizeException will be raised as well.
751      * Note: If provided input matrix A is singular, a SingularMatrixException
752      * will be thrown.
753      * Note: In order to execute this method, an LU decomposition must be
754      * available, otherwise a NotAvailableException will be raised. In order
755      * to avoid this exception call decompose() method first.
756      * Note: DEFAULT_ROUND_ERROR is used as rounding error
757      *
758      * @param b Parameters matrix that determine a linear system of equations.
759      *          Provided matrix must have the same number of rows as provided input
760      *          matrix for LU decomposition. Besides, each column on parameters matrix
761      *          will represent a new system of equations, whose solution will be returned
762      *          on appropriate column as an output of this method.
763      * @return Matrix containing solution of linear system of equations on each
764      * column for each column of provided parameters matrix b.
765      * @throws NotAvailableException    Exception thrown if attempting to call this
766      *                                  method before computing LU decomposition. To avoid this exception call
767      *                                  decompose() method first.
768      * @throws WrongSizeException       Exception thrown if attempting to call this
769      *                                  method using a non-square input matrix; or if provided parameters matrix
770      *                                  (b) does not have the same number of rows as input matrix being LU
771      *                                  decomposed.
772      * @throws SingularMatrixException  Exception thrown if provided input matrix
773      *                                  to be LU decomposed is singular. In this case linear system of equations
774      *                                  cannot be solved.
775      * @throws IllegalArgumentException Exception thrown if provided rounding
776      *                                  error is lower than minimum allowed value (MIN_ROUND_ERROR).
777      * @see #decompose()
778      */
779     public Matrix solve(final Matrix b) throws NotAvailableException, WrongSizeException, SingularMatrixException {
780         return solve(b, DEFAULT_ROUND_ERROR);
781     }
782 
783     /**
784      * Solves a linear system of equations of the following form:
785      * A * X = B.
786      * Where A is the input matrix provided for LU decomposition, X is the
787      * solution to the system of equations, and B is the parameters
788      * vector/matrix.
789      * Note: This method can be reused for different b vectors/matrices without
790      * having to recompute LU decomposition on the same input matrix.
791      * Note: Provided b matrix must have the same number of rows as provided
792      * input matrix A, otherwise a WrongSizeException will be raised.
793      * Note: Provided input matrix A must be square, otherwise a
794      * WrongSizeException will be raised as well.
795      * Note: If provided input matrix A is singular, a SingularMatrixException
796      * will be thrown.
797      * Note: In order to execute this method, an LU decomposition must be
798      * available, otherwise a NotAvailableException will be raised. In order
799      * to avoid this exception call decompose() method first.
800      *
801      * @param b             Parameters matrix that determine a linear system of equations.
802      *                      Provided matrix must have the same number of rows as provided input
803      *                      matrix for LU decomposition. Besides, each column on parameters matrix
804      *                      will represent a new system of equations, whose solution will be returned
805      *                      on appropriate column as an output of this method.
806      * @param roundingError Determines the amount of margin given to determine
807      *                      whether a matrix is singular or not due to rounding errors. If not
808      *                      provided, by default rounding error is set to zero, but this value can
809      *                      be relaxed if needed.
810      * @return Matrix containing solution of linear system of equations on each
811      * column for each column of provided parameters matrix b.
812      * @throws NotAvailableException    Exception thrown if attempting to call this
813      *                                  method before computing LU decomposition. To avoid this exception call
814      *                                  decompose() method first.
815      * @throws WrongSizeException       Exception thrown if attempting to call this
816      *                                  method using a non-square input matrix; or if provided parameters matrix
817      *                                  (b) does not have the same number of rows as input matrix being LU
818      *                                  decomposed.
819      * @throws SingularMatrixException  Exception thrown if provided input matrix
820      *                                  to be LU decomposed is singular. In this case linear system of equations
821      *                                  cannot be solved.
822      * @throws IllegalArgumentException Exception thrown if provided rounding
823      *                                  error is lower than minimum allowed value (MIN_ROUND_ERROR).
824      * @see #decompose()
825      */
826     public Matrix solve(final Matrix b, final double roundingError) throws NotAvailableException, WrongSizeException,
827             SingularMatrixException {
828 
829         if (!isDecompositionAvailable()) {
830             throw new NotAvailableException();
831         }
832 
833         final var columns = lu.getColumns();
834         final var colsB = b.getColumns();
835         final var out = new Matrix(columns, colsB);
836         solve(b, roundingError, out);
837         return out;
838     }
839 }