Numerical Methods To Solve Systems of Equations in Python
Numerical Methods To Solve Systems of Equations in Python
Numerical Methods To Solve Systems of Equations in Python
Contents
Introduction: ................................................................................................................................................. 1
Conjugate gradient method .......................................................................................................................... 2
Definitions and methodology ................................................................................................................... 2
Python code .............................................................................................................................................. 2
Jacobi method ............................................................................................................................................... 3
Methodology............................................................................................................................................. 3
Python code .............................................................................................................................................. 4
Jacobi method for diagonal dominant matrix .............................................................................................. 5
Definitions and methodology ................................................................................................................... 5
Python code .............................................................................................................................................. 5
Gauss Seidel Method .................................................................................................................................... 7
Methodology............................................................................................................................................. 7
Python code .............................................................................................................................................. 7
Jacobi relaxation method............................................................................................................................ 11
Methodology........................................................................................................................................... 11
Python code ............................................................................................................................................ 11
We will make frequent use of the libraries numpy.linalg and numpy (for using norm, dot operator, inv,
transpose).
Introduction:
All the 5 methods presented below are iterative methods.
When for the system 𝐴𝑥 = 𝑏, the matrix 𝐴 is not symmetric and pos. definite we can turn this system
into 𝐴𝑡 𝐴𝑥 = 𝐴𝑡 𝑏 and it becomes one.
Hence most of the algorithms presented below are for symmetric matrices (except for Jacobi relaxed
method).
Also the norm I will be working with in considering the error terms will be the 2-norm, induced by the
classical dot product between two vectors, or between a matrix and a vector.
Conjugate gradient method
Definitions and methodology
1. We say that two directions 𝑢, 𝑣 are conjugate over A if < 𝑢, 𝐴𝑣 > = 0 where < 𝑢, 𝑣 > is the scalar
product of two vectors.
2. Problem:
Suppose we want to solve the system 𝐴𝑥 = 𝑏 where 𝐴 is positive definite,real and symmetric.
The bilinear form < 𝑢, 𝑣 >𝐴 =< 𝑢, 𝐴𝑣 > defines a scalar product over 𝑅 𝑛 and if 𝑃 = {𝑝1 , … , 𝑝𝑛 } forms a
basis over 𝑅 𝑛 then we may express the solution 𝑥 ∗ = ∑𝑛𝑖=1 𝛼𝑖 𝑝𝑖 .
𝑖=1
𝑛
This gives the following method for solving the equation Ax = b: find a sequence of n conjugate
directions, and then compute the coefficients 𝛼𝑘 .
Python code
First I am testing the functionality of the aforementioned libraries.
def tests_norms():
def test1():
A = np.array([[1,2],[2,3]])
print(np.linalg.norm(A))
"Computes Frobenius norm"
print(np.sqrt(1**2+2**2+3**2+4**2))
print(np.linalg.norm(A,2))
"Computes 2-norm"
test1()
def test2():
"For inverse of a matrix one can use directly the linalg package "
"inv member"
A = np.array([[1,2],[2,3]])
print(np.linalg.inv(A))
Now we get on with our algorithms and tests.
def tests_CGM():
def test(A,b,x0):
"I test the first step of the Conjugate Gradient Method"
r0 = b-np.dot(A,x0)
p0 = r0
alpha0 = np.dot(p0,r0)/np.dot(p0,np.dot(A,p0))
x= x0
x = x + alpha0*p0
print(x)
def ttest():
A = np.array([[2,1],[1,4]])
b = np.array([1,2])
x0 = np.array([1,1])
test(A,b,x0)
ttest()
def CGM(A,b,x0,tol):
err,y = 1,x0
r = b-np.dot(A,x0)
p=r
i=0
while (err>tol):
alpha = np.dot(p,r)/np.dot(p,np.dot(A,p))
z = y + alpha * p
r = r-alpha * np.dot(A,p)
beta = -np.dot(r,np.dot(A,p))/np.dot(p,np.dot(A,p))
p = r+beta * p
i=i+1
err = np.dot(z-y,z-y) #squared 2-norm of z-y
y=z
return y,err,i
def ttest3():
A = np.array([[10,7,8,7],[7,5,6,5],[8,6,10,9],[7,5,9,10]])
b = np.array([32,23,33,31])
x0 = np.zeros(4)
print(CGM(A,b,x0,10**(-10)))
Remark:
The errors are computed under the 2-norms.
Jacobi method
Methodology
We again seek to solve 𝐴𝑥 = 𝑏, 𝐴 ∈ 𝑀𝑚 (𝐶), 𝑏 ∈ 𝐶 𝑚 .
We consider 𝐵 = 𝐼 − 𝐴 so the system becomes (𝐼 − 𝐵)𝑥 = 𝑏 ⇒ 𝑥 = 𝐵𝑥 + 𝑏.
For any 𝑥 (0) ∈ 𝐶 𝑚 we define the sequence (𝑥 (𝑛) )𝑛∈𝑁 through the relation 𝑥 (𝑛+1) = 𝐵𝑥 (𝑛) + 𝑏, ∀𝑛 ∈ 𝑁.
Python code
def jacobi_methods(num):
def spectral_radius(A):
eig = np.linalg.eig(A)[0]
abs_eig = np.abs(eig)
return np.max(abs_eig)
def spectral_radius2(A):
"spectral radius of I-A"
B = np.identity(np.shape(A)[0])-A
return spectral_radius(B)
def jacobi_method(A,b,x0,tol):
"A = square matrix,b=the free term col.vector, x0 = initial guess "
sz = np.shape(A)
if sz[0]!=len(b):
raise IndexError('the number of rows of A and length of b are not\
compatible')
x,i,err,B = x0,0,1,np.identity(np.shape(A)[0])-A
if spectral_radius(B)>=1:
print('The method is not convergent')
return False
else:
while (err>=tol and i<30):
z = b+np.dot(B,x)
i = i+1
err = np.dot(z-x,z-x)
x=z
return x,err,i
def test_jacobi():
A = np.array([[0.2,0.3],[0.1,0.2]])
b = np.array([0.2,0.3])
x0 = np.array([0.1,0.2])
tol = 0.01 * np.exp(-5)
print(jacobi_method(A,b,x0,tol))
def test_matrix():
A = np.array([[0.2,0.3],[0.1,0.2]])
B = np.identity(np.shape(A)[0])-A
print(spectral_radius(B))
#test_matrix() -- Uncomment these to be able to test the functions
#test_jacobi() -- from outside
dic = {1:spectral_radius,2:spectral_radius2,3:jacobi_method}
return dic[num]
Jacobi method for diagonal dominant matrix
Definitions and methodology
Definition1:
We say that the matrix 𝐴 = (𝑎𝑖𝑗 )1≤𝑖,𝑗≤𝑚 is diagonal dominant on lines if and only if |𝑎𝑖𝑖 | >
∑𝑚
𝑗=1,𝑗≠𝑖|𝑎𝑖𝑗 |, ∀1 ≤ 𝑖 ≤ 𝑚.
Similarly, 𝐴 = (𝑎𝑖𝑗 )1≤𝑖,𝑗≤𝑚 is diagonal dominant on columns if and only if |𝑎𝑗𝑗 | > ∑𝑚
𝑖=1,𝑖≠𝑗|𝑎𝑖𝑗 |, ∀1 ≤ 𝑗 ≤
𝑚.
Method of solving the system:
Python code
Python code for testing the dominance of a matrix.
def test_dominance():
A = np.array([[1,0,0,0.5],[1,2,0.3,0.6],[0,1,3,1.5],[0,1,1,-3]])
print(A)
print(tests(1)(A))
print(tests(2)(A))
print(1/2*np.sum(np.abs(A[3])))
print(np.abs(A[3,3]))
print(np.abs(A[3,3])<=1/2*np.sum(np.abs(A[3])))
Now I get to the core algorithm.
def jacobi_functions():
def jacobi_matrix1(A,b):
"creates the matrix B = I - D^(-1)*A, and the new vector b=D^(-1)*A"
"for diagonal dominant matrix on rows"
D = np.diag(np.diag(A))
return np.identity(np.shape(A)[0]) - np.dot(np.linalg.inv(D),A),\
np.dot(np.linalg.inv(D),b)
def jacobi_matrix2(A):
"creates the matrix B = I - A*D^(-1)"
"in case of diagonal dominant matrix on columns"
D = np.diag(np.diag(A))
B = np.identity(np.shape(A)[0]) - np.dot(A,np.linalg.inv(D))
return B
def test_jacobi_matrix():
A = np.array([[1,2,3],[1,2,0],[0,1,1]])
b = np.array([1,1,1])
print(jacobi_matrix1(A,b))
print(jacobi_matrix2(A))
#test_jacobi_matrix()
def test_jacobi_matrix2():
h2 = tests(1)
A = np.array([[1,0,0,0],[0,2,0,0],[0,0,3,0],[0,0,0,4]])
B = np.diag(([1,2,3,4]))
print(A)
print(B)
print(h2(A)==1)
#test_jacobi_matrix2()
def jacobi_method(A,x0,a,tol):
sz = np.shape(A)[0]
if sz!=len(x0):
raise IndexError('The dimensions of A and x0 are not compatible')
h2,h3 = tests(1),tests(2)
if h2(A)==1:
"If A is dominant on rows"
B,b = jacobi_matrix1(A,a)
x,err,i = x0,1,0
while(err>=tol and i<30):
z = np.dot(B,x) + b
err = np.dot(z - x,z-x)
i = i+1
x=z
return x,err,i
elif h3(A)==1:
"If A is dominant on cols"
B = jacobi_matrix2(A)
x,err,i = x0,1,0
y = np.diag(np.diag(A))*x
while(err>=tol and i<30):
z = np.dot(B,y) + a
err = np.dot(z-y,z-y)
i = i+1
y=z
return x,err,i
else:
raise ValueError('You better try another method for solving the system')
def test_jacobi_method():
A = np.diag([2,3,2,4])
b = np.array([1,2,3,4])
x0 = np.ones(4)
print(jacobi_method(A,x0,b,10**(-4)))
test_jacobi_method()
Hence we can define, for 𝑥 (0) ∈ 𝐶 𝑚 ,the seq. 𝑥 (𝑛+1) = 𝐶𝑥 (𝑛) + 𝑐 ⇔ 𝑥 (𝑛+1) = 𝐿𝑥 (𝑛+1) + 𝑅𝑥 (𝑛) +
𝑏, ∀𝑛 ∈ 𝑁.
Python code
"Gauss Seidel method"
import numpy as np
def tests():
def test1():
l = list([[(i,j) for j in range(4)] for i in range(4)])
print(l)
m = list([x if x[0]>x[1] else 0 for x in l])
print(m)
def test2():
A = np.random.normal(0,1,(4,4))
B,C = np.zeros((4,4)),np.zeros((4,4))
for i in range(1,4):
for j in range(i):
B[i,j] = A[i,j]
for i in range(0,4):
for k in range(i,4):
C[i,k] = A[i,k]
print(A)
print(B)
print(C)
print(np.zeros((4,4)))
test2()
def decomposition(A):
"A = L + R, where L contains the matrix A under its main diagonal"
sz = np.shape(A)[0]
L,R = np.zeros((sz,sz)),np.zeros((sz,sz))
for i in range(1,sz):
for j in range(i):
L[i,j] = A[i,j]
for i in range(0,sz):
for k in range(i,sz):
R[i,k]=A[i,k]
return L,R
def test_decomposition():
A = np.random.normal(0,1,(5,5))
print(A)
print(decomposition(A)[0])
print(decomposition(A)[1])
def gauss_seidel_method(A,b,x0,tol):
if np.linalg.det(A)==0:
raise Exception("The matrix is singular")
else:
sz = np.shape(A)[0]
B = np.identity(sz) - A
L,R = decomposition(B)
K = np.identity(sz) - L
C = np.dot(np.linalg.inv(K),R)
c = np.dot(np.linalg.inv(K),b)
x,err,i= x0,1,0
while (err>=tol and i<30):
z = np.dot(C,x)+c
err = np.dot(z-x,z-x)
i=i+1
x=z
return x,err,i
def test_gauss_seidel_method():
A1,A2 = np.diag([1,2,3,4]),np.diag([1,2,3,4])
b = np.ones(4)
x0 = np.zeros(4)
A1[3,0],A1[0,3] = 2,2
print(gauss_seidel_method(A2,b,x0,0.000001))
#print(gauss_seidel_method(A1,b,x0,0.000001))
"We see that for diagonal matrices the method is not always converging"
"I will need to import from Jacobi the jacobi_methods(num) function containing"
"spectral radius function"
def gs_spectral_radius(A):
B=np.identity(np.shape(A)[0])-A
L,R = decomposition(B)
K = np.identity(np.shape(A)[0])-L
C = np.dot(np.linalg.inv(K),R)
return jacobi_methods(1)(C)
def test_spectral_radius():
A = np.array([[1,2,-2],[1,1,1],[2,2,1]])
print(gs_spectral_radius(A))
B = np.diag([1,2,3,4])
print(gs_spectral_radius(B))
def gauss_seidel_method2(A,b,x0,tol):
"An improved version, which abandonds the task if the spectral radius "
" of the matrix C previously defined is greater or equal than 1"
sz = np.shape(A)[0]
B = np.identity(sz)-A
L,R = decomposition(B)
K = np.identity(sz) - L
C = np.dot(np.linalg.inv(K),R)
if np.linalg.det(A)==0:
raise Exception("The matrix is singular")
elif gs_spectral_radius(C)>=1:
raise Exception("The method will not converge, the spectral radius is >=1")
else:
c = np.dot(np.linalg.inv(K),b)
x,err,i = x0,1,0
while (err>=tol and i<30):
z=np.dot(C,x)+c
err = np.dot(z-x,z-x)
i = i+1
x=z
return x,err,i
def test_gauss_seidel_method2():
A1,b,x0 = np.diag([1,2,3,4]), np.ones(4),np.zeros(4)
print(gauss_seidel_method2(A1,b,x0,0.00001))
def norm_inf_gauss_tests(num):
def modified_ones(q,n):
"q is a vector of length that must be inferior to n"
"the result would be [q1,q2,...,qk,1,1,..,1]"
z = np.ones(n)
for i in range(len(q)):
z[i] = q[i]
return z
def test1():
q = [2.1,2.2,2.3]
x = modified_ones(q,5)
print(x)
def norm_inf(B):
"Cumulative scalar product"
"q1 = sum of elements of B[0],q2 = <(q1,1,1...,1),(B[1,0],...,B[1,m])>"
"q3 = <(q1,q2,1,1,...,1),(B[2,0],B[2,1],...,B[2,m])>"
sz = np.shape(B)[0]
q=[]
q.append(np.dot(np.ones(sz),B[0]))
for i in range(1,sz):
aux = q[0:i]
q.append(np.dot(modified_ones(aux,np.shape(B)[0]),B[i]))
return q
def test2():
B = np.array([[1,2],[3,-1]])
print(norm_inf(B))
"should return 3.0 and 8.0 if it is correctly implemented"
#test2()
def norm_inf_gauss(A):
"the return of the function will specify if Gauss-Seidel is applicable"
"If the maximum of the q's is <1 then Gauss-Seidel converges otherwise no"
B = np.identity(np.shape(A)[0]) - A
q = norm_inf(np.abs(B))
return q
dic = {1:modified_ones,2:norm_inf,3:norm_inf_gauss}
return dic[num]
Remark (Alternative sufficient condition for convergence):
Regarding the last function norm_inf_gauss I have to take into consideration the following:
Considering 𝑞1 = ∑𝑚 𝑖−1 𝑚
𝑗=1|𝑏1𝑗 |, 𝑞𝑖 = ∑𝑗=1|𝑏𝑖𝑗 |𝑞𝑗 + ∑𝑗=𝑖|𝑏𝑖𝑗 |, 2 ≤ 𝑖 ≤ 𝑚, 𝑞 = max 𝑞𝑖 , if 𝑞 < 1 then we
1≤𝑞≤𝑚
(𝑛) 𝑞 𝑞𝑛
have the following evaluations: ||𝑥 − 𝑥|| ≤ 1−𝑞
||𝑥 (𝑛) − 𝑥((𝑛 − 1))|| ≤ 1−𝑞 ||𝑥 (1) − 𝑥 (0) || .
∞ ∞ ∞
Python code
"Simultaneous Relaxation method"
import numpy as np
def relaxed_method():
def issymetric(A):
if np.all(np.transpose(A)==A):
return 1
else:
return 0
def is_pos_def(A):
if np.all(np.linalg.eig(A)[0]>0) and issymetric(A)==1:
return 1
else:
return 0
def opt_relax_param(A):
"Optimal relaxation parameter.Interest on positive-definite and "
"symmetric matrices"
eig = np.linalg.eig(A)[0]
m,M = np.min(eig),np.max(eig)
return 2/(m+M)
def chg_var(A,a):
"change of variables in relaxed method"
sz = np.shape(A)[0]
sigma = opt_relax_param(A)
D = np.diag(np.diag(A))
C = np.identity(sz) - sigma * np.dot(np.linalg.inv(D),A)
c = sigma*np.dot(np.linalg.inv(D),a)
return C,c
def test():
A = np.array([[5,3,2],[3,6,3],[2,3,5]])
a= np.array([1,2,3])
print(chg_var(A,a))
#test()
def solution1(A,a,x0,tol,n):
"n is the number of iterations"
"I assume matrix A fullfils all necessary conditions"
x,err,i=x0,1,0
C,c = chg_var(A,a)
while (err>=tol and i<n):
z = np.dot(C,x) + c
err = np.dot(z-x,z-x)
i=i+1
x=z
return x,err,i
def solution(A,a,x0,tol,n):
"The matrix might not be symmetric so I transform the system into "
" A^tAx=A^ta"
if issymetric(A)!=1 or is_pos_def(A)!=1:
return solution1(np.dot(np.transpose(A),A),np.dot(np.transpose(A),a),x0,tol,n)
else:
return solution1(A,a,x0,tol,n)
def test_sol():
A = np.array([[2,-1],[1,3]])
a = np.array([1,2])
x0,tol,n = np.array([1,1]),0.0000000001,50
print(solution(A,a,x0,tol,n))
test_sol()