Gauss elimination is a systematic way of eliminating unknowns, one at a time, from the given equations until we are left with an equation containing only one unknown. At the end of the elimination procedure, we end up with $N$ equations where the first contains all $N$ unknowns, the second contains $N-1$ unknowns, the third, $N-2$ unknowns and the last, only one unknown. Back substitution comes into play at that point. The value of the last unknown is found from the last equation with only one unknown, and then its value is plugged back into the second-last equation containing two unknonwns, and so on. At the end of back substitution, all the $N$ unknowns are found and the system of equations is solved.
The code in the cells below reads the $A$ and $B$ matrices and generates the augmented matrix $C$.
import numpy as np
np.set_printoptions(precision=2, suppress=True) # only for pretty printing outputs
# Read the matrices A and B from user
A = np.array([[-2, -4, -1, 1],
[-4, -4, -4, 1], # Enter coefficients here for A
[-5, 0, 4, -4],
[3, 1, -2, 3]], dtype='float')
B = np.array([[-7, -32, -14, 17]]) # Enter RHS values here for B
C = np.concatenate((A, B.T), axis=1)
print(C)
The result at the end of the Gaussian elimination step is a matrix that contains no $0$s in the first row, the first element as $0$ in the second row, the first two elements as $0$s in the third row, $\ldots$, the first $N-2$ elements as $0$ in the $N-1$ or the last but one row, and the first $N-1$ elements as $0$s in the last row. In fact, the last row contains $0$s for all the coefficients except for that of $x_N$ in the last but one column and some value in the last column. Mathematically, we say that all the elements
$$
C_{i,j} = 0, ~~~~~~~\mbox{for}~ j < i
$$
Such a matrix is said to be in
Gauss elimination is done using standard
In practice, we introduce a small computational extra into the above elimination procedure. When operating with decimal numbers on a computer, it is better to deal with large numbers. So we introduce a
Finally, here are the steps in Gaussian elimination of matrix $C$.
#############################################################################
# Code for gauss_eliminate step. It takes the augmented matrix as input:
# named as eqns in the code. The debug parameter, if True prints a lot
# of intermediate values so that the code can be debugged.
# The function returns the augmented matrix in row echelon form so that
# it can be given to a back substitution function.
#############################################################################
def gauss_eliminate(eqns, debug=False) :
nrows = eqns.shape[0]
ncols = eqns.shape[1]
for r_piv in range(nrows - 1) : # Step 1
# The below step finds the pivot row. Do not try to understand it
# on the first attempt. You must read the numpy tutorial to
# find out about np.ix_() function. Step 2
pivot = np.ix_(abs(eqns[r_piv:,r_piv]) ==
abs(eqns[r_piv:,r_piv]).max())[0][0] + r_piv
# In rare cases, it is possible that the unknown
# already is not present in the input equations.
# If the pivot is already 0, skip remaining steps
if eqns[pivot,r_piv] != 0 :
# We swap rows if necessary! Python has an easy operation to swap
# rows of a matrix. We use it. Search the Internet to understand
# how it is done! Step 3
if pivot != r_piv :
eqns[[r_piv, pivot]] = eqns[[pivot, r_piv]]
if debug :
print('Pivot row: ', pivot)
print('Swapping Rows: ', r_piv, pivot)
print('Swapped Equations Array: ')
print(eqns)
# For all the rows below the pivot,
# row = mult_fact * row - pivot_row
for row in range(r_piv+1, nrows) : # Step 4
############################################
# Insert your code here for Step 4A and 4B
############################################
if debug :
print('Multiplicative Factor: ', k)
print('After reduction of row: ', row+1)
print(eqns)
# We are done with eliminating the r_piv unknown. Repeat for others.
return eqns
The code skeleton given below keeps track of the unknown whose value will be computed in the current step as
Here are the steps on the row echelon matrix $R$ obtained from Gaussian elimination step.
We are ready to repeat the steps to compute another unknown.
def back_substitute(eqns) :
nrows = eqns.shape[0]
ncols = eqns.shape[1]
sol = # Step 1: Insert code
for var_to_compute in range(nrows-1, -1, -1) : # Step 2
eqn_num = var_to_compute
# Check to see if the coefficient is 0. We cannot
# solve the system of equation in that case.
if eqns[eqn_num, var_to_compute] == 0 :
print('Unique solution does not exist!')
return False
rhs = # Step 3: Insert code
psum = 0.0 # Step 4: Insert code
# Compute psum value #
rhs = # Step 5: Insert code
sol[var_to_compute] = # Step 6: Insert code
return sol
We now see how to get these two matrices using the intermediate steps of Gaussian elimination. Remember, the we eliminate the unknowns one at a time. To eliminate $x_1$, we find multiplication factors that make all the elements of
While we do the Gaussian elimination, let us keep track of the multiplication factors in each column in a matrix called
The elimination must be done using the following formula instead of the one used in Gauss elimination - this helps compute the row-echelon matrix as the desired U.
As the multiplication factors are used to eliminate only entries below the diagonal in the $A$ portion of the augmented matrix, the matrix containing all the multiplication factors with the diagonal elements as 1 is in a
Where do we get the $U$ matrix from? Well, we already have it! It is the row echelon matrix from the Gaussian elimination method without the last column. Remember the last column is from the matrix $B$ and has nothing to do with $A$. Thus, we have both the $L$ and $U$ matrices from one single run of the Gaussian elimination method.
You can verify that these are indeed the required $L$ and $U$ matrices by multiplying them. The product is the $A$ matrix.
The code skeleton is given in the cells below. Use the following steps to complete the code. In the code, matrix $C$ is given by the variable
def LU_decomposition(eqns, debug=False) :
nrows = eqns.shape[0]
ncols = eqns.shape[1]
temp = eqns.copy()
MFM = np.eye(eqns.shape[0]) # Step 2: Done for you
for r_piv in range(ncols - 1) : # Repeat step
# The diagonal element cannot be 0 in this
# simple algorithm. Check for that.
if temp[r_piv,r_piv] != 0 : # Part of Step 1
# Insert code to compute multiplication factors
# and store in MFM matrix # Step 4
return MFM, temp[:,:-1]
A = np.array([[5, 1, -4, -3],
[-5, -3., 1., -3],
[-3. , 1., -2., 0],
[5., -2., 3., -4.]
], dtype='float')
B = np.array([[-41., -30., -19., -3.]])
C = np.concatenate((A, B.T), axis=1)
L, U = LU_decomposition(C, debug=True)
print('############# U MATRIX ###############')
print(U)
print('############# L MATRIX ###############')
print(L)
print('############# VERIFY A = LU ###############')
print(L.dot(U))
A = np.array([[12, -1, -1, -1, -1, -1, -1, -3, -1, -1],
[-1, 10, -1, -1, -1, -3, -1, -1, -1, -1],
[-1, -1, 7, 0, 0, 0, -2, 1, 1, 1],
[-2, 1, -1, 15, -5, -1, 0, -2, 3, -1],
[1, 1, 1, 1, -22, 1, 1, 1, -4, 1],
[-3, -3, 0, -1, 0, 9, 0, 0, 0, 0],
[5, -3, -2, -2, 0, 3, 15, -1, -3, -1],
[1, -2, 1, -2, 1, -2, 1, 25, -1, 1],
[1, 1, 1, 1, 1, -1, -1, -1, 18, -1],
[-1, -1, -1, -1, 1, 1, 1, 1, 1, 10]], dtype='float')
B = np.array([70, 0, 32, 5, 22, 0, 106, 30, 5, 69], dtype='float')