Additional links:

<< Previous lecture | Next lecture >>


Introduction

In the following we want to study how to use numpy and scipy to solve Linear Systems with Python. Most of the functions in numpy and scipy for Linear Algebra are contained in the sub-packages np.linalg and scipy.linalg, as you will see in the following.

To fix the notation, given a matrix and a vector , solving a linear system means finding (when exists) a vector such that it solves

This is not hard to do in numpy, since it implements a function np.linalg.solve, taking as input a 2-dimensional array A and a 1-dimensional array y, and returns the solution x to the linear system. In particular:

# Define the problem
A = np.array([[1, 1, 1], [2, 1, 2], [0, 0, 1]])
y = np.array([0, 1, 0])
 
# Solve the system
x_sol = np.linalg.solve(A, y)
print(f"The solution is {x_sol}.")

Testing the accuracy

When the matrix is ill-conditioned, the solution of a linear system won’t be correct, since the small perturbations on introduced by the floating point system will be amplified and the corresponding solution will be dramatically distant to the true solution. To check how accurate our computed solution is to the true solution of the system (i.e. to quantify the amplification of the perturbation on the data), it is common to use the relative error, which is defined as

Clearly, the problem is that if our algorithm fails in recovering the true solution due to the ill-conditioning of the system matrix , how can we compute the true solution , required to compute ? The solution is to build a test problem.

Creating a Test Problem

Consider a matrix and assume we want to test the accuracy of an algorithm solving systems involving . Fix an -dimensional vector , and compute . Clearly, this procedure defines a linear system

of which we know that is a solution, since we built the term accordingly. Now, when we apply our algorithm to that linear system, we get a solution , approximately solving . Given that, we can always compute the relative error associated to the solution obtained by the algorithm. Using numpy, this can be simply done as

import numpy as np
 
# Setting up the dimension
n = 10
 
# Creating the test problem
A = np.random.randn(n, n) # n x n random matrix
x_true = np.ones((n, ))   # n-dimensional vector of ones
 
y = A @ x_true # Compute the term y s.t. x_true is a sol.
 
# Solving the system with numpy
x_sol = np.solve(A, y)
 
# Computing the accuracy
E_rel = np.linalg.norm(x_true - x_sol, 2) / np.linalg.norm(x_true, 2)
print(f"The relative error is {E_rel}")

Condition number

You should already know that the conditioning of an matrix can be quantified by a term called condition number which, whenever is invertible, is defined as

where identifies the norm with respect to which the condition number is computed (hence the term -condition number).

An invertible matrix is said to be ill-conditioned when its condition number grows exponentially with the dimension of the problem, .

The condition number is related to the accuracy of the computed solution of a linear system by the following inequality

which implies that the relative error on the computed solution is big whenever is big. Moreover, note that as a consequence of the formula above, the accuracy of a computed solution is partially a property of the condition number of itself, meaning that no algorithm is able to compute an accurate solution to an ill-conditioned system.

Computing the -condition number of a matrix in numpy is trivial: just using the function np.linalg.cond(A, p) to compute does the job.

Solving Linear System by Matrix Splitting

As you should know, when the matrix is unstructured, the linear system can be efficiently solved by using LU Decomposition. In particular, with Gaussian elimination algorithm, one can factorize any non-singular matrix into:

where is a lower-triangular matrix, is an upper-triangular matrix with all ones on the diagonal and is a permutation matrix (i.e. a matrix obtained by permutating the rows of the identity matrix). If the decomposition is computed without pivoting, the permutation matrix equals the identity. Note that the assumption that is non-singular is not restrictive, since it is a necessary condition for the solvability of .

Since is an orthogonal matrix, , thus

Since linear systems of the form

can be efficiently solved by the Forward (Backward) substitution, and the computation of the LU factorization by Gaussian elimination is pretty fast ( floating point operations), we can use that to solve the former linear system. Indeed,

then, by Forward-Backward substitution, this system can be solved by subsequently solve

whose solution is a solution for .

Even if this procedure is automatically performed by the np.linalg.solve function, we can unroll it with the functions scipy.linalg.lu(A) and scipy.linalg.solve_triangular(A, b), whose documentation can be found here and here.

Exercise

Write a function solve(A,y) that takes as input a non-singular matrix and a vector and returns the solution of without using np.linalg.lu.

Homework Assignment

Homework 1