Skip to content

LU Decomposition

LU decomposition factors a matrix into lower and upper triangular matrices.

Basic LU Factorization

1. linalg.lu

import numpy as np
from scipy import linalg

def main():
    A = np.array([[2, 1, 1],
                  [4, 3, 3],
                  [8, 7, 9]])

    P, L, U = linalg.lu(A)

    print("P (permutation matrix):")
    print(P)
    print()
    print("L (lower triangular):")
    print(L)
    print()
    print("U (upper triangular):")
    print(U)
    print()
    print("Verify P @ L @ U:")
    print(P @ L @ U)

if __name__ == "__main__":
    main()

2. Mathematical Form

\[A = PLU\]

where: - \(P\) is a permutation matrix - \(L\) is lower triangular with ones on diagonal - \(U\) is upper triangular

3. Permutation Purpose

import numpy as np
from scipy import linalg

def main():
    # Without permutation, LU can fail or be unstable
    A = np.array([[0, 1],
                  [1, 1]])

    P, L, U = linalg.lu(A)

    print("A has zero on diagonal:")
    print(A)
    print()
    print("P swaps rows:")
    print(P)
    print()
    print("Result: P @ L @ U =")
    print(P @ L @ U)

if __name__ == "__main__":
    main()

LU Factor for Solving

1. linalg.lu_factor

import numpy as np
from scipy import linalg

def main():
    A = np.array([[2, 1, 1],
                  [4, 3, 3],
                  [8, 7, 9]])
    b = np.array([4, 10, 24])

    # Factor once
    lu, piv = linalg.lu_factor(A)

    # Solve using factorization
    x = linalg.lu_solve((lu, piv), b)

    print(f"Solution: x = {x}")
    print(f"Verify A @ x = {A @ x}")

if __name__ == "__main__":
    main()

2. Multiple Right-Hand Sides

import numpy as np
from scipy import linalg

def main():
    A = np.array([[2, 1],
                  [1, 3]])

    # Factor once
    lu, piv = linalg.lu_factor(A)

    # Solve for multiple b vectors
    b1 = np.array([3, 4])
    b2 = np.array([1, 2])
    b3 = np.array([5, 5])

    x1 = linalg.lu_solve((lu, piv), b1)
    x2 = linalg.lu_solve((lu, piv), b2)
    x3 = linalg.lu_solve((lu, piv), b3)

    print(f"x1 = {x1}")
    print(f"x2 = {x2}")
    print(f"x3 = {x3}")

if __name__ == "__main__":
    main()

3. Performance Benefit

import numpy as np
from scipy import linalg
import time

def main():
    n = 1000
    A = np.random.randn(n, n)

    # Multiple solves without factorization
    start = time.perf_counter()
    for _ in range(10):
        b = np.random.randn(n)
        x = linalg.solve(A, b)
    no_factor_time = time.perf_counter() - start

    # Factor once, solve multiple times
    start = time.perf_counter()
    lu, piv = linalg.lu_factor(A)
    for _ in range(10):
        b = np.random.randn(n)
        x = linalg.lu_solve((lu, piv), b)
    factor_time = time.perf_counter() - start

    print(f"Without pre-factoring: {no_factor_time:.4f} sec")
    print(f"With pre-factoring:    {factor_time:.4f} sec")

if __name__ == "__main__":
    main()

Packed Format

1. Understanding lu_factor Output

import numpy as np
from scipy import linalg

def main():
    A = np.array([[2, 1, 1],
                  [4, 3, 3],
                  [8, 7, 9]])

    # lu_factor returns packed format
    lu, piv = linalg.lu_factor(A)

    print("Packed LU matrix:")
    print(lu)
    print()
    print("Pivot indices:")
    print(piv)
    print()

    # Compare with full decomposition
    P, L, U = linalg.lu(A)
    print("L from full decomposition:")
    print(L)
    print()
    print("U from full decomposition:")
    print(U)

if __name__ == "__main__":
    main()

2. Extract L and U

import numpy as np
from scipy import linalg

def main():
    A = np.array([[2, 1, 1],
                  [4, 3, 3],
                  [8, 7, 9]])

    lu, piv = linalg.lu_factor(A)

    # Extract L (lower part + unit diagonal)
    L = np.tril(lu, k=-1) + np.eye(len(A))

    # Extract U (upper part including diagonal)
    U = np.triu(lu)

    print("Extracted L:")
    print(L)
    print()
    print("Extracted U:")
    print(U)

if __name__ == "__main__":
    main()

Determinant via LU

1. Compute Determinant

import numpy as np
from scipy import linalg

def main():
    A = np.array([[2, 1, 1],
                  [4, 3, 3],
                  [8, 7, 9]])

    # Determinant = product of U diagonal * sign from permutation
    P, L, U = linalg.lu(A)

    det_U = np.prod(np.diag(U))
    det_P = np.linalg.det(P)  # +1 or -1

    det_A = det_P * det_U

    print(f"det(A) via LU: {det_A}")
    print(f"det(A) direct: {np.linalg.det(A)}")

if __name__ == "__main__":
    main()

2. Why This Works

\[\det(A) = \det(P)\det(L)\det(U) = \det(P) \cdot 1 \cdot \prod_{i} U_{ii}\]

Inverse via LU

1. Compute Inverse

import numpy as np
from scipy import linalg

def main():
    A = np.array([[2, 1],
                  [1, 3]])

    # Factor
    lu, piv = linalg.lu_factor(A)

    # Solve A @ X = I for inverse
    I = np.eye(len(A))
    A_inv = linalg.lu_solve((lu, piv), I)

    print("A inverse via LU:")
    print(A_inv)
    print()
    print("Verify A @ A_inv:")
    print(A @ A_inv)

if __name__ == "__main__":
    main()

Applications

1. System of Equations

import numpy as np
from scipy import linalg

def main():
    # Economic model: supply-demand equilibrium
    # 2p1 + p2 = 10
    # p1 + 3p2 = 15

    A = np.array([[2, 1],
                  [1, 3]])
    b = np.array([10, 15])

    lu, piv = linalg.lu_factor(A)
    prices = linalg.lu_solve((lu, piv), b)

    print(f"Equilibrium prices: {prices}")

if __name__ == "__main__":
    main()

2. Circuit Analysis

import numpy as np
from scipy import linalg

def main():
    # Kirchhoff's laws
    R = np.array([[10, -5, 0],
                  [-5, 15, -10],
                  [0, -10, 20]])
    V = np.array([10, 0, 0])

    lu, piv = linalg.lu_factor(R)
    currents = linalg.lu_solve((lu, piv), V)

    print(f"Branch currents: {currents}")

if __name__ == "__main__":
    main()

Summary

1. Functions

Function Description
linalg.lu(A) Full P, L, U matrices
linalg.lu_factor(A) Packed format for solving
linalg.lu_solve((lu, piv), b) Solve using factorization

2. Complexity

  • Factorization: \(O(n^3)\)
  • Each solve: \(O(n^2)\)
  • Pre-factor when solving multiple systems with same \(A\)