Skip to main content

Python NumPy: How to Calculate Matrix Determinant in NumPy

The determinant is a scalar value that reveals important matrix properties: whether a matrix is invertible, how it scales volumes, and its relationship to eigenvalues.

Basic Calculation

Use np.linalg.det() to compute the determinant:

import numpy as np

matrix = np.array([
[1, 2],
[3, 4]
])

det = np.linalg.det(matrix)
print(f"Determinant: {det}")

Output:

Determinant: -2.0000000000000004

Manual Verification (2×2)

For a 2×2 matrix [[a, b], [c, d]], the determinant is ad - bc:

import numpy as np

matrix = np.array([[1, 2], [3, 4]])

# Manual: (1×4) - (2×3) = 4 - 6 = -2
manual_det = matrix[0, 0] * matrix[1, 1] - matrix[0, 1] * matrix[1, 0]

# NumPy calculation
numpy_det = np.linalg.det(matrix)

print(f"Manual: {manual_det}")
print(f"NumPy: {numpy_det}")

Output:

Manual: -2
NumPy: -2.0000000000000004

Checking for Singular Matrices

A determinant of zero means the matrix is singular (non-invertible):

import numpy as np

# Singular matrix: rows are proportional
singular = np.array([
[1, 2],
[2, 4] # 2× first row
])

det = np.linalg.det(singular)
print(f"Determinant: {det}") # 0.0 (or very close to 0)

# Attempting to invert will fail or give unreliable results
try:
inv = np.linalg.inv(singular)
except np.linalg.LinAlgError:
print("Cannot invert singular matrix")

Output:

Determinant: 0.0
Cannot invert singular matrix

Floating-Point Precision

Due to floating-point arithmetic, check for near-zero rather than exact zero:

import numpy as np

# Nearly singular matrix
nearly_singular = np.array([
[1, 2],
[2, 4.0000000001]
])

det = np.linalg.det(nearly_singular)
print(f"Determinant: {det}") # Very small, not exactly 0

# Use isclose for reliable checking
if np.isclose(det, 0, atol=1e-10):
print("Matrix is effectively singular")
else:
print("Matrix is invertible")

Output:

Determinant: 1.0000000827403705e-10
Matrix is invertible
tip

Always use np.isclose(det, 0) instead of det == 0 when checking for singularity to account for floating-point imprecision.

Determinant Properties

import numpy as np

A = np.array([[1, 2], [3, 4]])
B = np.array([[2, 0], [1, 3]])

# Property 1: det(AB) = det(A) × det(B)
det_AB = np.linalg.det(A @ B)
det_A_times_det_B = np.linalg.det(A) * np.linalg.det(B)
print(f"det(AB) = {det_AB:.4f}")
print(f"det(A)×det(B) = {det_A_times_det_B:.4f}")

# Property 2: det(A^T) = det(A)
print(f"det(A) = {np.linalg.det(A):.4f}")
print(f"det(A.T) = {np.linalg.det(A.T):.4f}")

# Property 3: det(cA) = c^n × det(A) for n×n matrix
c = 2
n = A.shape[0]
print(f"det(2A) = {np.linalg.det(c * A):.4f}")
print(f"2^2 × det(A) = {c**n * np.linalg.det(A):.4f}")

# Property 4: det(A^-1) = 1/det(A)
if not np.isclose(np.linalg.det(A), 0):
A_inv = np.linalg.inv(A)
print(f"det(A^-1) = {np.linalg.det(A_inv):.4f}")
print(f"1/det(A) = {1/np.linalg.det(A):.4f}")

Output:

det(AB) = -12.0000
det(A)×det(B) = -12.0000
det(A) = -2.0000
det(A.T) = -2.0000
det(2A) = -8.0000
2^2 × det(A) = -8.0000
det(A^-1) = -0.5000
1/det(A) = -0.5000

Large Matrices: Using slogdet()

For large matrices, determinants can overflow or underflow. Use slogdet() to get the sign and log of the absolute determinant:

import numpy as np

# Large matrix where regular determinant might overflow
large_matrix = np.random.randn(100, 100)

# Regular determinant (might be very large or very small)
det = np.linalg.det(large_matrix)
print(f"Regular det: {det}") # May show inf or 0

# Log-determinant (numerically stable)
sign, logdet = np.linalg.slogdet(large_matrix)
print(f"Sign: {sign}")
print(f"Log|det|: {logdet}")

# Reconstruct: det = sign × exp(logdet)
reconstructed = sign * np.exp(logdet)
print(f"Reconstructed: {reconstructed}")

Output:

Regular det: -1.0398456917186009e+78
Sign: -1.0
Log|det|: 179.64070958233293
Reconstructed: -1.0398456917186009e+78

When to Use slogdet()

import numpy as np

def stable_determinant(matrix):
"""Compute determinant with overflow protection."""
sign, logdet = np.linalg.slogdet(matrix)

if sign == 0:
return 0.0 # Singular matrix

# Check if result would overflow
if logdet > 700: # exp(700) ≈ 10^304
print(f"Warning: |det| ≈ exp({logdet:.1f}) - extremely large")
return sign * np.inf
elif logdet < -700:
print(f"Warning: |det| ≈ exp({logdet:.1f}) - extremely small")
return 0.0

return sign * np.exp(logdet)

# Test with matrices of different sizes
for n in [10, 50, 100, 200]:
matrix = np.random.randn(n, n)
det = stable_determinant(matrix)
print(f"{n}×{n} matrix: det ≈ {det:.2e}")

Output:

10×10 matrix: det ≈ 1.13e+04
50×50 matrix: det ≈ -3.88e+31
100×100 matrix: det ≈ 5.68e+77
200×200 matrix: det ≈ -1.36e+185
note

slogdet() returns (sign, logdet) where the actual determinant is sign * exp(logdet). This representation avoids overflow for large matrices.

Determinant and Volume

The absolute value of the determinant represents the scaling factor for volumes under the linear transformation:

import numpy as np
import matplotlib.pyplot as plt

# Original unit square vertices
unit_square = np.array([
[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0, 0] # Close the square
]).T

# Transformation matrix
A = np.array([
[2, 1],
[0, 3]
])

# Transform the square
transformed = A @ unit_square

det = np.linalg.det(A)
print(f"Determinant: {det}")
print(f"Original area: 1")
print(f"Transformed area: {abs(det)}")

# The determinant (6) tells us the area is scaled by 6×

Output:

Determinant: 6.0
Original area: 1
Transformed area: 6.0

Determinant and Eigenvalues

The determinant equals the product of eigenvalues:

import numpy as np

matrix = np.array([
[4, 2],
[1, 3]
])

# Calculate determinant
det = np.linalg.det(matrix)

# Calculate eigenvalues
eigenvalues = np.linalg.eigvals(matrix)
product_of_eigenvalues = np.prod(eigenvalues)

print(f"Determinant: {det:.4f}")
print(f"Eigenvalues: {eigenvalues}")
print(f"Product of eigenvalues: {product_of_eigenvalues.real:.4f}")

Output:

Determinant: 10.0000
Eigenvalues: [5. 2.]
Product of eigenvalues: 10.0000

Practical Example: Checking System Solvability

import numpy as np

def analyze_system(A, b):
"""Analyze a linear system Ax = b."""
det = np.linalg.det(A)

print(f"Coefficient matrix:\n{A}")
print(f"Determinant: {det:.6f}")

if np.isclose(det, 0, atol=1e-10):
print("System is singular - no unique solution")
print("Possible scenarios:")
print(" - No solution (inconsistent)")
print(" - Infinite solutions (dependent)")
return None

x = np.linalg.solve(A, b)
print(f"Unique solution: {x}")

# Verify
residual = np.linalg.norm(A @ x - b)
print(f"Residual: {residual:.2e}")

return x

# Solvable system
A1 = np.array([[2, 1], [1, 3]])
b1 = np.array([5, 5])
analyze_system(A1, b1)

print("\n" + "="*40 + "\n")

# Singular system
A2 = np.array([[1, 2], [2, 4]])
b2 = np.array([3, 6])
analyze_system(A2, b2)

Output:

Coefficient matrix:
[[2 1]
[1 3]]
Determinant: 5.000000
Unique solution: [2. 1.]
Residual: 0.00e+00

========================================

Coefficient matrix:
[[1 2]
[2 4]]
Determinant: 0.000000
System is singular - no unique solution
Possible scenarios:
- No solution (inconsistent)
- Infinite solutions (dependent)

Summary

FunctionUse CaseReturns
np.linalg.det(A)Standard determinantfloat
np.linalg.slogdet(A)Large matrices(sign, log|det|)
Determinant ValueMatrix Property
≠ 0Invertible (non-singular)
= 0Singular (no inverse)
> 0Preserves orientation
< 0Reverses orientation
|det|Volume scaling factor

Best Practice: Use np.linalg.det() for standard calculations and np.isclose(det, 0) for singularity checks. Switch to slogdet() for matrices larger than ~50×50 to avoid numerical overflow.