Lab05: Systems of Equations (Conclusion)


In this lab, you must complete implementing LU Decomposition and Iterative Jacobi methods for solving systems of equations. There is also an exercise at the end comprising problems from Root Finding and Systems of Equations.


1. LU decomposition

This method uses the information from Gaussian elimination to simplify solving systems of equations when only the $B$ matrix changes. The idea is to split the $A$ matrix (that remains the same between problems) into two parts $L$ and $U$ such that $A = LU$. $L$ and $U$ are special: $L$ is a lower triangular matrix and $U$ is an upper triangular matrix. In a lower triangular matrix, all the elements above the diagonal are 0, and in an upper triangular matrix, all the elements below the digaonal are 0. For example, if we take only the $A$ part of the row echelon form of the augmented matrix $R$ (previous section), it forms an upper triangular matrix.

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 column 1 in rows 2 to $N$ $= 0$. Thus, there are $N-1$ multiplication factors in the first column after eliminating $x_1$. Similarly, we make $0$ all the elements of column 2 in rows 3 to $N$ by finding $N-2$ multiplication factors while eliminating $x_2$. The idea is to keep track of these multiplicative factors.

While we do the Gaussian elimination, let us keep track of the multiplication factors in each column in a matrix called MFM for multiplication factors matrix. In addition, we can make all its diagonal elements as 1.

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.

k = C[row,r_piv] / C[r_piv,r_piv]
C[row] = C[row] - k * C[r_piv]

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 lower triangular matrix form. This is our matrix $L$.

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 eqns.

  1. Take the augmented matrix and copy the code from Gauss Eliminate until you come to the pivot step. In this simple version, we don't find any pivot nor do we swap rows. We simply assume that the diagonal elements of matrix $A$ are not 0. If they are, rearrange the matrix yourself by hand!
  2. Create a new $N\times{N}$ matrix MFM and initialise it to an identity matrix. An identity matrix has $1$s on the diagonal and $0$s everywhere else.
  3. Make the first column as the current column and the pivot as the diagonal element in the current column.
  4. Compute the multiplication factors for the current column and store them in the corresponding elements of the MFM matrix.
  5. Repeat the above step now by incrememting the current column by $1$ until the last column.

Code skeleton is given in the cell below. The function takes a coefficient matrix $A$ as the argument and returns $L$ and $U$ matrices.

In [1]:
import numpy as np
np.set_printoptions(precision=2, suppress=True)
In [2]:
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) :        # 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
            for row in range(r_piv+1, nrows) :
                k = temp[row,r_piv] / temp[r_piv,r_piv]
                temp[row] = temp[row] - k * temp[r_piv]
                MFM[row,r_piv] = k
    
    return MFM, temp

Testing the LU decomposition code

You can test the code written above by using the $A$ matrix given in the cell below. It also prints the product of the $L$ and $U$ matrices returned by your function. Make sure $L\times{U} = A$.

In [3]:
A = np.array([[6, 5, 6, 4, 4, 7],
              [5, 8, 4, 2, 2, 4],
              [7, 7, 1, 4, 3, 2],
              [5, 4, 7, 3, 3, 1],
              [4, 7, 2, 4, 3, 6],
             [5, 6, 1, 8, 2, 7]], dtype='float')
L, U = LU_decomposition(A)
print('Verification: ')
print(L.dot(U))

Solving Problems using LU Decomposition

LU Decomposition method is used when solving problems where $A$ matrix remains the same but the different problems have different $B$ matrices.

We use the following steps to solve such problems.

  1. Use the LU_decomposition() function to get L and U matrices.
  2. The given system of equations is now written as $$ \begin{eqnarray*} AX & = & B \\ \Rightarrow (LU)X & = & B \\ \Rightarrow L(UX) & = & B \\ \Rightarrow LY & = & B ~\mbox{and}~ Y = UX \end{eqnarray*} $$
  3. First solve the equation $LY = B$ to get $Y$ values. As $L$ is a lower-triangular matrix, the first row contains only $y_1$, the second, only $y_1$ and $y_2$, and so on. Thus, $y_1, y_2, \ldots, y_N$ may be obtained by computing $y_1$ from the first row, $y_2$ from the second row and $y_1$ and so on. This procedure is called forward substitution and the code skeleton is given below.
  4. Once $Y$ is obtained, we get $X$ from the equation $UX = Y$. $U$ is an upper triangular matrix and we can use back_substitute() function written earlier to solve for $X$.

Complete the code skeleton in the cell below and do the problems given to verify that your code is working correctly. The function needs an augmented matrix as the argument.

Note that you need to paste the code for back_substitute() function from the earlier lab into the cell numbered 5 below.

In [4]:
def fwd_substitute(eqns) :
    nrows = eqns.shape[0]
    ncols = eqns.shape[1]
    sol = np.zeros((nrows,1))
    
    for var_to_compute in range(nrows) :  # 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 = eqns[eqn_num,ncols-1]                 # Step 3: Insert code
        psum = 0.0                                  # Step 4: Insert code
        psum = eqns[eqn_num,:ncols-1].dot(sol)      #
        rhs = rhs - psum                            # Step 5: Insert code
        sol[var_to_compute] = rhs / eqns[eqn_num,var_to_compute] # Step 6: Insert code
    
    return sol
In [5]:
###################################################
#     PASTE back_substitute() CODE HERE!          #
###################################################

Verifying that your code works!

Run your code by executing the following cells. You should use the $L$ and $U$ obtained earlier and do the following three steps

  1. Create an augmented matrix from $L$ and $B$.
  2. Call the fwd_substitute() function to solve $LY = B$ and get $Y$.
  3. Create an augmented matrix from $U$ and $Y$ and pass it to the function back_substitute() to solve $UX = Y$ and get $X$.
You already know the answer, don't you? In case you forgot, it is

5, 6, 0, 0, 1, 4
In [ ]:
B = np.array([[92, 91, 88, 56, 89, 91]])
print(B)
In [ ]:
C = np.concatenate((L, B.T), axis=1)
Y = fwd_substitute(C)
print(Y)
In [ ]:
print('############# ANSWER ######################')
ans = back_substitute(np.concatenate((U, Y), axis=1))
print(ans.T)


2. Iterative Jacobi Method

Jacobi is a fast iterative method for certain systems of equations. Iterative Jacobi is a very good method when the coefficient matrix $A$ is diagonally dominant. A diagonally dominant matrix is one in which the magnitude of the diagonal element in each row is equal to or greater than the sum of the magnitudes of all the other elements in that row. Jacobi method may not converge to a solution otherwise.

Iterative Jacobi method starts with an initial guess of the solution: let $X = X_0$. The update equation is given by $$ X^{t+1} = D^{-1}\left(B - A_{off}X^{t}\right) $$ Where $X^t$ is the 'solution' at iteration $t$. The matrices $A_{off}$ and $D$ are derived from the $A$ matrix. $A_{off}$ is the matrix containing only the non-diagonal elements of $A$, i.e. all the diagonal elements are $0$. $D$ is the matrix containing only the diagonal elements. Both $A_{off}$ and $D$ are square matrices with the same dimensions as $A$.

The code skeleton is given below including the creation of the two matrices $A_{off}$ and $D$. Just insert the update step to complete the function.

In [6]:
def jacobi(A, B, eps=1e-3, debug=False) :
    nrows = A.shape[0]
    ncols = A.shape[1]
    sol = np.zeros((nrows,1))
    
    # Create a square matrix with only diagonal elements of A
    D = np.diagonal(A) * np.eye(A.shape[0])
    # Remove diagonal elements from A to get A_off
    A_off = A - D
    
    change = 1.0                     # Criterion to stop iteration
    while change > eps :
        psol = sol.copy()
        ##############################################
        # Insert code here for updating the solution #
        ##############################################
        
        change = abs(sol - psol).max() # Compute the change from
                                       # previous values
        if debug :
            print('Current Soln: ', sol.T)
            
    return sol

Verifying the code

Run the following two cells to verify your code for the Iterative Jacobi method. You may note that the matrix is diagonally dominant. Also, you know the solution, don't you!

In [ ]:
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')
In [ ]:
ans = jacobi(A, B)
print(ans.T)


Exercise

  1. Solve the equation $x^5 - 100 = 0$. This is of interest in astronomy!
  2. Solve the three sets of equations $AX = B$ when $$ A = \left(\begin{array}{ccccc} -2 & -1 & 6 & -4 & 4 \\ -4 & -4 & -2 & 1 & -5 \\ 5 & -3 & 5 & 1 & -3 \\ 1 & 6 & 4 & 6 & -4 \\ 6 & 2 & -1 & -2 & 6 \end{array}\right) $$ and $B$ is given as $$ \begin{eqnarray*} B & = & \left(39, -52, 40, 76, 21\right) \\ B & = & \left(10, -45, 1, 45, 33\right) \\ B & = & \left(12, -61, -23, 29, 55\right) \end{eqnarray*} $$
  3. Solve the following system of equations using Gauss elimination $$ A = \left(\begin{array}{ccccccc} -1 & 8 & 0 & 9 & 0 & 3 & 2 \\ 4 & -3 & 3 & 7 & 7 & -2 & 0 \\ 1 & 6 & -1 & 5 & 4 & -2 & -2 \\ -3 & 7 & 6 & -3 & 0 & -3 & 0 \\ 1 & 3 & 7 & -2 &-2 & -1 & 9 \\ -1 &-3 & 3 & 7 & 9 & 5 & 5 \\ -1 & -2 & 6 & 7 & 1 & 4 & 2 \end{array}\right) $$ $$ B = \left(83, 70, 66, 20, 50, 92, 37\right) $$
  4. Solve the system using the Iterative Jacobi Method $$ \begin{eqnarray*} 8x_1 + x_2 + 8x_3 + 3x_4 & = & 42 \\ 3x_1 + 12x_2 + 6x_3 + x_4 - 2x_5 & = & 35 \\ 2x_1 + 8x_2 +17x_3 + 5x_4 - 3x_5 & = & 66 \\ 7x_1 - x_2 - 2x_3 - 13x_4 + 6x_5 & = & -83 \\ 3x_1 - 3x_2 + 8x_3 + 2x_4 -20x_5 & = & -39 \end{eqnarray*} $$
  5. Let us look at a very similar system of equations: can you solve it with Jacobi? $$ \begin{eqnarray*} 3x_1 + x_2 + 8x_3 + 3x_4 & = & 37 \\ 3x_1 + 2x_2 + 6x_3 + x_4 - 2x_5 & = & 15 \\ 2x_1 + 8x_2 +7x_3 + 5x_4 - 3x_5 & = & 56 \\ 7x_1 - x_2 - 2x_3 - 3x_4 + 6x_5 & = & - 3 \\ 3x_1 - 3x_2 + 8x_3 + 2x_4 -2x_5 & = & -15 \end{eqnarray*} $$ What happened?

    Solve it using Gauss Elimination. Did it work?

  6. Find at least one root of $$4x^5 - 3x^2 + 14x - 26 = 0$$
  7. (For Systems and Computational Biology Students) A typical stoichiometric representation of the aerobic growth of a microorganism with a defined chemical formula is shown below $$ \underbrace{\mathrm{C_2H_2OH}}_{C-source} + a\,\mathrm{RQ} + \underbrace{b\,\mathrm{NH_3}}_{N-source} \rightarrow \underbrace{c\,\mathrm{CH_{1.7}N_{0.15}O_{0.4}}}_{Micro-organism} + d{H_2O} $$ where $RQ$ is the respiratory quotient given by ratio of moles of $CO_2$ produced per mole of $O_2$ consumed. The problem is to identify the stoichiometric coefficients $a, b, c$ and $d$ from the following system of equations: $$ \left(\begin{array}{cccc} RQ & 0 & 1 & 0 \\ 0 & -3 & 1.7 & 2 \\ -2 & 0 & 0.4 & 1 \\ 0 & -1 & 0.15& 0 \end{array}\right) \left(\begin{array}{c} a \\ b \\ c \\ d \end{array}\right) = \left(\begin{array}{c} 2 \\ 6 \\ 1 - 2RQ \\ 0 \end{array}\right) $$ where $RQ = 0.2$ in the experiment.


END