# Linear systems

## Contents

# 2.3. Linear systems¶

We now attend to the central problem of this chapter: Given a square, \(n\times n\) matrix \(\mathbf{A}\) and an \(n\)-vector \(\mathbf{b}\), find an \(n\)-vector \(\mathbf{x}\) such that \(\mathbf{A}\mathbf{x}=\mathbf{b}\). Writing out these equations, we obtain

If \(\mathbf{A}\) is invertible, then the mathematical expression of the solution is \(\mathbf{x}=\mathbf{A}^{-1}\mathbf{b}\) because

When \(\mathbf{A}\) is singular, then \(\mathbf{A}\mathbf{x}=\mathbf{b}\) may have no solution or infinitely many solutions.

If we define

then it is easy to check that for any real value of \(\alpha\) we have

Hence the linear system \(\mathbf{S}\mathbf{x}=\mathbf{b}\) with \(\mathbf{b}=\begin{bmatrix} 1\\0\end{bmatrix}\) has infinitely many solutions. For most other choices of \(\mathbf{b}\), the system has no solution.

## Don’t use the inverse¶

Matrix inverses are indispensable for mathematical discussion and derivations. However, as you may remember from a linear algebra course, they are not trivial to compute from the entries of the original matrix. Julia does have a command `inv`

that finds the inverse of a matrix, but it is almost never the best means to solve a problem. In particular, for solving a linear system of equations, finding the inverse is slower than the standard algorithm.

As demonstrated in Demo 2.1.3, a linear system of equations can be solved by a backslash (the `\`

symbol, not to be confused with the slash `/`

used in web addresses).

For a square matrix \(A\), the command `A\b`

is mathematically equivalent to \(\mathbf{A}^{-1}\mathbf{b}\).

```
A = [1 0 -1; 2 2 1; -1 -3 0]
```

```
3×3 Matrix{Int64}:
1 0 -1
2 2 1
-1 -3 0
```

```
b = [1,2,3]
```

```
3-element Vector{Int64}:
1
2
3
```

```
x = A\b
```

```
3-element Vector{Float64}:
2.1428571428571432
-1.7142857142857144
1.1428571428571428
```

One way to check the answer is to compute a quantity known as the **residual**. It is (ideally) close to machine precision (relative to the elements in the solution).

```
residual = b - A*x
```

```
3-element Vector{Float64}:
-4.440892098500626e-16
-4.440892098500626e-16
0.0
```

If the matrix \(\mathbf{A}\) is singular, you may get an error.

```
A = [0 1; 0 0]
b = [1,-1]
x = A\b
```

```
LAPACKException(1)
Stacktrace:
[1] chklapackerror(ret::Int64)
@ LinearAlgebra.LAPACK /Applications/julia-rosetta/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/LinearAlgebra/src/lapack.jl:43
[2] trtrs!(uplo::Char, trans::Char, diag::Char, A::Matrix{Float64}, B::Vector{Float64})
@ LinearAlgebra.LAPACK /Applications/julia-rosetta/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/LinearAlgebra/src/lapack.jl:3431
[3] ldiv!
@ /Applications/julia-rosetta/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/LinearAlgebra/src/triangular.jl:727 [inlined]
[4] \(A::UpperTriangular{Int64, Matrix{Int64}}, B::Vector{Int64})
@ LinearAlgebra /Applications/julia-rosetta/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/LinearAlgebra/src/triangular.jl:1661
[5] \(A::Matrix{Int64}, B::Vector{Int64})
@ LinearAlgebra /Applications/julia-rosetta/Julia-1.7.app/Contents/Resources/julia/share/julia/stdlib/v1.7/LinearAlgebra/src/generic.jl:1140
[6] top-level scope
@ In[6]:3
[7] eval
@ ./boot.jl:373 [inlined]
[8] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1196
```

The error message here is admittedly cryptic. In this case we can check that the rank of \(\mathbf{A}\) is less than its number of columns, indicating singularity.

```
rank(A)
```

```
1
```

A linear system with a singular matrix might have no solution or infinitely many solutions, but in either case, backslash will fail. Moreover, detecting singularity is a lot like checking whether two floating-point numbers are *exactly* equal: because of roundoff, it could be missed. In Section 2.8 we’ll find a robust way to fully describe the situation.

## Triangular systems¶

The solution process is especially easy to demonstrate for a system with a **triangular matrix**. For example, consider the lower triangular system

The first row of this system states simply that \(4x_1=8\), which is easily solved as \(x_1=8/4=2\). Now, the second row states that \(3x_1-x_2=5\). As \(x_1\) is already known, it can be replaced to find that \(x_2 = -(5-3\cdot 2)=1\). Similarly, the third row gives \(x_3=(0+1\cdot 2)/3 = 2/3\), and the last row yields \(x_4=(1-1\cdot 2 + 1\cdot 1 + 1\cdot 2/3)/2 = 1/3\). Hence the solution is

The process just described is called **forward substitution**. In the \(4\times 4\) lower triangular case of \(\mathbf{L}\mathbf{x}=\mathbf{b}\) it leads to the formulas

For upper triangular systems \(\mathbf{U}\mathbf{x}=\mathbf{b}\) an analogous process of **backward substitution** begins by solving for the last component \(x_n=b_n/U_{nn}\) and working backward. For the \(4\times 4\) case we have

Solving the system backward, starting with \(x_4\) first and then proceeding in descending order, gives

It should be clear that forward or backward substitution fails if and only if one of the diagonal entries of the system matrix is zero. We have essentially proved the following theorem.

A triangular matrix is singular if and only if at least one of its diagonal elements is zero.

## Implementation¶

Consider how to implement the sequential process implied by Equation (2.3.1). It seems clear that we want to loop through the elements of \(\mathbf{x}\) in order. Within each iteration of that loop, we have an expression whose length depends on the iteration number. One way we could do this would be with a nested loop:

```
for i in 1:4
s = 0
for j in 1:i-1
s += L[i,j]*x[j]
end
x[i] = (b[i]-s) / L[i,i]
end
```

A briefer version of the inner loop over `j`

is the expression

```
s = sum( L[i,j]*x[j] for j in 1:i-1 )
```

However, when `i`

equals 1, the range `1:i-1`

is empty and the sum causes an error. To avoid this we can handle this case before the `i`

loop begins, and start that loop at 2. This is the approach taken in Function 2.3.4.

**Forward substitution to solve a lower triangular linear system**

```
1"""
2 forwardsub(L,b)
3
4Solve the lower triangular linear system with matrix `L` and
5right-hand side vector `b`.
6"""
7function forwardsub(L,b)
8 n = size(L,1)
9 x = zeros(n)
10 x[1] = b[1]/L[1,1]
11 for i in 2:n
12 s = sum( L[i,j]*x[j] for j in 1:i-1 )
13 x[i] = ( b[i] - s ) / L[i,i]
14 end
15 return x
16end
```

The implementation of backward substitution is much like forward substitution and is given in Function 2.3.5.

**Backward substitution to solve an upper triangular linear system**

```
1"""
2 backsub(U,b)
3
4Solve the upper triangular linear system with matrix `U` and
5right-hand side vector `b`.
6"""
7function backsub(U,b)
8 n = size(U,1)
9 x = zeros(n)
10 x[n] = b[n]/U[n,n]
11 for i in n-1:-1:1
12 s = sum( U[i,j]*x[j] for j in i+1:n )
13 x[i] = ( b[i] - s ) / U[i,i]
14 end
15 return x
16end
```

It’s easy to get just the lower triangular part of any matrix using the `tril`

function.

Use `tril`

to return a matrix that zeros out everything above the main diagonal. The `triu`

function zeros out below the diagonal.

```
A = rand(1.:9.,5,5)
L = tril(A)
```

```
5×5 Matrix{Float64}:
7.0 0.0 0.0 0.0 0.0
7.0 3.0 0.0 0.0 0.0
4.0 9.0 2.0 0.0 0.0
4.0 7.0 4.0 3.0 0.0
3.0 5.0 8.0 7.0 1.0
```

We’ll set up and solve a linear system with this matrix.

```
b = ones(5)
x = FNC.forwardsub(L,b)
```

```
5-element Vector{Float64}:
0.14285714285714285
0.0
0.2142857142857143
-0.14285714285714288
-0.14285714285714257
```

It’s not clear how accurate this answer is. However, the residual should be zero or comparable to \(\macheps\).

```
b - L*x
```

```
5-element Vector{Float64}:
0.0
0.0
0.0
0.0
0.0
```

Next we’ll engineer a problem to which we know the exact answer. Use `\alpha`

`Tab` and `\beta`

`Tab` to get the Greek letters.

The notation `0=>ones(5)`

creates a `Pair`

. In `diagm`

, pairs indicate the position of a diagonal and the elements that are to be placed on it.

```
α = 0.3;
β = 2.2;
U = diagm( 0=>ones(5), 1=>[-1,-1,-1,-1] )
U[1,[4,5]] = [ α-β, β ]
U
```

```
5×5 Matrix{Float64}:
1.0 -1.0 0.0 -1.9 2.2
0.0 1.0 -1.0 0.0 0.0
0.0 0.0 1.0 -1.0 0.0
0.0 0.0 0.0 1.0 -1.0
0.0 0.0 0.0 0.0 1.0
```

```
x_exact = ones(5)
b = [α,0,0,0,1]
```

```
5-element Vector{Float64}:
0.3
0.0
0.0
0.0
1.0
```

Now we use backward substitution to solve for \(\mathbf{x}\), and compare to the exact solution we know already.

```
x = FNC.backsub(U,b)
err = x - x_exact
```

```
5-element Vector{Float64}:
2.220446049250313e-16
0.0
0.0
0.0
0.0
```

Everything seems OK here. But another example, with a different value for \(\beta\), is more troubling.

```
α = 0.3;
β = 1e12;
U = diagm( 0=>ones(5), 1=>[-1,-1,-1,-1] )
U[1,[4,5]] = [ α-β, β ]
b = [α,0,0,0,1]
x = FNC.backsub(U,b)
err = x - x_exact
```

```
5-element Vector{Float64}:
-4.882812499995559e-5
0.0
0.0
0.0
0.0
```

It’s not so good to get 4 digits of accuracy after starting with 16! The source of the error is not hard to track down. Solving for \(x_1\) performs \((\alpha-\beta)+\beta\) in the first row. Since \(|\alpha|\) is so much smaller than \(|\beta|\), this a recipe for losing digits to subtractive cancellation.

The example in Demo 2.3.6 is our first clue that linear system problems may have large condition numbers, making inaccurate solutions inevitable in floating-point arithmetic. We will learn how to spot such problems in Section 2.8. Before reaching that point, however, we need to discuss how to solve general linear systems, not just triangular ones.

## Exercises¶

✍ Find a vector \(\mathbf{b}\) such that the system \(\begin{bmatrix} 0&1\\0&0 \end{bmatrix} \mathbf{x}=\mathbf{b}\) has no solution.

✍ Solve the following triangular systems by hand.

**(a)**\(\displaystyle \begin{aligned} -2x_1 &= -4 \\ x_1 - x_2 &= 2 \\ 3x_1 + 2x_2 + x_3 &= 1 \end{aligned} \quad\)**(b)**\(\displaystyle \begin{bmatrix} 4 & 0 & 0 & 0 \\ 1 & -2 & 0 & 0 \\ -1 & 4 & 4 & 0 \\ 2 & -5 & 5 & 1 \end{bmatrix} \mathbf{x} = \begin{bmatrix} -4 \\ 1 \\ -3 \\ 5 \end{bmatrix}\quad\)**(c)**\(\displaystyle \begin{aligned} 3x_1 + 2x_2 + x_3 &= 1 \\ x_2 - x_3 &= 2 \\ 2 x_3 &= -4 \end{aligned}\)⌨ Use Function 2.3.4 or Function 2.3.5 to solve each system from the preceding exercise. Verify that the solution is correct by computing \(\mathbf{L}\mathbf{x}\) and subtracting \(\mathbf{b}\).

⌨ Use Function 2.3.5 to solve the following systems. Verify that the solution is correct by computing \(\mathbf{U}\mathbf{x}\) and subtracting \(\mathbf{b}\).

**(a)**\(\;\begin{bmatrix} 3 & 1 & 0 \\ 0 & -1 & -2 \\ 0 & 0 & 3 \\ \end{bmatrix} \mathbf{x} = \begin{bmatrix} 1 \\ 1 \\ 6 \end{bmatrix}\qquad\)**(b)**\(\;\begin{bmatrix} 3 & 1 & 0 & 6 \\ 0 & -1 & -2 & 7 \\ 0 & 0 & 3 & 4 \\ 0 & 0 & 0 & 5 \end{bmatrix} \mathbf{x} = \begin{bmatrix} 4 \\ 1 \\ 1 \\ 5 \end{bmatrix}\)Suppose a string is stretched with tension \(\tau\) horizontally between two anchors at \(x=0\) and \(x=1\). At each of the \(n-1\) equally spaced positions \(x_k=k/n\), \(k=1,\ldots,n-1\), we attach a little mass \(m_i\) and allow the string to come to equilibrium. This causes vertical displacement of the string. Let \(q_k\) be the amount of displacement at \(x_k\). If the displacements are not too large, then an approximate force balance equation is

\[n \tau (q_k - q_{k-1}) + n\tau (q_k - q_{k+1}) = m_k g, \qquad k=1,\ldots,n-1,\]where \(g=-9.8\) m/s\(^2\) is the acceleration due to gravity, and we define \(q_0=0\) and \(q_n=0\) due to the anchors. This defines a linear system for \(q_1,\ldots,q_{n-1}\).

**(a)**✍ Show that the force balance equations can be written as a linear system \(\mathbf{A}\mathbf{q}=\mathbf{f}\), where \(\mathbf{q}\) is a vector of the unknown displacements and \(\mathbf{A}\) is a tridiagonal matrix (i.e., \(A_{ij}=0\) if \(|i-j|>1\)) of size \((n-1)\times(n-1)\).**(b)**⌨ Let \(\tau=10\) N, and \(m_k=(1/10n)\) kg for every \(k\). Using backslash, find the displacements when \(n=8\) and \(n=40\), and superimpose plots of \(\mathbf{q}\) over \(0\le x \le 1\) for the two cases. (Be sure to include the zero values at \(x=0\) and \(x=1\) in your plots.)**(c)**⌨ Repeat (b) for the case \(m_k = (k/5n^2)\) kg.⌨ If \(\mathbf{B}\in\mathbb{R}^{n \times p}\) has columns \(\mathbf{b}_1,\ldots,\mathbf{b}_p\), then we can pose \(p\) linear systems at once by writing \(\mathbf{A} \mathbf{X} = \mathbf{B}\), where \(\mathbf{X}\) is \(n\times p\). Specifically, this equation implies \(\mathbf{A} \mathbf{x}_j = \mathbf{b}_j\) for \(j=1,\ldots,p\).

**(a)**Modify Function 2.3.4 and Function 2.3.5 so that they solve the case where the second input is \(n\times p\) for \(p\ge 1\).**(b)**If \(\mathbf{A} \mathbf{X}=\mathbf{I}\), then \(\mathbf{X}=\mathbf{A}^{-1}\). Use this fact to write a function`ltinverse`

that uses your modified**forwardsub**to compute the inverse of a lower triangular matrix. Test your function on at least two nontrivial matrices. (We remind you here that this is just an exercise; matrix inverses are rarely a good idea in numerical practice!)⌨ Demo 2.3.6 showed solutions of \(\mathbf{A}\mathbf{x}=\mathbf{b}\), where

\[\begin{split}\mathbf{A} = \begin{bmatrix} 1 & -1 & 0 & \alpha-\beta & \beta \\ 0 & 1 & -1 & 0 & 0 \\ 0 & 0 & 1 & -1 & 0 \\ 0 & 0 & 0 & 1 & -1 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix}, \quad \mathbf{b} = \begin{bmatrix} \alpha \\ 0 \\ 0 \\ 0 \\ 1 \end{bmatrix}.\end{split}\]Use Function 2.3.5 to solve with \(\alpha=0.1\) and \(\beta=10,100,10^3,\ldots,10^{12}\), tabulating the values of \(\beta\) and \(|x_1-1|\). (This kind of behavior is explained in Section 2.8.)

The function

`rank`

computes the rank of a matrix. However, it is numerically unstable for matrices that are nearly singular, in a sense to be defined in a later section.