Professional Documents
Culture Documents
Report 2 Doleduy
Report 2 Doleduy
Report 2
DO LE DUY・ 1026322038
ECS-ID: a0192284
Introduction
We use Python with Jupyter Notebook to solve the tasks this report. The program is edited and ran on Visual Studio Code. We
only use matplotlib for general plotting; otherwise, no other third-party numerical functions is used.
In [ ]:
import matplotlib.pyplot as plt
import copy
In [ ]:
import sys
EPS_env = sys.float_info.epsilon
print ("The EPS value is: ", EPS_env)
In [ ]:
def mul_mat_vec(A, x):
rs = len(A)
cs = len(A[0])
y = [0]*rs
for i in range(rs):
for j in range(cs):
y[i] += A[i][j] * x[j]
return y
def transpose(A):
return list(zip(*A))
def norm(v):
import math
val = 0
for i in range(len(v)):
val += v[i]**2
return math.sqrt(val)
return transpose(C_T)
The constants used in the tasks are defined in the snippet below:
In [ ]:
A = '''12 1 5 1 1 2 -4 1 2
1 16 -1 -4 -5 -2 1 2 3
5 -1 15 -5 3 1 -2 1 -4
1 -4 -5 10 3 -3 -1 4 1
1 -5 3 3 11 -1 4 1 1
2 -2 1 -3 -1 15 -5 2 5
-4 1 -2 -1 4 -5 15 4 -4
1 2 1 4 1 2 4 11 -1
2 3 -4 1 1 5 -4 -1 15'''
alpha1 = [1.0]*9
alpha2 = [1e10]*9
alpha3 = [1e-10]*9
b1 = mul_mat_vec(A,alpha1)
b2 = mul_mat_vec(A,alpha2)
b3 = mul_mat_vec(A,alpha3)
Task 2.1
Task 2.1.1
This task requires to solve the linear system A ⋅ x = b using LU decomposition. The first step in this process is using Crout's
Method to find the L and U matrices such that A = LU , L is lower triangular and U is unit upper triangular. The defining
equations for Crout's Method are
i−1
k=1
and
i−1
aij − ∑ lik ukj
k=1
uij = , where i < j
lii
Then:
A ⋅ x = (L ⋅ U )x = L ⋅ (U x) = b
L ⋅ y = b
U ⋅ x = y
We will forward substitution to solve for U x and then backward substitution x. The functions are defined as follow:
In [ ]:
def crout(A):
n = len(A)
L = [[0.0]*n for i in range(n)]
U = [[0.0]*n for i in range(n)]
for i in range(n):
U[i][i] = 1.0
return x
Ax1 = b1
Using the process detail before, we could determine the vector x to be: 1
In [ ]:
L, U = crout(A)
x1 = LU_result1 = LU_sys_solver(L, U, b1)
print(x1)
In [ ]:
print([x1[i] - alpha1[i] for i in range(len(x1))])
Task 2.1.2
This task requires to use iterative methods to solve the linear system. The three iterative methods to be implement are Jacobi
Method, Gauss–Seidel Method, and SOR(successive over relaxation) Method.
Jacobi Method
(k+1) −1 (k)
x = D (b − (L + U )x ).
(k+1) 1 ⎛ (k)
⎞
x = bi − ∑ aij x , i = 1, 2, … , n.
i j
aii ⎝ ⎠
j≠i
We implemented the The Jacobi Method and applied it to solve the task as follow:
In [ ]:
def jacobi(A, b, alpha, EPS = 1e-10, N = 10000, verbose = True):
# Initialize matrices, and vectors
rs = len(A)
D = [0.0] * rs
R = copy.deepcopy(A)
for i in range(rs):
D[i] = A[i][i]
R[i][i] = 0.0
x = [0.0] * rs
x_temp = [0.0] * rs
Gauss–Seidel Method
i−1 n
(k+1) 1 (k+1) (k)
x = (bi − ∑ aij x − ∑ aij x )
i j j
aii
j=1 j=i+1
We will implement the Gauss–Seidel Method based on the above equation and applied it to solve the linear system:
In [ ]:
def ga_se(A, b, alpha, EPS = 1e-10, N = 10000, verbose = True):
# Initialize matrices, and vectors
rs = len(A)
D = [0.0] * rs
R = copy.deepcopy(A)
for i in range(rs):
D[i] = A[i][i]
R[i][i] = 0.0
x = [0.0] * rs
SOR Method
Similarly, the defining element-based equation for SOR Method is shown below:
In [ ]:
def sor(A, b, w, alpha, EPS = 1e-10, N = 10000, verbose = True):
# Initialize matrices, and vectors
rs = len(A)
D = [0.0] * rs
R = copy.deepcopy(A)
for i in range(rs):
D[i] = A[i][i]
R[i][i] = 0.0
x = [0.0] * rs
x_temp = [0.0] * rs
By studying the plots of residal norm over iterations, we could conclude that the error converges linearly as the difference
decreases exponentially over time.
Task 2.1.3
The error norm also decrease exponentially i.e converge linearly, and with the same rate as residal norm. As a result, the error
norm is always proportional to the residal norm after sufficient time has passed. We could conclude that for this problem, we
could reliably increase the accuracy of the solution by decreasing the condition for residal norm.
Task 2.1.4
Conjugate gradient method
The conjugate gradient method is an algorithm for solving Ax = b where A is a real, symmetric, positive-definite matrix. We
input an approximate initial solution vector x .
0
r0 := b − Ax0
p0 := r0
k := 0
repeat
⊤
r rk
k
αk :=
⊤
p Apk
k
xk+1 := xk + αk pk
rk+1 := rk − αk Apk
⊤
r rk+1
k+1
βk :=
⊤
r rk
k
pk+1 := rk+1 + βk pk
k := k + 1
end repeat
The algorithm is implemented as the subroutine linear_cg . We confirm the correctness of the subroutine by solving for the
solution of Ax 1
= b1 .
In [ ]:
def linear_cg(A, b, e = 1e-10, N = 1000):
n = len(A)
xk = [0.0] * n
rk = [mul_mat_vec(A, xk)[i] - b[i] for i in range(n)]
pk = [-r for r in rk]
num_iter = 0
x = [xk]
for iter in range(N):
apk = mul_mat_vec(A, pk)
rkrk = dot(rk, rk)
num_iter += 1
x.append(xk)
print('Iteration: {}; x = {}; residual = {}'.format(num_iter, xk, norm(rk)))
if norm(rk) < e:
break
return x
We will quickly confirm if the change Δx after the kth iteration for the first 9 iteration is orthogonal to x with the snippet
k k
below:
In [ ]:
check_orth = [] # dot product of Delta x_k and x_k
for i in range(0, 9):
check_orth.append(
dot([x[i+1][j] - x[i][j] for j in range(len(x[i]))], x[i]) )
print(check_orth)
Task 2.2
Task 2.2.1
We use the function LU_sys_solver to solve for the solution of two prolems Ax 2 = b2 and Ax 3 = b3 . We also find the error
vectors of the two solutions x and x by comparing with the true solution α and α . The programs and results are shown
2 3 1 2
below:
In [ ]:
# Solving for Ax2 = b2
x2 = LU_sys_solver(L, U, b2)
In [ ]:
# Solving for Ax3 = b3
x3 = LU_sys_solver(L, U, b3)
Task 2.2.2
We solve the problem Ax 2 = b2 and Ax 3 = b3 with iteration methods as follow:
In [ ]:
# Solve Ax2 = b2
With true solution being much larger, the stopping condition before could not be satisfied. The residal norm and error norm still
follow the same trend, as the error norm is always larger than the residal norm.
To have the methods converge with the same number of iterations as when the true solution is α , we increase the EPS with the
1
In [ ]:
jacobi_result2 = jacobi(A, b2, alpha2, EPS=1)
ga_se_result2 = ga_se(A, b2, alpha2, EPS=1)
sor_result2 = sor(A, b2, 1.5, alpha2, EPS=1)
In [ ]:
# Solve Ax2 = b2
Iter count: 5
Solution: [1.1372609791556192e-10, 7.106843444617866e-11, 4.301136802719157e-11, -2.086449420041737e-12, 1.38044
3040580933e-10, 5.225468869901263e-11, 4.6143830511130064e-11, 1.7114612293138368e-10, 9.932520798545972e-11]
Iter count: 5
Solution: [1.0438763710531638e-10, 9.760438668771425e-11, 8.709176688022779e-11, 8.252579887288617e-11, 1.097842
0847132347e-10, 9.37897809163082e-11, 8.964807713650862e-11, 1.1111487993368873e-10, 9.701512732007388e-11]
Iter count: 9
Solution: [8.55424714210626e-11, 1.2660170589580515e-10, 1.5308160895207729e-10, 1.9145600312007618e-10, 6.66175
8780508508e-11, 1.4231453750933576e-10, 1.473041747832121e-10, 3.682827764786573e-11, 1.0158445822959387e-10]
With true solution becoming much smaller, the routine quickly reach the stopping condition, however the error is very high. As
there are only a few iteration, not much could be observed about the change of residal and error norm.
To have the methods converge with the same number of iterations as when the true solution is α and decrease the error, we
1
In [ ]:
jacobi_result3 = jacobi(A, b3, alpha3, EPS=1e-20)
ga_se_result3 = ga_se(A, b3, alpha3, EPS=1e-20)
sor_result3 = sor(A, b3, 1.5, alpha3, EPS=1e-20)
Task 2.2.3
From the element-based equation of the three iteration methods, we speculate that the magnitude of solution could be
approximated by the value of b and a . To minimize error, we want to account for the element that has the smallest magnitude.
ii
min(b)
EPS0
max(aii )
In [ ]:
# Solve Ax2 = b2
D = [0.0]*len(A)
for i in range(len(A)):
D[i] = A[i][i]
In [ ]:
# Solve Ax3 = b3
D = [0.0]*len(A)
for i in range(len(A)):
D[i] = A[i][i]
Task 2.3
Task 2.3.1
Consider an n × n matrix A that has n linearly independent real eigenvalues such that |λ 1| ≥ |λ2 | ≥ ⋯ ≥ |λn | and
corresponding eigenvector v .
i
= c1 λ1 v 1 + c2 λ2 v 2 + ⋯ + cn λn v n
c2 λ2 cn λn
= c1 λ1 [v1 + v2 + ⋯ + v n ] = c1 λ1 x 1
c1 λ1 c1 λ1
And, similarly:
2 2
c2 λ2 cn λn
Ax1 = λ1 v1 + v2 + ⋯ + vn
c1 λ1 c1 λ1
2
2
c2 λ2 cn λn
= λ1 [v1 + v2 + ⋯ + v n ] = λ1 x2
c1 λ2 c1 λ
2
1 1
As λ is the largest eigenvalue, therefore, the ratio for all i . Thus when we repeat multiplying A for sufficiently large
λi
1 < 1 > 1
λ1
amount of times, all the terms that contain will approach zero. Then, the equation could be simplified to be:
λi
λ1
Axk = λ1 v1
To avoid large value, we also need to apply normalization after each iteration, which is usually done by factoring out the largest
element in the vector, resulting in the largest element in the vector becoming 1. As a result, after convergence, the factoring out
number will equal the largest eigenvalue and the resulting vector is the corresponding eigenvector.
We will set the stopping condition to assess convergence to be the norm of the residual vector being sufficiently small.
Based on the explaination above, we will implement the power method as follow:
In [ ]:
def power(A, N = 1000, e = 1e-10):
x = [1.0]*len(A)
m = 0
for iter in range(N):
x = mul_mat_vec(A, x)
m_old = m
m = max([comp for comp in x], key=abs)
x = [comp * 1.0/m for comp in x]
if abs(m_old - m) < e:
break
return m, x, iter
k+1 −1 k k+1 k
x = A x ⟺ Ax = x
As a replacement to multiple the inverse matrix, we will solve the linear equation Ax k+1
= x
k
. The inverse power method is
implemented and applied as follow:
In [ ]:
def inverse_power(L, U, N = 1000, e = 1e-10):
x = [1.0]*len(L)
m = 0
for iter in range(N):
x = LU_sys_solver(L, U, x)
m_old = m
m = max([comp for comp in x], key=abs)
x = [comp * 1.0/m for comp in x]
if abs(m_old - m) < e:
break
return m, x, iter
N = 80
La, Ua = crout(A)
ma2, xa2, itera2 = inverse_power(La, Ua, N)
print("Smallest Eigenvalue: {} after {} iter".format(1/ma2, itera2))
print("Condition Number:", ma1*ma2)
Task 2.3.2
The routine gen_sym_mat generate a symmetric matrix with n rows and n columns and elements ranging from 0 to 100. We will
create the symmetrix matrix B with the routine.
In [ ]:
import random
def gen_sym_mat(n):
AS = [[0.0] * n for r in range(n)]
for r in range(n):
for c in range(r, n):
AS[r][c] = random.randint(0,100)
AS[c][r] = AS[r][c]
return AS
B = gen_sym_mat(6)
B
In [ ]:
mb1, xb1, iterb1 = power(B, N)
Lb, Ub = crout(B)
mb2, xb2, iterb2 = inverse_power(Lb, Ub, N)
Task 2.3.3
In a iteration k of the QR method to find eigenvalues of a matrix A, we first use Gram Schmidt to find the QR decomposition of
matrix A , getting Q - an orthogonal matrix and R - an upper triangular matrix. Then we fine the matrix A
k k k k+1
= R k Qk and
continue to the next iteration. As k increases, the matrix A will finally converge to an upper triangular matr form, the diagonal
i
However, as
−1 −1
Ak+1 = Rk Qk = Q Qk R k Qk = Q A k Qk
k k
Ak and A k+1
, and consequently all A are similar and have similar eigenvalues.
i
We will implement Gram Schmidt Method to find the QR decomposition of A and find all eigenvalues of A. In this section we will
the class numpy.array for faster acces to the column vector.
In [ ]:
import numpy as np
def gram_schmidt(A):
u[:, 0] = A[:, 0]
Q[:, 0] = u[:, 0] / norm(u[:, 0])
u[:, i] = A[:, i]
for j in range(i):
u[:, i] -= dot(A[:, i], Q[:, j]) * Q[:, j] # get each u vector
R = np.zeros((n, m))
for i in range(n):
for j in range(i, m):
R[i, j] = dot(A[:, j] , Q[:, i])
return Q, R
A_ = A
for i in range(40):
q, r = gram_schmidt(np.array(A_))
A_ = mul_mat_mat(r, q)
print("Eigenvalues: ")
for i in range(len(A_)):
print(A_[i][i])
Eigenvalues:
26.628501077264783
22.324107306017662
20.498455520616453
16.56935069903095
13.5118585429233
11.736032639984458
5.712657306865459
2.9800062417161004
0.03903066558110365