Skip to content

Refactoring and optimization of the lu_decomposition algorithm - #9231

Merged
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition
Oct 16, 2023
Merged

Refactoring and optimization of the lu_decomposition algorithm#9231
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition

Conversation

@quant12345

Copy link
Copy Markdown
Contributor

Describe your change:

Replacing the generator on numpy vector operations from lu_decomposition.

before: total = sum(lower[i][k] * upper[k][j] for k in range(j))

after: total = np.sum(lower[i, :i] * upper[:i, j])

In 'total', the necessary data is extracted through slices and the sum of the products is obtained.
Moreover, as the array size increases, the ratio of the calculation time of the original algorithm
growing towards the new. As a result, with an array size of n x n 1000, the calculation time for the
original algorithm is almost 7 minutes, and the new one is 12 seconds(time in tests is seconds).

The tests generate n x n arrays. To prevent it from triggering:

raise ArithmeticError("No LU decomposition exists")

diagonal elements are enlarged. Two arrays are extracted from the resulting tuple
rounded to the fifth digit and compared for identity.

code performance tests
"""
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 datetime
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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
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
def lower_upper_decomposition_new(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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
lower = np.zeros((rows, columns))
upper = np.zeros((rows, columns))
# in 'total', the necessary data is extracted through slices
# and the sum of the products is obtained.
for i in range(columns):
for j in range(i):
total = np.sum(lower[i, :i] * upper[:i, 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 = np.sum(lower[i, :i] * upper[:i, j])
upper[i][j] = table[i][j] - total
return lower, upper
if __name__ == "__main__":
import doctest
doctest.testmod()
arr = [18, 30, 50, 100, 200, 300, 500, 700, 1000]
for i in range(len(arr)):
n = arr[i]
matrix = np.random.randint(low=-5, high=5, size=(n, n))
matrix[np.diag_indices_from(matrix)] = 10 * n
now = datetime.datetime.now()
original = lower_upper_decomposition(matrix)
time_original = datetime.datetime.now() - now
now = datetime.datetime.now()
new = lower_upper_decomposition_new(matrix)
time_new = datetime.datetime.now() - now
word = ('array size n x n {0} time_old {1} time_new {2}'
' difference in time {3} {4} array 1 {5} array 2 {6}'
.format(n, time_original.total_seconds(),
time_new.total_seconds(), round(time_original / time_new, 2),
'comparison result',
np.all(np.round(original[0], 5) == np.round(new[0], 5)),
np.all(np.round(original[1], 5) == np.round(new[1], 5))))
print(word)

Output:

array size n x n 18 time_old 0.006474 time_new 0.006151 difference in time 1.05 comparison result array 1 True array 2 True
array size n x n 30 time_old 0.012381 time_new 0.008019 difference in time 1.54 comparison result array 1 True array 2 True
array size n x n 50 time_old 0.055401 time_new 0.022404 difference in time 2.47 comparison result array 1 True array 2 True
array size n x n 100 time_old 0.418046 time_new 0.085158 difference in time 4.91 comparison result array 1 True array 2 True
array size n x n 200 time_old 3.175042 time_new 0.352767 difference in time 9.0 comparison result array 1 True array 2 True
array size n x n 300 time_old 10.780385 time_new 0.834303 difference in time 12.92 comparison result array 1 True array 2 True
array size n x n 500 time_old 50.109723 time_new 2.55773 difference in time 19.59 comparison result array 1 True array 2 True
array size n x n 700 time_old 138.859871 time_new 6.515611 difference in time 21.31 comparison result array 1 True array 2 True
array size n x n 1000 time_old 408.079446 time_new 11.721369 difference in time 34.81 comparison result array 1 True array 2 True 

decomposition

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

@algorithms-keeperalgorithms-keeperBot added enhancement This PR modified some existing files awaiting reviews This PR is ready to be reviewed labels Oct 1, 2023
@tianyizheng02
tianyizheng02 merged commit 3c14e6a into TheAlgorithms:masterOct 16, 2023
@algorithms-keeperalgorithms-keeperBot removed the awaiting reviews This PR is ready to be reviewed label Oct 16, 2023
@quant12345
quant12345 deleted the decomposition branch August 6, 2024 09:32
@isidroasisidroas mentioned this pull request Jan 25, 2025
14 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementThis PR modified some existing files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@quant12345@tianyizheng02
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Refactoring and optimization of the lu_decomposition algorithm by quant12345 · Pull Request #9231 · TheAlgorithms/Python · GitHub
Skip to content

Refactoring and optimization of the lu_decomposition algorithm - #9231

Merged
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition
Oct 16, 2023
Merged

Refactoring and optimization of the lu_decomposition algorithm#9231
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition

Conversation

@quant12345

Copy link
Copy Markdown
Contributor

Describe your change:

Replacing the generator on numpy vector operations from lu_decomposition.

before: total = sum(lower[i][k] * upper[k][j] for k in range(j))

after: total = np.sum(lower[i, :i] * upper[:i, j])

In 'total', the necessary data is extracted through slices and the sum of the products is obtained.
Moreover, as the array size increases, the ratio of the calculation time of the original algorithm
growing towards the new. As a result, with an array size of n x n 1000, the calculation time for the
original algorithm is almost 7 minutes, and the new one is 12 seconds(time in tests is seconds).

The tests generate n x n arrays. To prevent it from triggering:

raise ArithmeticError("No LU decomposition exists")

diagonal elements are enlarged. Two arrays are extracted from the resulting tuple
rounded to the fifth digit and compared for identity.

code performance tests
"""
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 datetime
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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
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
def lower_upper_decomposition_new(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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
lower = np.zeros((rows, columns))
upper = np.zeros((rows, columns))
# in 'total', the necessary data is extracted through slices
# and the sum of the products is obtained.
for i in range(columns):
for j in range(i):
total = np.sum(lower[i, :i] * upper[:i, 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 = np.sum(lower[i, :i] * upper[:i, j])
upper[i][j] = table[i][j] - total
return lower, upper
if __name__ == "__main__":
import doctest
doctest.testmod()
arr = [18, 30, 50, 100, 200, 300, 500, 700, 1000]
for i in range(len(arr)):
n = arr[i]
matrix = np.random.randint(low=-5, high=5, size=(n, n))
matrix[np.diag_indices_from(matrix)] = 10 * n
now = datetime.datetime.now()
original = lower_upper_decomposition(matrix)
time_original = datetime.datetime.now() - now
now = datetime.datetime.now()
new = lower_upper_decomposition_new(matrix)
time_new = datetime.datetime.now() - now
word = ('array size n x n {0} time_old {1} time_new {2}'
' difference in time {3} {4} array 1 {5} array 2 {6}'
.format(n, time_original.total_seconds(),
time_new.total_seconds(), round(time_original / time_new, 2),
'comparison result',
np.all(np.round(original[0], 5) == np.round(new[0], 5)),
np.all(np.round(original[1], 5) == np.round(new[1], 5))))
print(word)

Output:

array size n x n 18 time_old 0.006474 time_new 0.006151 difference in time 1.05 comparison result array 1 True array 2 True
array size n x n 30 time_old 0.012381 time_new 0.008019 difference in time 1.54 comparison result array 1 True array 2 True
array size n x n 50 time_old 0.055401 time_new 0.022404 difference in time 2.47 comparison result array 1 True array 2 True
array size n x n 100 time_old 0.418046 time_new 0.085158 difference in time 4.91 comparison result array 1 True array 2 True
array size n x n 200 time_old 3.175042 time_new 0.352767 difference in time 9.0 comparison result array 1 True array 2 True
array size n x n 300 time_old 10.780385 time_new 0.834303 difference in time 12.92 comparison result array 1 True array 2 True
array size n x n 500 time_old 50.109723 time_new 2.55773 difference in time 19.59 comparison result array 1 True array 2 True
array size n x n 700 time_old 138.859871 time_new 6.515611 difference in time 21.31 comparison result array 1 True array 2 True
array size n x n 1000 time_old 408.079446 time_new 11.721369 difference in time 34.81 comparison result array 1 True array 2 True 

decomposition

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

@algorithms-keeperalgorithms-keeperBot added enhancement This PR modified some existing files awaiting reviews This PR is ready to be reviewed labels Oct 1, 2023
@tianyizheng02
tianyizheng02 merged commit 3c14e6a into TheAlgorithms:masterOct 16, 2023
@algorithms-keeperalgorithms-keeperBot removed the awaiting reviews This PR is ready to be reviewed label Oct 16, 2023
@quant12345
quant12345 deleted the decomposition branch August 6, 2024 09:32
@isidroasisidroas mentioned this pull request Jan 25, 2025
14 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementThis PR modified some existing files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@quant12345@tianyizheng02
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Refactoring and optimization of the lu_decomposition algorithm by quant12345 · Pull Request #9231 · TheAlgorithms/Python · GitHub
Skip to content

Refactoring and optimization of the lu_decomposition algorithm - #9231

Merged
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition
Oct 16, 2023
Merged

Refactoring and optimization of the lu_decomposition algorithm#9231
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition

Conversation

@quant12345

Copy link
Copy Markdown
Contributor

Describe your change:

Replacing the generator on numpy vector operations from lu_decomposition.

before: total = sum(lower[i][k] * upper[k][j] for k in range(j))

after: total = np.sum(lower[i, :i] * upper[:i, j])

In 'total', the necessary data is extracted through slices and the sum of the products is obtained.
Moreover, as the array size increases, the ratio of the calculation time of the original algorithm
growing towards the new. As a result, with an array size of n x n 1000, the calculation time for the
original algorithm is almost 7 minutes, and the new one is 12 seconds(time in tests is seconds).

The tests generate n x n arrays. To prevent it from triggering:

raise ArithmeticError("No LU decomposition exists")

diagonal elements are enlarged. Two arrays are extracted from the resulting tuple
rounded to the fifth digit and compared for identity.

code performance tests
"""
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 datetime
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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
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
def lower_upper_decomposition_new(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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
lower = np.zeros((rows, columns))
upper = np.zeros((rows, columns))
# in 'total', the necessary data is extracted through slices
# and the sum of the products is obtained.
for i in range(columns):
for j in range(i):
total = np.sum(lower[i, :i] * upper[:i, 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 = np.sum(lower[i, :i] * upper[:i, j])
upper[i][j] = table[i][j] - total
return lower, upper
if __name__ == "__main__":
import doctest
doctest.testmod()
arr = [18, 30, 50, 100, 200, 300, 500, 700, 1000]
for i in range(len(arr)):
n = arr[i]
matrix = np.random.randint(low=-5, high=5, size=(n, n))
matrix[np.diag_indices_from(matrix)] = 10 * n
now = datetime.datetime.now()
original = lower_upper_decomposition(matrix)
time_original = datetime.datetime.now() - now
now = datetime.datetime.now()
new = lower_upper_decomposition_new(matrix)
time_new = datetime.datetime.now() - now
word = ('array size n x n {0} time_old {1} time_new {2}'
' difference in time {3} {4} array 1 {5} array 2 {6}'
.format(n, time_original.total_seconds(),
time_new.total_seconds(), round(time_original / time_new, 2),
'comparison result',
np.all(np.round(original[0], 5) == np.round(new[0], 5)),
np.all(np.round(original[1], 5) == np.round(new[1], 5))))
print(word)

Output:

array size n x n 18 time_old 0.006474 time_new 0.006151 difference in time 1.05 comparison result array 1 True array 2 True
array size n x n 30 time_old 0.012381 time_new 0.008019 difference in time 1.54 comparison result array 1 True array 2 True
array size n x n 50 time_old 0.055401 time_new 0.022404 difference in time 2.47 comparison result array 1 True array 2 True
array size n x n 100 time_old 0.418046 time_new 0.085158 difference in time 4.91 comparison result array 1 True array 2 True
array size n x n 200 time_old 3.175042 time_new 0.352767 difference in time 9.0 comparison result array 1 True array 2 True
array size n x n 300 time_old 10.780385 time_new 0.834303 difference in time 12.92 comparison result array 1 True array 2 True
array size n x n 500 time_old 50.109723 time_new 2.55773 difference in time 19.59 comparison result array 1 True array 2 True
array size n x n 700 time_old 138.859871 time_new 6.515611 difference in time 21.31 comparison result array 1 True array 2 True
array size n x n 1000 time_old 408.079446 time_new 11.721369 difference in time 34.81 comparison result array 1 True array 2 True 

decomposition

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

@algorithms-keeperalgorithms-keeperBot added enhancement This PR modified some existing files awaiting reviews This PR is ready to be reviewed labels Oct 1, 2023
@tianyizheng02
tianyizheng02 merged commit 3c14e6a into TheAlgorithms:masterOct 16, 2023
@algorithms-keeperalgorithms-keeperBot removed the awaiting reviews This PR is ready to be reviewed label Oct 16, 2023
@quant12345
quant12345 deleted the decomposition branch August 6, 2024 09:32
@isidroasisidroas mentioned this pull request Jan 25, 2025
14 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementThis PR modified some existing files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@quant12345@tianyizheng02
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Refactoring and optimization of the lu_decomposition algorithm by quant12345 · Pull Request #9231 · TheAlgorithms/Python · GitHub
Skip to content

Refactoring and optimization of the lu_decomposition algorithm - #9231

Merged
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition
Oct 16, 2023
Merged

Refactoring and optimization of the lu_decomposition algorithm#9231
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition

Conversation

@quant12345

Copy link
Copy Markdown
Contributor

Describe your change:

Replacing the generator on numpy vector operations from lu_decomposition.

before: total = sum(lower[i][k] * upper[k][j] for k in range(j))

after: total = np.sum(lower[i, :i] * upper[:i, j])

In 'total', the necessary data is extracted through slices and the sum of the products is obtained.
Moreover, as the array size increases, the ratio of the calculation time of the original algorithm
growing towards the new. As a result, with an array size of n x n 1000, the calculation time for the
original algorithm is almost 7 minutes, and the new one is 12 seconds(time in tests is seconds).

The tests generate n x n arrays. To prevent it from triggering:

raise ArithmeticError("No LU decomposition exists")

diagonal elements are enlarged. Two arrays are extracted from the resulting tuple
rounded to the fifth digit and compared for identity.

code performance tests
"""
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 datetime
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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
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
def lower_upper_decomposition_new(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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
lower = np.zeros((rows, columns))
upper = np.zeros((rows, columns))
# in 'total', the necessary data is extracted through slices
# and the sum of the products is obtained.
for i in range(columns):
for j in range(i):
total = np.sum(lower[i, :i] * upper[:i, 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 = np.sum(lower[i, :i] * upper[:i, j])
upper[i][j] = table[i][j] - total
return lower, upper
if __name__ == "__main__":
import doctest
doctest.testmod()
arr = [18, 30, 50, 100, 200, 300, 500, 700, 1000]
for i in range(len(arr)):
n = arr[i]
matrix = np.random.randint(low=-5, high=5, size=(n, n))
matrix[np.diag_indices_from(matrix)] = 10 * n
now = datetime.datetime.now()
original = lower_upper_decomposition(matrix)
time_original = datetime.datetime.now() - now
now = datetime.datetime.now()
new = lower_upper_decomposition_new(matrix)
time_new = datetime.datetime.now() - now
word = ('array size n x n {0} time_old {1} time_new {2}'
' difference in time {3} {4} array 1 {5} array 2 {6}'
.format(n, time_original.total_seconds(),
time_new.total_seconds(), round(time_original / time_new, 2),
'comparison result',
np.all(np.round(original[0], 5) == np.round(new[0], 5)),
np.all(np.round(original[1], 5) == np.round(new[1], 5))))
print(word)

Output:

array size n x n 18 time_old 0.006474 time_new 0.006151 difference in time 1.05 comparison result array 1 True array 2 True
array size n x n 30 time_old 0.012381 time_new 0.008019 difference in time 1.54 comparison result array 1 True array 2 True
array size n x n 50 time_old 0.055401 time_new 0.022404 difference in time 2.47 comparison result array 1 True array 2 True
array size n x n 100 time_old 0.418046 time_new 0.085158 difference in time 4.91 comparison result array 1 True array 2 True
array size n x n 200 time_old 3.175042 time_new 0.352767 difference in time 9.0 comparison result array 1 True array 2 True
array size n x n 300 time_old 10.780385 time_new 0.834303 difference in time 12.92 comparison result array 1 True array 2 True
array size n x n 500 time_old 50.109723 time_new 2.55773 difference in time 19.59 comparison result array 1 True array 2 True
array size n x n 700 time_old 138.859871 time_new 6.515611 difference in time 21.31 comparison result array 1 True array 2 True
array size n x n 1000 time_old 408.079446 time_new 11.721369 difference in time 34.81 comparison result array 1 True array 2 True 

decomposition

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

@algorithms-keeperalgorithms-keeperBot added enhancement This PR modified some existing files awaiting reviews This PR is ready to be reviewed labels Oct 1, 2023
@tianyizheng02
tianyizheng02 merged commit 3c14e6a into TheAlgorithms:masterOct 16, 2023
@algorithms-keeperalgorithms-keeperBot removed the awaiting reviews This PR is ready to be reviewed label Oct 16, 2023
@quant12345
quant12345 deleted the decomposition branch August 6, 2024 09:32
@isidroasisidroas mentioned this pull request Jan 25, 2025
14 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementThis PR modified some existing files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@quant12345@tianyizheng02
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Refactoring and optimization of the lu_decomposition algorithm by quant12345 · Pull Request #9231 · TheAlgorithms/Python · GitHub
Skip to content

Refactoring and optimization of the lu_decomposition algorithm - #9231

Merged
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition
Oct 16, 2023
Merged

Refactoring and optimization of the lu_decomposition algorithm#9231
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition

Conversation

@quant12345

Copy link
Copy Markdown
Contributor

Describe your change:

Replacing the generator on numpy vector operations from lu_decomposition.

before: total = sum(lower[i][k] * upper[k][j] for k in range(j))

after: total = np.sum(lower[i, :i] * upper[:i, j])

In 'total', the necessary data is extracted through slices and the sum of the products is obtained.
Moreover, as the array size increases, the ratio of the calculation time of the original algorithm
growing towards the new. As a result, with an array size of n x n 1000, the calculation time for the
original algorithm is almost 7 minutes, and the new one is 12 seconds(time in tests is seconds).

The tests generate n x n arrays. To prevent it from triggering:

raise ArithmeticError("No LU decomposition exists")

diagonal elements are enlarged. Two arrays are extracted from the resulting tuple
rounded to the fifth digit and compared for identity.

code performance tests
"""
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 datetime
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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
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
def lower_upper_decomposition_new(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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
lower = np.zeros((rows, columns))
upper = np.zeros((rows, columns))
# in 'total', the necessary data is extracted through slices
# and the sum of the products is obtained.
for i in range(columns):
for j in range(i):
total = np.sum(lower[i, :i] * upper[:i, 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 = np.sum(lower[i, :i] * upper[:i, j])
upper[i][j] = table[i][j] - total
return lower, upper
if __name__ == "__main__":
import doctest
doctest.testmod()
arr = [18, 30, 50, 100, 200, 300, 500, 700, 1000]
for i in range(len(arr)):
n = arr[i]
matrix = np.random.randint(low=-5, high=5, size=(n, n))
matrix[np.diag_indices_from(matrix)] = 10 * n
now = datetime.datetime.now()
original = lower_upper_decomposition(matrix)
time_original = datetime.datetime.now() - now
now = datetime.datetime.now()
new = lower_upper_decomposition_new(matrix)
time_new = datetime.datetime.now() - now
word = ('array size n x n {0} time_old {1} time_new {2}'
' difference in time {3} {4} array 1 {5} array 2 {6}'
.format(n, time_original.total_seconds(),
time_new.total_seconds(), round(time_original / time_new, 2),
'comparison result',
np.all(np.round(original[0], 5) == np.round(new[0], 5)),
np.all(np.round(original[1], 5) == np.round(new[1], 5))))
print(word)

Output:

array size n x n 18 time_old 0.006474 time_new 0.006151 difference in time 1.05 comparison result array 1 True array 2 True
array size n x n 30 time_old 0.012381 time_new 0.008019 difference in time 1.54 comparison result array 1 True array 2 True
array size n x n 50 time_old 0.055401 time_new 0.022404 difference in time 2.47 comparison result array 1 True array 2 True
array size n x n 100 time_old 0.418046 time_new 0.085158 difference in time 4.91 comparison result array 1 True array 2 True
array size n x n 200 time_old 3.175042 time_new 0.352767 difference in time 9.0 comparison result array 1 True array 2 True
array size n x n 300 time_old 10.780385 time_new 0.834303 difference in time 12.92 comparison result array 1 True array 2 True
array size n x n 500 time_old 50.109723 time_new 2.55773 difference in time 19.59 comparison result array 1 True array 2 True
array size n x n 700 time_old 138.859871 time_new 6.515611 difference in time 21.31 comparison result array 1 True array 2 True
array size n x n 1000 time_old 408.079446 time_new 11.721369 difference in time 34.81 comparison result array 1 True array 2 True 

decomposition

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

@algorithms-keeperalgorithms-keeperBot added enhancement This PR modified some existing files awaiting reviews This PR is ready to be reviewed labels Oct 1, 2023
@tianyizheng02
tianyizheng02 merged commit 3c14e6a into TheAlgorithms:masterOct 16, 2023
@algorithms-keeperalgorithms-keeperBot removed the awaiting reviews This PR is ready to be reviewed label Oct 16, 2023
@quant12345
quant12345 deleted the decomposition branch August 6, 2024 09:32
@isidroasisidroas mentioned this pull request Jan 25, 2025
14 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementThis PR modified some existing files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@quant12345@tianyizheng02
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Refactoring and optimization of the lu_decomposition algorithm by quant12345 · Pull Request #9231 · TheAlgorithms/Python · GitHub
Skip to content

Refactoring and optimization of the lu_decomposition algorithm - #9231

Merged
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition
Oct 16, 2023
Merged

Refactoring and optimization of the lu_decomposition algorithm#9231
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition

Conversation

@quant12345

Copy link
Copy Markdown
Contributor

Describe your change:

Replacing the generator on numpy vector operations from lu_decomposition.

before: total = sum(lower[i][k] * upper[k][j] for k in range(j))

after: total = np.sum(lower[i, :i] * upper[:i, j])

In 'total', the necessary data is extracted through slices and the sum of the products is obtained.
Moreover, as the array size increases, the ratio of the calculation time of the original algorithm
growing towards the new. As a result, with an array size of n x n 1000, the calculation time for the
original algorithm is almost 7 minutes, and the new one is 12 seconds(time in tests is seconds).

The tests generate n x n arrays. To prevent it from triggering:

raise ArithmeticError("No LU decomposition exists")

diagonal elements are enlarged. Two arrays are extracted from the resulting tuple
rounded to the fifth digit and compared for identity.

code performance tests
"""
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 datetime
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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
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
def lower_upper_decomposition_new(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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
lower = np.zeros((rows, columns))
upper = np.zeros((rows, columns))
# in 'total', the necessary data is extracted through slices
# and the sum of the products is obtained.
for i in range(columns):
for j in range(i):
total = np.sum(lower[i, :i] * upper[:i, 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 = np.sum(lower[i, :i] * upper[:i, j])
upper[i][j] = table[i][j] - total
return lower, upper
if __name__ == "__main__":
import doctest
doctest.testmod()
arr = [18, 30, 50, 100, 200, 300, 500, 700, 1000]
for i in range(len(arr)):
n = arr[i]
matrix = np.random.randint(low=-5, high=5, size=(n, n))
matrix[np.diag_indices_from(matrix)] = 10 * n
now = datetime.datetime.now()
original = lower_upper_decomposition(matrix)
time_original = datetime.datetime.now() - now
now = datetime.datetime.now()
new = lower_upper_decomposition_new(matrix)
time_new = datetime.datetime.now() - now
word = ('array size n x n {0} time_old {1} time_new {2}'
' difference in time {3} {4} array 1 {5} array 2 {6}'
.format(n, time_original.total_seconds(),
time_new.total_seconds(), round(time_original / time_new, 2),
'comparison result',
np.all(np.round(original[0], 5) == np.round(new[0], 5)),
np.all(np.round(original[1], 5) == np.round(new[1], 5))))
print(word)

Output:

array size n x n 18 time_old 0.006474 time_new 0.006151 difference in time 1.05 comparison result array 1 True array 2 True
array size n x n 30 time_old 0.012381 time_new 0.008019 difference in time 1.54 comparison result array 1 True array 2 True
array size n x n 50 time_old 0.055401 time_new 0.022404 difference in time 2.47 comparison result array 1 True array 2 True
array size n x n 100 time_old 0.418046 time_new 0.085158 difference in time 4.91 comparison result array 1 True array 2 True
array size n x n 200 time_old 3.175042 time_new 0.352767 difference in time 9.0 comparison result array 1 True array 2 True
array size n x n 300 time_old 10.780385 time_new 0.834303 difference in time 12.92 comparison result array 1 True array 2 True
array size n x n 500 time_old 50.109723 time_new 2.55773 difference in time 19.59 comparison result array 1 True array 2 True
array size n x n 700 time_old 138.859871 time_new 6.515611 difference in time 21.31 comparison result array 1 True array 2 True
array size n x n 1000 time_old 408.079446 time_new 11.721369 difference in time 34.81 comparison result array 1 True array 2 True 

decomposition

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

@algorithms-keeperalgorithms-keeperBot added enhancement This PR modified some existing files awaiting reviews This PR is ready to be reviewed labels Oct 1, 2023
@tianyizheng02
tianyizheng02 merged commit 3c14e6a into TheAlgorithms:masterOct 16, 2023
@algorithms-keeperalgorithms-keeperBot removed the awaiting reviews This PR is ready to be reviewed label Oct 16, 2023
@quant12345
quant12345 deleted the decomposition branch August 6, 2024 09:32
@isidroasisidroas mentioned this pull request Jan 25, 2025
14 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementThis PR modified some existing files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@quant12345@tianyizheng02
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Refactoring and optimization of the lu_decomposition algorithm by quant12345 · Pull Request #9231 · TheAlgorithms/Python · GitHub
Skip to content

Refactoring and optimization of the lu_decomposition algorithm - #9231

Merged
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition
Oct 16, 2023
Merged

Refactoring and optimization of the lu_decomposition algorithm#9231
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition

Conversation

@quant12345

Copy link
Copy Markdown
Contributor

Describe your change:

Replacing the generator on numpy vector operations from lu_decomposition.

before: total = sum(lower[i][k] * upper[k][j] for k in range(j))

after: total = np.sum(lower[i, :i] * upper[:i, j])

In 'total', the necessary data is extracted through slices and the sum of the products is obtained.
Moreover, as the array size increases, the ratio of the calculation time of the original algorithm
growing towards the new. As a result, with an array size of n x n 1000, the calculation time for the
original algorithm is almost 7 minutes, and the new one is 12 seconds(time in tests is seconds).

The tests generate n x n arrays. To prevent it from triggering:

raise ArithmeticError("No LU decomposition exists")

diagonal elements are enlarged. Two arrays are extracted from the resulting tuple
rounded to the fifth digit and compared for identity.

code performance tests
"""
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 datetime
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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
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
def lower_upper_decomposition_new(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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
lower = np.zeros((rows, columns))
upper = np.zeros((rows, columns))
# in 'total', the necessary data is extracted through slices
# and the sum of the products is obtained.
for i in range(columns):
for j in range(i):
total = np.sum(lower[i, :i] * upper[:i, 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 = np.sum(lower[i, :i] * upper[:i, j])
upper[i][j] = table[i][j] - total
return lower, upper
if __name__ == "__main__":
import doctest
doctest.testmod()
arr = [18, 30, 50, 100, 200, 300, 500, 700, 1000]
for i in range(len(arr)):
n = arr[i]
matrix = np.random.randint(low=-5, high=5, size=(n, n))
matrix[np.diag_indices_from(matrix)] = 10 * n
now = datetime.datetime.now()
original = lower_upper_decomposition(matrix)
time_original = datetime.datetime.now() - now
now = datetime.datetime.now()
new = lower_upper_decomposition_new(matrix)
time_new = datetime.datetime.now() - now
word = ('array size n x n {0} time_old {1} time_new {2}'
' difference in time {3} {4} array 1 {5} array 2 {6}'
.format(n, time_original.total_seconds(),
time_new.total_seconds(), round(time_original / time_new, 2),
'comparison result',
np.all(np.round(original[0], 5) == np.round(new[0], 5)),
np.all(np.round(original[1], 5) == np.round(new[1], 5))))
print(word)

Output:

array size n x n 18 time_old 0.006474 time_new 0.006151 difference in time 1.05 comparison result array 1 True array 2 True
array size n x n 30 time_old 0.012381 time_new 0.008019 difference in time 1.54 comparison result array 1 True array 2 True
array size n x n 50 time_old 0.055401 time_new 0.022404 difference in time 2.47 comparison result array 1 True array 2 True
array size n x n 100 time_old 0.418046 time_new 0.085158 difference in time 4.91 comparison result array 1 True array 2 True
array size n x n 200 time_old 3.175042 time_new 0.352767 difference in time 9.0 comparison result array 1 True array 2 True
array size n x n 300 time_old 10.780385 time_new 0.834303 difference in time 12.92 comparison result array 1 True array 2 True
array size n x n 500 time_old 50.109723 time_new 2.55773 difference in time 19.59 comparison result array 1 True array 2 True
array size n x n 700 time_old 138.859871 time_new 6.515611 difference in time 21.31 comparison result array 1 True array 2 True
array size n x n 1000 time_old 408.079446 time_new 11.721369 difference in time 34.81 comparison result array 1 True array 2 True 

decomposition

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

@algorithms-keeperalgorithms-keeperBot added enhancement This PR modified some existing files awaiting reviews This PR is ready to be reviewed labels Oct 1, 2023
@tianyizheng02
tianyizheng02 merged commit 3c14e6a into TheAlgorithms:masterOct 16, 2023
@algorithms-keeperalgorithms-keeperBot removed the awaiting reviews This PR is ready to be reviewed label Oct 16, 2023
@quant12345
quant12345 deleted the decomposition branch August 6, 2024 09:32
@isidroasisidroas mentioned this pull request Jan 25, 2025
14 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementThis PR modified some existing files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@quant12345@tianyizheng02
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Refactoring and optimization of the lu_decomposition algorithm by quant12345 · Pull Request #9231 · TheAlgorithms/Python · GitHub
Skip to content

Refactoring and optimization of the lu_decomposition algorithm - #9231

Merged
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition
Oct 16, 2023
Merged

Refactoring and optimization of the lu_decomposition algorithm#9231
tianyizheng02 merged 3 commits into
TheAlgorithms:masterfrom
quant12345:decomposition

Conversation

@quant12345

Copy link
Copy Markdown
Contributor

Describe your change:

Replacing the generator on numpy vector operations from lu_decomposition.

before: total = sum(lower[i][k] * upper[k][j] for k in range(j))

after: total = np.sum(lower[i, :i] * upper[:i, j])

In 'total', the necessary data is extracted through slices and the sum of the products is obtained.
Moreover, as the array size increases, the ratio of the calculation time of the original algorithm
growing towards the new. As a result, with an array size of n x n 1000, the calculation time for the
original algorithm is almost 7 minutes, and the new one is 12 seconds(time in tests is seconds).

The tests generate n x n arrays. To prevent it from triggering:

raise ArithmeticError("No LU decomposition exists")

diagonal elements are enlarged. Two arrays are extracted from the resulting tuple
rounded to the fifth digit and compared for identity.

code performance tests
"""
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 datetime
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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
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
def lower_upper_decomposition_new(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:
msg = (
"'table' has to be of square shaped array but got a "
f"{rows}x{columns} array:\n{table}"
)
raise ValueError(msg)
lower = np.zeros((rows, columns))
upper = np.zeros((rows, columns))
# in 'total', the necessary data is extracted through slices
# and the sum of the products is obtained.
for i in range(columns):
for j in range(i):
total = np.sum(lower[i, :i] * upper[:i, 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 = np.sum(lower[i, :i] * upper[:i, j])
upper[i][j] = table[i][j] - total
return lower, upper
if __name__ == "__main__":
import doctest
doctest.testmod()
arr = [18, 30, 50, 100, 200, 300, 500, 700, 1000]
for i in range(len(arr)):
n = arr[i]
matrix = np.random.randint(low=-5, high=5, size=(n, n))
matrix[np.diag_indices_from(matrix)] = 10 * n
now = datetime.datetime.now()
original = lower_upper_decomposition(matrix)
time_original = datetime.datetime.now() - now
now = datetime.datetime.now()
new = lower_upper_decomposition_new(matrix)
time_new = datetime.datetime.now() - now
word = ('array size n x n {0} time_old {1} time_new {2}'
' difference in time {3} {4} array 1 {5} array 2 {6}'
.format(n, time_original.total_seconds(),
time_new.total_seconds(), round(time_original / time_new, 2),
'comparison result',
np.all(np.round(original[0], 5) == np.round(new[0], 5)),
np.all(np.round(original[1], 5) == np.round(new[1], 5))))
print(word)

Output:

array size n x n 18 time_old 0.006474 time_new 0.006151 difference in time 1.05 comparison result array 1 True array 2 True
array size n x n 30 time_old 0.012381 time_new 0.008019 difference in time 1.54 comparison result array 1 True array 2 True
array size n x n 50 time_old 0.055401 time_new 0.022404 difference in time 2.47 comparison result array 1 True array 2 True
array size n x n 100 time_old 0.418046 time_new 0.085158 difference in time 4.91 comparison result array 1 True array 2 True
array size n x n 200 time_old 3.175042 time_new 0.352767 difference in time 9.0 comparison result array 1 True array 2 True
array size n x n 300 time_old 10.780385 time_new 0.834303 difference in time 12.92 comparison result array 1 True array 2 True
array size n x n 500 time_old 50.109723 time_new 2.55773 difference in time 19.59 comparison result array 1 True array 2 True
array size n x n 700 time_old 138.859871 time_new 6.515611 difference in time 21.31 comparison result array 1 True array 2 True
array size n x n 1000 time_old 408.079446 time_new 11.721369 difference in time 34.81 comparison result array 1 True array 2 True 

decomposition

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

@algorithms-keeperalgorithms-keeperBot added enhancement This PR modified some existing files awaiting reviews This PR is ready to be reviewed labels Oct 1, 2023
@tianyizheng02
tianyizheng02 merged commit 3c14e6a into TheAlgorithms:masterOct 16, 2023
@algorithms-keeperalgorithms-keeperBot removed the awaiting reviews This PR is ready to be reviewed label Oct 16, 2023
@quant12345
quant12345 deleted the decomposition branch August 6, 2024 09:32
@isidroasisidroas mentioned this pull request Jan 25, 2025
14 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementThis PR modified some existing files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@quant12345@tianyizheng02