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 }