Copyright (C) 2021 Andreas Kloeckner
import numpy as np
import numpy.linalg as la
np.set_printoptions(linewidth=150, suppress=True, precision=3)
n = 5
np.random.seed(235)
A = np.random.randn(n, n)
A[0,0] = 0
A
array([[ 0. , 0.769, 1.436, -1.441, -0.165], [-0.407, -1.935, -0.037, 0.178, 0.371], [ 0.271, -0.743, -1.049, -1.63 , -1.277], [-0.476, -1.349, 1.129, -1.424, -1.897], [-0.01 , -0.978, -1.146, 2.017, 1.023]])
This function returns a matrix that swas rows i
and j
:
def row_swap_mat(i, j):
P = np.eye(n)
P[i] = 0
P[j] = 0
P[i, j] = 1
P[j, i] = 1
return P
We're trying to obtain $PA=LU$. Initialize:
i = 0
I = np.eye(n)
P = I.copy()
Lsub = np.zeros_like(A)
U = np.zeros_like(A)
remaining = A.copy()
remaining
array([[ 0. , 0.769, 1.436, -1.441, -0.165], [-0.407, -1.935, -0.037, 0.178, 0.371], [ 0.271, -0.743, -1.049, -1.63 , -1.277], [-0.476, -1.349, 1.129, -1.424, -1.897], [-0.01 , -0.978, -1.146, 2.017, 1.023]])
First, find the pivot as ipiv
:
ipiv = i + np.argmax(np.abs(remaining[i:, i]))
ipiv
3
Swap the rows in remaining
, record in P
:
swap_mat = row_swap_mat(i, ipiv)
P = swap_mat @ P
remaining = swap_mat @ remaining
remaining
array([[-0.476, -1.349, 1.129, -1.424, -1.897], [-0.407, -1.935, -0.037, 0.178, 0.371], [ 0.271, -0.743, -1.049, -1.63 , -1.277], [ 0. , 0.769, 1.436, -1.441, -0.165], [-0.01 , -0.978, -1.146, 2.017, 1.023]])
Now carry out a step of LU, as above:
U[i, i:] = remaining[i, i:]
Lsub[i+1:,i] = remaining[i+1:,i]/U[i,i]
remaining[i+1:, i+1:] -= np.outer(Lsub[i+1:,i], U[i, i+1:])
i = i + 1
print(P@A-(Lsub+I)@U)
[[ 0. 0. 0. 0. 0. ] [ 0. -0.78 -1.003 1.397 1.995] [ 0. -1.513 -0.405 -2.443 -2.359] [ 0. 0.769 1.436 -1.441 -0.165] [ 0. -0.949 -1.17 2.048 1.064]]
Find the pivot and perform the swaps so that you still have a valid $PA=LU$ factorization:
ipiv = i + np.argmax(np.abs(remaining[i:, i]))
swap_mat = row_swap_mat(i, ipiv)
P = swap_mat @ P
Lsub = swap_mat @ Lsub
remaining = swap_mat @ remaining
Here are some checks to make sure you're on the right track:
print("Should maintain the same 'zero fringe' as the previous step:")
print(P @ A - (Lsub+I) @ U)
print("Should be zero:")
print(remaining[i:, i:] - (P @ A - (Lsub+I) @ U)[i:, i:])
Should maintain the same 'zero fringe' as the previous step: [[ 0. 0. 0. 0. 0. ] [ 0. -0. 0. 0. 0. ] [ 0. 0. 0. -0. 0. ] [ 0. -0. -0. 0. 0. ] [ 0. 0. 0. 0. 1.439]] Should be zero: [[0.]]
Carry out a step of LU, as always:
U[i, i:] = remaining[i, i:]
Lsub[i+1:,i] = remaining[i+1:,i]/U[i,i]
remaining[i+1:, i+1:] -= np.outer(Lsub[i+1:,i], U[i, i+1:])
i = i + 1
print(P@A-(Lsub+I)@U)
[[ 0. 0. 0. 0. 0.] [ 0. -0. 0. 0. 0.] [ 0. 0. 0. -0. 0.] [ 0. -0. -0. 0. 0.] [ 0. 0. 0. 0. 0.]]
P
array([[0., 0., 0., 1., 0.], [0., 0., 1., 0., 0.], [1., 0., 0., 0., 0.], [0., 0., 0., 0., 1.], [0., 1., 0., 0., 0.]])
I+Lsub
array([[ 1. , 0. , 0. , 0. , 0. ], [-0.571, 1. , 0. , 0. , 0. ], [ 0. , -0.508, 1. , 0. , 0. ], [ 0.021, 0.627, -0.745, 1. , 0. ], [ 0.856, 0.515, -0.646, 0.583, 1. ]])
U
array([[-0.476, -1.349, 1.129, -1.424, -1.897], [ 0. , -1.513, -0.405, -2.443, -2.359], [ 0. , 0. , 1.23 , -2.682, -1.363], [ 0. , 0. , 0. , 1.583, 1.529], [ 0. , 0. , 0. , 0. , 1.439]])
Lsub
instead of all of L
?