Skip to content

Commit b371553

Browse files
Matrix determinant calculation using various methods
1 parent b5219bc commit b371553

File tree

1 file changed

+197
-0
lines changed

1 file changed

+197
-0
lines changed

linear_algebra/determinant.py

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
"""
2+
Matrix determinant calculation using various methods.
3+
4+
The determinant is a scalar value that characterizes a square matrix.
5+
It provides important information about the matrix, such as whether it's invertible.
6+
7+
Reference: https://en.wikipedia.org/wiki/Determinant
8+
"""
9+
10+
import numpy as np
11+
from numpy import float64
12+
from numpy.typing import NDArray
13+
14+
15+
def determinant_recursive(matrix: NDArray[float64]) -> float:
16+
"""
17+
Calculate the determinant of a square matrix using recursive cofactor expansion.
18+
19+
This method is suitable for small matrices but becomes inefficient for large matrices.
20+
21+
Parameters:
22+
matrix (NDArray[float64]): A square matrix
23+
24+
Returns:
25+
float: The determinant of the matrix
26+
27+
Raises:
28+
ValueError: If the matrix is not square
29+
30+
Examples:
31+
>>> import numpy as np
32+
>>> matrix = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=float)
33+
>>> determinant_recursive(matrix)
34+
-2.0
35+
36+
>>> matrix = np.array([[5.0]], dtype=float)
37+
>>> determinant_recursive(matrix)
38+
5.0
39+
"""
40+
if matrix.shape[0] != matrix.shape[1]:
41+
raise ValueError("Matrix must be square")
42+
43+
n = matrix.shape[0]
44+
45+
# Base cases
46+
if n == 1:
47+
return float(matrix[0, 0])
48+
49+
if n == 2:
50+
return float(matrix[0, 0] * matrix[1, 1] - matrix[0, 1] * matrix[1, 0])
51+
52+
# Recursive case: cofactor expansion along the first row
53+
det = 0.0
54+
for col in range(n):
55+
# Create submatrix by removing row 0 and column col
56+
submatrix = np.delete(np.delete(matrix, 0, axis=0), col, axis=1)
57+
58+
# Calculate cofactor
59+
cofactor = ((-1) ** col) * matrix[0, col] * determinant_recursive(submatrix)
60+
det += cofactor
61+
62+
return det
63+
64+
65+
def determinant_lu(matrix: NDArray[float64]) -> float:
66+
"""
67+
Calculate the determinant using LU decomposition.
68+
69+
This method is more efficient for larger matrices than recursive expansion.
70+
71+
Parameters:
72+
matrix (NDArray[float64]): A square matrix
73+
74+
Returns:
75+
float: The determinant of the matrix
76+
77+
Raises:
78+
ValueError: If the matrix is not square
79+
"""
80+
if matrix.shape[0] != matrix.shape[1]:
81+
raise ValueError("Matrix must be square")
82+
83+
n = matrix.shape[0]
84+
85+
# Create a copy to avoid modifying the original matrix
86+
A = matrix.astype(float64, copy=True)
87+
88+
# Keep track of row swaps for sign adjustment
89+
swap_count = 0
90+
91+
# Forward elimination to get upper triangular matrix
92+
for i in range(n):
93+
# Find pivot
94+
max_row = i
95+
for k in range(i + 1, n):
96+
if abs(A[k, i]) > abs(A[max_row, i]):
97+
max_row = k
98+
99+
# Swap rows if needed
100+
if max_row != i:
101+
A[[i, max_row]] = A[[max_row, i]]
102+
swap_count += 1
103+
104+
# Check for singular matrix
105+
if abs(A[i, i]) < 1e-14:
106+
return 0.0
107+
108+
# Eliminate below pivot
109+
for k in range(i + 1, n):
110+
factor = A[k, i] / A[i, i]
111+
for j in range(i, n):
112+
A[k, j] -= factor * A[i, j]
113+
114+
# Calculate determinant as product of diagonal elements
115+
det = 1.0
116+
for i in range(n):
117+
det *= A[i, i]
118+
119+
# Adjust sign based on number of row swaps
120+
if swap_count % 2 == 1:
121+
det = -det
122+
123+
return det
124+
125+
126+
def determinant(matrix: NDArray[float64]) -> float:
127+
"""
128+
Calculate the determinant of a square matrix using the most appropriate method.
129+
130+
Uses recursive expansion for small matrices (≤3x3) and LU decomposition for larger ones.
131+
132+
Parameters:
133+
matrix (NDArray[float64]): A square matrix
134+
135+
Returns:
136+
float: The determinant of the matrix
137+
138+
Examples:
139+
>>> import numpy as np
140+
>>> matrix = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=float)
141+
>>> determinant(matrix)
142+
-2.0
143+
"""
144+
if matrix.shape[0] != matrix.shape[1]:
145+
raise ValueError("Matrix must be square")
146+
147+
n = matrix.shape[0]
148+
149+
# Use recursive method for small matrices, LU decomposition for larger ones
150+
if n <= 3:
151+
return determinant_recursive(matrix)
152+
else:
153+
return determinant_lu(matrix)
154+
155+
156+
def test_determinant() -> None:
157+
"""
158+
Test function for matrix determinant calculation.
159+
160+
>>> test_determinant() # self running tests
161+
"""
162+
# Test 1: 2x2 matrix
163+
matrix_2x2 = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=float)
164+
det_2x2 = determinant(matrix_2x2)
165+
assert abs(det_2x2 - (-2.0)) < 1e-10, "2x2 determinant calculation failed"
166+
167+
# Test 2: 3x3 matrix
168+
matrix_3x3 = np.array([[2.0, -3.0, 1.0],
169+
[2.0, 0.0, -1.0],
170+
[1.0, 4.0, 5.0]], dtype=float)
171+
det_3x3 = determinant(matrix_3x3)
172+
assert abs(det_3x3 - 49.0) < 1e-10, "3x3 determinant calculation failed"
173+
174+
# Test 3: Singular matrix
175+
singular_matrix = np.array([[1.0, 2.0], [2.0, 4.0]], dtype=float)
176+
det_singular = determinant(singular_matrix)
177+
assert abs(det_singular) < 1e-10, "Singular matrix should have zero determinant"
178+
179+
# Test 4: Identity matrix
180+
identity_3x3 = np.eye(3, dtype=float)
181+
det_identity = determinant(identity_3x3)
182+
assert abs(det_identity - 1.0) < 1e-10, "Identity matrix should have determinant 1"
183+
184+
# Test 5: Compare recursive and LU methods
185+
test_matrix = np.array([[1.0, 2.0, 3.0],
186+
[0.0, 1.0, 4.0],
187+
[5.0, 6.0, 0.0]], dtype=float)
188+
det_recursive = determinant_recursive(test_matrix)
189+
det_lu = determinant_lu(test_matrix)
190+
assert abs(det_recursive - det_lu) < 1e-10, "Recursive and LU methods should give same result"
191+
192+
193+
if __name__ == "__main__":
194+
import doctest
195+
196+
doctest.testmod()
197+
test_determinant()

0 commit comments

Comments
 (0)