mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-12-18 01:00:15 +00:00
5ce63b5966
* updating DIRECTORY.md * Fix mypy errors in lu_decomposition.py * Replace for-loops with comprehensions * Add explanation of LU decomposition and extra doctests Add an explanation of LU decomposition with conditions for when an LU decomposition exists Add extra doctests to handle each of the possible conditions for when a decomposition exists/doesn't exist * updating DIRECTORY.md * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
107 lines
3.7 KiB
Python
107 lines
3.7 KiB
Python
"""
|
||
Lower–upper (LU) decomposition factors a matrix as a product of a lower
|
||
triangular matrix and an upper triangular matrix. A square matrix has an LU
|
||
decomposition under the following conditions:
|
||
- If the matrix is invertible, then it has an LU decomposition if and only
|
||
if all of its leading principal minors are non-zero (see
|
||
https://en.wikipedia.org/wiki/Minor_(linear_algebra) for an explanation of
|
||
leading principal minors of a matrix).
|
||
- If the matrix is singular (i.e., not invertible) and it has a rank of k
|
||
(i.e., it has k linearly independent columns), then it has an LU
|
||
decomposition if its first k leading principal minors are non-zero.
|
||
|
||
This algorithm will simply attempt to perform LU decomposition on any square
|
||
matrix and raise an error if no such decomposition exists.
|
||
|
||
Reference: https://en.wikipedia.org/wiki/LU_decomposition
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import numpy as np
|
||
|
||
|
||
def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||
"""
|
||
Perform LU decomposition on a given matrix and raises an error if the matrix
|
||
isn't square or if no such decomposition exists
|
||
>>> matrix = np.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]])
|
||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||
>>> lower_mat
|
||
array([[1. , 0. , 0. ],
|
||
[0. , 1. , 0. ],
|
||
[2.5, 8. , 1. ]])
|
||
>>> upper_mat
|
||
array([[ 2. , -2. , 1. ],
|
||
[ 0. , 1. , 2. ],
|
||
[ 0. , 0. , -17.5]])
|
||
|
||
>>> matrix = np.array([[4, 3], [6, 3]])
|
||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||
>>> lower_mat
|
||
array([[1. , 0. ],
|
||
[1.5, 1. ]])
|
||
>>> upper_mat
|
||
array([[ 4. , 3. ],
|
||
[ 0. , -1.5]])
|
||
|
||
# Matrix is not square
|
||
>>> matrix = np.array([[2, -2, 1], [0, 1, 2]])
|
||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||
Traceback (most recent call last):
|
||
...
|
||
ValueError: 'table' has to be of square shaped array but got a 2x3 array:
|
||
[[ 2 -2 1]
|
||
[ 0 1 2]]
|
||
|
||
# Matrix is invertible, but its first leading principal minor is 0
|
||
>>> matrix = np.array([[0, 1], [1, 0]])
|
||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||
Traceback (most recent call last):
|
||
...
|
||
ArithmeticError: No LU decomposition exists
|
||
|
||
# Matrix is singular, but its first leading principal minor is 1
|
||
>>> matrix = np.array([[1, 0], [1, 0]])
|
||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||
>>> lower_mat
|
||
array([[1., 0.],
|
||
[1., 1.]])
|
||
>>> upper_mat
|
||
array([[1., 0.],
|
||
[0., 0.]])
|
||
|
||
# Matrix is singular, but its first leading principal minor is 0
|
||
>>> matrix = np.array([[0, 1], [0, 1]])
|
||
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
|
||
Traceback (most recent call last):
|
||
...
|
||
ArithmeticError: No LU decomposition exists
|
||
"""
|
||
# Ensure that table is a square array
|
||
rows, columns = np.shape(table)
|
||
if rows != columns:
|
||
raise ValueError(
|
||
f"'table' has to be of square shaped array but got a "
|
||
f"{rows}x{columns} array:\n{table}"
|
||
)
|
||
|
||
lower = np.zeros((rows, columns))
|
||
upper = np.zeros((rows, columns))
|
||
for i in range(columns):
|
||
for j in range(i):
|
||
total = sum(lower[i][k] * upper[k][j] for k in range(j))
|
||
if upper[j][j] == 0:
|
||
raise ArithmeticError("No LU decomposition exists")
|
||
lower[i][j] = (table[i][j] - total) / upper[j][j]
|
||
lower[i][i] = 1
|
||
for j in range(i, columns):
|
||
total = sum(lower[i][k] * upper[k][j] for k in range(j))
|
||
upper[i][j] = table[i][j] - total
|
||
return lower, upper
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import doctest
|
||
|
||
doctest.testmod()
|