import numpy as np
import numpy.linalg as la
np.set_printoptions(precision=3, suppress=True)
Let's grab a (admittedly well-chosen) sample matrix A
:
n = 4
np.random.seed(235)
A = np.round(5*np.random.randn(n, n))
A[0,0] = 0
A[2,1] = 17
A[0,2] = 19
A
Now define a function row_swap_mat(i, j)
that returns a permutation matrix that swaps row 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
What do these matrices look like?
row_swap_mat(0,1)
Do they work?
row_swap_mat(0,1).dot(A)
U
is the copy of A
that we'll modify:
U = A.copy()
Create P1 to swap up the right row:
P1 = row_swap_mat(0, 3)
U = P1.dot(U)
U
M1 = np.eye(n)
M1[1,0] = -U[1,0]/U[0,0]
M1[2,0] = -U[2,0]/U[0,0]
M1
U = M1.dot(U)
U
Create P2
to swap up the right row:
P2 = row_swap_mat(2,1)
U = P2.dot(U)
U
Make the second-column elimination matrix M2
:
M2 = np.eye(n)
M2[2,1] = -U[2,1]/U[1,1]
M2[3,1] = -U[3,1]/U[1,1]
M2
U = M2.dot(U)
U
Create P3
to swap up the right entry:
P3 = row_swap_mat(3, 2)
U = P3.dot(U)
U
Make the third-column elimination matrix M3
:
M3 = np.eye(n)
M3[3,2] = -U[3,2]/U[2,2]
M3
U = M3.dot(U)
U
So we've built $M3P_3M_2P_2M_1P_1A=U$.
M3.dot(P3).dot(M2).dot(P2).dot(M1).dot(P1).dot(A)
That left factor is anything but lower triangular:
M3.dot(P3).dot(M2).dot(P2).dot(M1).dot(P1)
Now try the reordering trick:
L3 = M3
L2 = P3.dot(M2).dot(la.inv(P3))
L1 = P3.dot(P2).dot(M1).dot(la.inv(P2)).dot(la.inv(P3))
L3.dot(L2).dot(L1).dot(P3).dot(P2).dot(P1)
We were promised that all of the L
n are still lower-triangular:
print(L1)
print(L2)
print(L3)
So their product is, too:
Ltemp = L3.dot(L2).dot(L1)
Ltemp
P
is still a permutation matrix (but a more complicated one):
P = P3.dot(P2).dot(P1)
P
So to sum up, we've made:
Ltemp.dot(P).dot(A) - U
Multiply from the left by Ltemp
${}^{-1}$, which is also lower triangular:
L = la.inv(Ltemp)
L
P.dot(A) - L.dot(U)