25.3.5.7 Substitution

We introduced the LU-decomposition as a method for solving a linear system of equations. However, until now we have only decomposed the matrix A into L and U. Obviously, we now want to take the second step and see that this matrix decomposition helps us in solving a linear system of equations.

For this we return to the system of Eq. 25.2 from which we also took the matrix. The right-hand side is

b=(240)

si179_e

We will now use the function DoLUDecomposition in combination with Eq. 25.20 and Eq. 25.21 for solving the linear system of equations

Ly=bUx=y

si180_e

described by Eq. 25.18 and Eq. 25.19.

Calculating ysi151_e. We first solve Eq. 25.19 by applying Eq. 25.20 for finding ysi151_e by forward-substitution. For this, we find the following equations

y1=b1=2y2=b2l21y1=4732=23y3=b3k=12l3kyk=(l31y1+32y2)=132+11923=1219

si183_e

Calculating xsi1_e. Now that we have ysi151_e we proceed by finding xsi1_e by applying Eq. 25.21. For this we find the following equations

x3=1u33(y3)=19101219=65x2=1u22(y2u23x3)=319(2311365)=45x1=1u11(y1u12x2u13x3)=13(2445265)=65

si187_e

which obviously is the correct result (see Eq. 25.5). As you can see, we are able to find the solution to our original system of equation pretty much effortlessly once we have performed the LU-decomposition. In this case, we did not even have to perform a row permutation and there seems to be no problem with stability. This is obviously not the case, as our solver would still fail if we found a zero on the downwards-diagonal due to the division in the back-substitution performed in Eq. 25.21. As we have already done previously, this problem can be circumvented by changing the order of the equations, i.e., by implementing pivoting.

Maple Worksheet. Obviously, at this point we want to implement this scheme as a function. The function DoLUSubstitution implements the substitution steps required for solving linear systems of equations using LU-decomposition. It requires the matrices L and U as well as the vector bsi25_e as parameters. It consists of two loops, the first of which performs the forward-substitution (see line 6) while the second one performs the back-substitution (see line 14).

For the forward-substitution, the sum required for Eq. 25.20 is created in line 9 before applying Eq. 25.20 in line 11 to find the individual entries of ysi151_e. For the back-substitution, we first calculate the sum required for Eq. 25.21 in line 17 before applying Eq. 25.21 in line 19 for finding xsi1_e.

If we run our example (see line 24) we obtain the correct result

x=(0.40.81.2)

si191_e

Stability Consideration. As we can see, the LU-decomposition gives us the correct result and seems to be sufficiently stable. However, this is only because the example we chose happens to have no zeros on the downwards-diagonal. As already discussed before, these would result in a division by zero. As sparsely populated matrices with a lot of zero entries are very common, it is a good idea to implement pivoting for LU-decomposition as well.

25.3.5.8 Pivoting

As we have already done for SOR in section 25.3.4.4, we will implement partial pivoting for LU-decomposition. This method is often referred to as permutating LU-decomposition (PLU). For this we change the implementation of the function DoLUDecomposition as shown in listing 25.7. As you can see, we use a pivoting vector piv that is first initialized to the sequence (1, 2, . . . , n) in line 8. Inside of the line loop line 12 we perform the pivoting operation as before (see line 14). We keep the original order for the matrices L and U, but we then use the pivoting vector to access the respective row in m as can be seen in line 26 and line 34. We then need to return the pivoting vector in addition to the matrices L and U in line 37 because we require the pivoting vector to correctly associate the individual entries of the vectorsxsi1_e and bsi25_e when solving the linear system of equations.

Listing 25.6

[Maple] Listing for LU-substitution without pivoting. This listing is part of the listing LUDecomposition.mw, which can be found in the supplementary material.

 1 DoLUSubstitution:=proc(L::Matrix,U::Matrix,b::Vector) local n,i,k,x,y,summed:
 2   n:=Size(b,1):
 3   y:=Vector(1..n):
 4   x:=Vector(1..n):
 5   #forward-substitution - find y[i]
 6   for i from 1 to n do
 7     summed:=0:
 8     for k from 1 to 1-1 do
 9       summed:=summed+L[i,k]*y[k]:
10     end do:
11     y[i]:=evalf(b[i]-summed):
12   end do:
13   #back-substitution - find x[i]
14   for i from n by -1 to 1 do
15     summed:=0:
16     for k from i+1 to n do
17       summed:=summed+U[i,k]*x[k]:
18     end do:
19     x[i]:=evalf(1/U[i,i]*(y[i]-summed)):
20   end do:
21   return x:
22 end proc:
23
24 mml:=Matrix([[3,4,2],[7,3,1],[1,1,1]]);b:=Vector([2,4,0]);
25 DoLUSubstitution(DoLUDecomposition(m),b);

Listing 25.7

[Maple] Listing for LU-decomposition with pivoting. This listing is part of the listing LUDecomposition.mw, which can be found in the supplementary material.

 1 DoLUDecompositionPivot:=proc(mml::Matrix) local n,i,j,k,l,summed,L,U,piv,store:
 2   n:=Size(b,1):
 3   L:=Matrix(n,n):
 4   for i to n do
 5     L[i,i]:=1:
 6   end do:
 7   U:=Matrix(n,n):
 8   piv:=Vector(1..n):
 9   for i to n do
10     piv[i]:=i:
11   end do:
12   for i to n do
13    for l from i+1 to n do
14     if abs(m[piv[l],i])>abs(m[piv[i],i]) then
15       store:=piv[l]:
16       piv[l]:=piv[i]:
17       piv[i]:=store:
18     end if:
19    end do:
20    #calculate l[ij]
21    for j from 1 to i-1 do
22     summed:=0:
23     for k from 1 to j-1 do
24       summed:=summed+U[k,j]*L[i,k]:
25     end do:
26     L[i,j]:=1/U[j,j]*(m[piv[i],j]-summed):
27    end do:
28    #calculate u[ij]
29    for j from i to n do
30     summed:=0:
31     for k from 1 to i-1 do
32       summed:=summed+U[k,j]*L[i,k]:
33     end do:
34     U[i,j]:=m[piv[i],j]-summed:
35    end do:
36   end do:
37   return piv,L,U;
38 end proc:
39
40 mml:=Matrix([[3,4,2],[7,3,1],[1,1,1]]);
41 DoLUDecompositionPivot(m);
42
43 mml:=Matrix([[0,4,2],[7,0,1],[1,1,0]]);
44 DoLUDecompositionPivot(m);

Example. If we apply the function DoLUDecompositionPivot to our original matrix (see line 40) you can see from the output that the algorithm has performed a pivoting operation by switching the first and the second rows. We have found previously that pivoting is not necessary for this problem. However, switching the two rows places the 7 on the downwards-diagonal in the first row. This makes our algorithm more stable even though it is not required.

A Second Example. In the next step, we modify our original problem by setting (pretty much deliberately) two zeros on the downwards-diagonal. If we ran this second example (see line 43) with the original implementation of our LU-decomposition using the function DoLUDecomposition, we would receive an error due to the division by zero. If we run this example with the LU-decomposition using pivoting, we avoid this error and obtain the solution

L=(10001017141),U=(70104200214)

si194_e

The pivoting vector we also receive shows the sequence (2, 1, 3) indicating that the algorithm switched the lines such that the zeros are moved off the downwards-diagonal.

Adapting the Function DoLUSubstitution. Now that we have adapted the LU-decomposition to support pivoting, we also need to adapt the function DoLUSubstitution. The implementation of the method DoLUSubstitutionPivot is shown in listing 25.8 and is in analogy to the implementation of LUSubstitution (see listing 25.6). The only difference is that the function expects the pivoting vector piv as additional parameter. We then use this vector for accessing the correct entries in bsi25_e (see line 11). We can now run our original example (see line 24) which would obviously return the correct result again. In addition we can now also solve the second example we applied when testing the LU-decomposition with pivoting in listing 25.7. This is the output of line 27. The solution to this matrix is

Listing 25.8

[Maple] Listing for LU-substitution with pivoting. This listing is part of the listing LUDecomposition.mw, which can be found in the supplementary material.

 1 DoLUSubstitutionPivot:=proc(piv::Vector,L::Matrix,U::Matrix,b::Vector) local n,i,k,x,y,summed:
 2   n:=Size(m,1):
 3   y:=Vector(1..n):
 4   x:=Vector(1..n):
 5   #forward-substitution - find y[i]
 6   for i from 1 to n do
 7     summed:=0:
 8     for k from 1 to i-1 do
 9       summed:=summed+L[i,k]*y[k]:
10     end do:
11     y[i]:=evalf(b[piv[i]]-summed):
12   end do:
13   #back-substitution - find x[i]
14   for i from n by -1 to 1 do
15     summed:=0:
16     for k from i+1 to n do
17       summed:=summed+U[i,k]*x[k]:
18     end do:
19     x[i]:=evalf(1/U[i,i]*(y[i]-summed)):
20   end do:
21   return x:
22 end proc:
23
24 mml:=Matrix([[3,4,2],[7,3,1],[1,1,1]]);b:=Vector([2,4,0]);
25 DoLUSubstitutionPivot(DoLUDecompositionPivot(m),b);
26
27 mml:=Matrix([[0,4,2],[7,0,1],[1,1,0]]);
28 DoLUDecompositionPivot(m);

x=(0.3330.3331.666)=(121353)

si196_e  25.34

which is the correct result.

25.3.5.9 Summary

In this section we have discussed LU-decomposition, a very useful method for solving systems of linear equations. Besides being an exact method that does not require iteration, LU-decomposition allows us to use the decomposed matrices L and U for various values of bsi25_e , and thus to “reuse” a solution to linear systems with changing right-hand sides. We have seen that LU-decomposition by itself is already reasonably stable. By implementing partial pivoting, we can use this method to solve almost any kind of linear system of equations, which makes it a very potent and widely applicable method.

25.3.6 Sequential Dense LU-Decomposition

25.3.6.1 Introduction

In this section we will briefly introduce a modification of the classical LU-decomposition which is a little more memory efficient. In many cases, it is not necessary to have both matrices U and L separately. As they are both triangular matrices, they can be stored in a single matrix without losing information. In fact, there is a modification to the classical LU-decomposition that works with the matrix A directly transferring it to a matrix A'si198_e of the form

A'=LIn+U

si199_e  25.35

with In being the identity matrix of rank n. This scheme is referred to as sequential dense LU-decomposition. It is a very memory-efficient scheme and therefore lends itself well to implementation for numerical solvers.

25.3.6.2 Derivation

We will derive the scheme using the generic matrix A that we gradually transform to the dense LU-decomposed matrix A'si198_e.

A=(a11a12a1na21a22a2na3na3nann)=(100l2110ln1ln21)(u11u12u1n0u22u2n00unn)=LU

si201_e

First Row. We now change the entries of A row-by-row figuring out how we need to modify them in order to end up with A'si198_e. We begin with the first row for which we need to find the values of u11 to u12. For this we find

u11=a11u12=a12uij=a1ju1n=a1n

si203_e

which means that the values of a11 to a1n can remain unchanged as they already represent the correct values of u11 to u1n. In algorithmic notation we could note down that

u1j=a1j

si204_e  25.36

for i = 1 as the row index and i ≤ j ≤ n as the column index.

Remainder of First Column. After having cleared the first row, we proceed the rest of the first column for which we find the following equations

l21u11=a21l21a11=a21l21a=a21a11l21u11=ai1li1a11=ai1li1a=ai1a11ln1u11=an1ln1a11=an1ln1=an1a11

si205_e

As you can see, we find the remaining values of the first column by simply dividing the entries of A by the entry in the first row and first column a11. In algorithmic notation we can note

li1=ai1a11

si206_e

with the row index i with 1 ≤ i ≤ n. As you can see, we simply have to divide the entries ai1 by the value a11 to find li1. On the other hand, we may just as well simply perform this division on the matrix A itself, which will be the first modification we make. We therefore replace the first row entries of A with the values a0i1 according to

a'il=aila11=li1

si207_e  25.37

Second Row. At this point, the first row and the first column have been determined and the respective entries in L and U are known. We now proceed with the second row. Obviously, it would be convenient to simply repeat the process. However, we will see that this is not possible directly as the values of the entries we already determined will interfere. This is where the second modification will be made to A. By a small trick, we will change A in order to make all influences of already determined entries “disappear.” For the second row we find the following equations

l21u12+u22=a22u22=a22l21u12l21u13+u23=a23u23=a23l21u13l21u1j+u2j=a2ju2j=a2jl21u1jl21u1n+u2n=a2nu2n=a2nl21u1n

si208_e

As you can see, the operation is almost analogous to the treatment of the first row with the one exception that we cannot simply copy the values of a2j. We need to subtract the produce l21 u1j first. Now, we could just as well simply subtract this product from a2j and end up with the same result. In algorithmic notation, we could note down that

u2j=a2jl21u1j

si209_e  25.38

a'=a2jl21u1j=a2ja'21a1j=u2j

si210_e  25.39

where we have used Eq. 25.36 to set u1j = a1j and Eq. 25.37 to set l21 = a021. Obviously, we expect a similar influence of the already determined values on all other lines as well. We will therefore perform a likewise subtraction on the other lines using the same formula according to

a'ij=a2jl21u1j=aija'i1a1j=uij

si211_e  25.40

for i ≥ 2 and j ≥ 2. We will see in a moment that we actually required this modification.

Remainder of Second Column. Having processed the second row, we now turn to the second column starting from the second row. Proceeding likewise we find the following equations

l31u12+l32u22=a32l32=1u22(a32l31u12)l41u12+l42u22=a42l42=1u22(a42l41u12)li1u12+li2u22=ai2li2=1u22(ai2li1u12)ln1u12+lin2u22=an2ln2=1u22(an2ln1u12)

si212_e

In algorithmic notation we note

li2=1u22(ai2ii1u12)

si213_e

This expression can be simplified by noting that for j = 2 Eq. 25.39 tells us that a022 = u22 in which case we can rewrite the expression to

li2=1a'22(ai2li1u12)

si214_e  25.41

As we presumed just a moment ago, we actually find that the modification we made by Eq. 25.40 was necessary and that this modification allows us to write Eq. 25.41 as

li2=1a'22(ai2li1u12)=ai2'a22'

si215_e  25.42

for j = 2. If we compare this with Eq. 25.37, we find that this is the same operation and we can change a0i2 again in-place.

Iteration. Obviously, at this point we have found an iteration pattern if we repeat the process consisting of the following steps starting with A using the line index i

1. In line i replace for all (i, j) with i < j ≤: n replace aiji=aijaiisi216_e according to Eq. 25.37

2. Correct submatrix entries (k, j) with i < k ≤ n and i < j ≤ n: replace akji=akjak,iai,jsi217_e according to Eq. 25.40

3. Repeat for next row i + 1

25.3.6.3 Example

We will quickly carry out this example using the matrix we have been using as example so far

A=(342731111)

si218_e

We proceed by the outlined scheme by first dividing all entries for i < j ≤ n = 3 in the first column by a11 = 3 in which case we end up with

A=(34273311311)

si219_e

In the next step we correct the submatrix entries (k, j) for i < k ≤ n and i < j ≤ n finding

A=(34273193113131313)

si220_e

We then proceed to i = 2 again dividing all values i < j ≤ n = 3 (which is only the value a32) by a22=193si221_e yielding

A=(342731931131311913)

si222_e

We then proceed by correcting the remaining submatrix which only consists of the entry a33, which we correct to the final result

A=(3427319311313119103)

si223_e

If you compare this result with Eq. 25.33, you can see that we have obtained the matrix A′ according to Eq. 25.35.

25.3.6.4 Maple Worksheet

In the next step, we will transfer the algorithm to a Maple worksheet as we will need this specific algorithm again. The listing is shown in listing 25.9. The sequential dense LU-decomposition is implemented by the function DoLUDecompositionSequentialDense. It expects the matrix to decompose as parameter m. It will modify this matrix in place, therefore it may be required to copy the matrix if the original is to be retained. First the rank of the matrix is determined (see line 2) before the individual rows are processed indexed with the variable i. For each line the columns are iterated using the variable j dividing each entry according to Eq. 25.37. After this, the submatrix is corrected. For this a new row index k is introduced. The column index j can be reused here, but the value i is required which in turn requires the new index. Line 9 implements Eq. 25.40. The last two lines (see line 16) output the example which we derived manually.

Listing 25.9

[Maple] Listing for the sequential dense LU-decomposition. This listing is part of the listing LUDecomposition.mw, which can be found in the supplementary material.

 1 DoLUDecompositionSequentialDense:=proc(mml::Matrix) local n,i,j,k:
 2   n:=Size(m,1):
 3   for i from 1 to n do
 4     for j from i+1 to n do
 5      m[j,i]:=m[j,i]/m[i,i]:
 6     end do:
 7    for k from i+1 to n do
 8     for j from i+1 to n do
 9      m[k,j]:=m[k,j]-m[k,i]*m[i,j]:
10     end do:
11    end do:
12   end do:
13   return m;
14 end proc:
15
16 mml:=Matrix([[3,4,2],[7,3,1],[1,1,1]]);
17 DoLUDecompositionSequentialDense(m);

25.3.6.5 Pivoting

As already discussed during the discussion of the regular LU-decomposition, it is generally required to use pivoting for LU-decomposition. We will therefore reimplement DoLUDecompositionSequentialDense in a way that it supports pivoting. The listing is shown in listing 25.10. We implement partial pivoting in analogy to listing 25.7 using a pivoting vector piv (see line 4). You will see that this implementation is identical to the implementation of listing 25.7. The only difference is that each access to a row i must now be made via the pivoting vector. As we only have the matrix m in which we construct the matrices L and U, we cannot keep the original order as we did for the matrices in listing 25.7.

Listing 25.10

[Maple] Listing for the sequential dense LU-decomposition with pivoting. This listing is part of the listing LUDecomposition.mw, which can be found in the supplementary material.

 1 DoLUDecompositionSequentialDensePivot:=proc(mml::Matrix) local n,i,j,k,l,piv,store:
 2   n:=Size(m,1):
 3   piv:=Vector(1..n):
 4   for i from 1 to n do
 5    piv[i]:=i:
 6   end do:
 7   for i from 1 to n do
 8    for l from i to n do
 9     if abs(m[piv[l],i])>abs(m[piv[i],i]) then
10       store:=piv[l]:
11       piv[l]:=piv[i]:
12       piv[i]:=store:
13     end if:
14    end do:
15    for j from i+1 to n do
16     m[piv[j],i]:=m[piv[j],i]/m[piv[i],i]:
17    end do:
18    for k from i+1 to n do
19     for j from i+1 to n do
20      m[piv[k],j]:=m[piv[k],j]-m[piv[k],i]*m[piv[i],j]:
21     end do:
22    end do:
23   end do:
24   return piv,m;
25 end proc:
26
27 mml:=Matrix([[0,4,2],[7,3,1],[1,1,0]]);
28 DoLUDecompositionPivot(m);
29 DoLUDecompositionSequentialDensePivot(m);

If we run our example (see line 27), we will see that we obtain the correct solution

A=(0427011714914)

si224_e

which is the same result as obtained from DoLUDecompositionPivot, keeping in mind that L and U are combined according to Eq. 25.35. Also, by inspecting piv you can see that the algorithm changed the order of the rows ending up with the sequence (2, 1, 3). The function DoLUDecompositionPivot will rearrange L and U in order to “hide” this reordering from the user. The function DoLUDecompositionSequentialDensePivot cannot do this, as the function only has a single matrix to operate on. Therefore the substitution function for DoLUDecompositionSequentialDensePivot would need to account for this inability to reorder by accessing L and U (which are then stored in m) via the pivoting vector as index.

25.3.7 Substitution

Listing 25.11 shows the implementation of the substitution step for sequential dense LU-decomposition. This listing is in analogy to listing 25.6 with the only difference that it accepts the matrix m which has been modified by DoLUDecompositionSequentialDensePivot. As a consequence of the pivoting, we need to access all entries in the matrix via the pivoting vector both during the forward- (see line 9) and the back-substitution (see line 17). However, we provide the vectors ysi151_e and xsi1_e in the correct order so the user will not see any signs of the pivoting.

Listing 25.11

[Maple] Listing for sequential dense LU-substitution with pivoting. This listing is part of the listing LUDecomposition.mw, which can be found in the supplementary material.

 1 DoLUSubstitutionDensePivot:=proc(piv::Vector,mml::Matrix,b::Vector) local n,i,k,x,y,summed:
 2   n:=Size(b,1):
 3   y:=Vector(1..n):
 4   x:=Vector(1..n):
 5   #forward-substitution - find y[i]
 6   for i from 1 to n do
 7    summed:=0:
 8    for k from 1 to i-1 do
 9     summed:=summed+m[piv[i],k]*y[k]:
10    end do:
11    y[i]:=evalf(b[piv[i]]-summed):
12   end do:
13   #back-substitution - find x[i]
14   for i from n by -1 to 1 do
15    summed:=0:
16    for k from i+1 to n do
17     summed:=summed+m[piv[i],k]*x[k]:
18    end do:
19    x[i]:=evalf(1/(m[piv[i],i])*(y[i]-summed)):
20   end do:
21   return x:
22 end proc:
23
24 mml:=Matrix([[0,4,2],[7,0,1],[1,1,0]]);b:=Vector([2,4,0]);
25 DoLUSubstitutionDensePivot(DoLUDecompositionSequentialDensePivot(m),b);

If we run the example which we also used in line 27, we see that the solution we find is

x=(131353)

si227_e

which is identical to the solution found in Eq. 25.34.

25.3.8 Tridiagonal Matrix Algorithmml: Thomas Algorithm

25.3.8.1 Introduction

There is one algorithm which we want to introduce at this point that is commonly used to solve linear systems of equations where the matrix A is tridiagonal. This algorithm is generally referred to as the tridiagonal matrix algorithm or Thomas1 algorithm. Please recall that tridiagonal matrices are matrices which have nonzero entries on the downwards-diagonal and in the columns right and left of the diagonal only. All other entries are zero. These matrices occur surprisingly often and they must usually not be solved by a complete LU-decomposition if the matrix is set up such that the largest values are collected on the downwards-diagonal. Again, such matrices occur frequently. We will come across one example when studying one-dimensional FEM (see Eq. 32.48). We will use this example here while introducing the algorithm. Please refer to section 32.5 for the derivation of the matrix.

25.3.8.2 Derivation

The general linear system of equation can be written as

Ax=b(β1γ100000α2β2γ200000γ3β3γ3000000000αnβn)(x1x2x3x4x5x(n1)xn)=(b1b2b3b4b5b(n1)bn)

si228_e  (Eq. 25.43)

which we may rewrite to

β1x1+γ1x2=b1i=1αixi1+βixi+γixi+1=bii=2..n

si229_e  (Eq. 25.44)

for the ith equation. We split up the solving process in two steps: a forward step and a back-substitution.

Forward Step: First Row. The first step is dividing the first row by β1. Doing this, we obtain a 1 on the downwards-diagonal entry of the matrix. In this case we can rewrite Eq. 25.44 for i = 1 to

x1+γ1β1x2=b1β1x1+γ1x2=b2

si230_e  (Eq. 25.45)

The first two equations are now given by

x1+γ1x2=b1α2x1+β2x2+γ2x3=b2

si231_e

Forward Step: Subsequent Rows. We now multiply the first row by α2 and subtract it from the second row finding

x1+γ1x2=b1(β2α2γ1)x2+γ2x3=b2b1α2

si232_e

We then divide the second row by (β2α2γ′1) thus obtaining again a 1 on the diagonal entry of the second row. We then obtain

x1+γ1x2=b1+x2+γ2β2α2γ1x3=b2b1α2β2α2γ1

si233_e

which we simplify to

x1+γ1x2=b1+x2+γ2x3=b2

si234_e

We then proceed with all subsequent rows accordingly. The general scheme is

αi=0βi=0γi=γiβiαiγi1

si235_e  (Eq. 25.46)

bi=biαibi1βiαiγi1

si236_e  (Eq. 25.47)

After proceeding with these modifications Eq. 25.43 can be written as

Ax=b(1γ10000001γ20000001γ300000000001)(x1x2x3x4x5x(n1)xn)=(b1b2b3b4b5b(n1)bn)

si237_e  (Eq. 25.48)

Back-Substitution. The back-substitution uses the fact that the last line of Eq. 25.48 gives the solution for xn directly, which is simply b0n. We can then substitute all the rows above the last row one by one noting that

xi=biγixi+1

si238_e  (Eq. 25.49)

25.3.8.3 Maple Worksheet

We will quickly implement the Thomas algorithm in Maple. The function we will implement is called DoThomas and is shown in listing 25.12 together with the example from Eq. 32.48. We first restart the Maple server and include Core.txt (see line 1). The function DoThomas accepts the matrix A as parameter m and the right-hand side vector bsi25_e. It first determines the rank of the matrix and prepares the solution vector x (see line 4 and line 5). It then calculates the values for the first row applying Eq. 25.45 for γ′1 and b′1 (see line 6 and line 7). It then loops all subsequent rows applying Eq. 25.46 for finding γi and Eq. 25.47 for finding b′i (see line 9 and line 10). The loop only extends to n − 1 because for the last row only b′n can be calculated as this row has no entry for γ (see line 12). Please note that in all cases the algorithm does not set the values for α′i and β′1, as these are not required to find the solution.

Listing 25.12

[Maple] Listing for solving a linear system of equations using the Thomas algorithm. A digital version of the listing can be found under the name ThomasAlgorithm.mw in the supplementary material.

 1 restart:read "Core.txt":
 2
 3 DoThomas:=proc(m::Matrix,b) local n,x,i:
 4   n:=Size(m,1):
 5   x:=Vector(1..n):
 6   m[1,2]:=m[1,2]/m[1,1]:
 7   b[1]:=b[1]/m[1,1]:
 8   for i from 2 to n-1 do
 9    m[i,i+1]:=m[i,i+1]/(m[i,i]-m[i,i-1]*m[i-1,i]):
10    b[i]:=(b[i]-m[i,i-1]*b[i-1])/(m[i,i]-m[i,i-1]*m[i-1,i]):
11   end do:
12   b[n]:=(b[n]-m[n,n-1]*b[n-1])/(m[n,n]-m[n,n-1]*m[n-1,i]):
13   x[n]:=evalf(b[n]):
14   for i from n-1 to 1 by -1 do
15    x[i]:=evalf(b[i]-m[i,i+1]*x[i+1]):
16   end do:
17   return x;
18 end proc:
19
20 A:=Matrix([[2 ,-1,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0],
21           [-1,2 ,-1,0 ,0 ,0 ,0 ,0 ,0 ,0],
22           [ 0,-1,2 ,-1,0 ,0 ,0 ,0 ,0 ,0],
23           [ 0, 0,-1, 2,-1,0 ,0 ,0 ,0 ,0],
24           [ 0, 0, 0,-1,2 ,-1,0 ,0 ,0 ,0],
25           [ 0, 0, 0, 0,-1,2 ,-1,0 ,0 ,0],
26           [ 0, 0, 0, 0,0 ,-1,2 ,-1,0 ,0],
27           [ 0, 0, 0, 0,0 ,0 ,-1,2 ,-1,0],
28           [ 0, 0, 0, 0,0 ,0 ,0 ,-1,2 ,-1],
29           [ 0, 0, 0, 0,0 ,0 ,0 , 0,-1,2]]):
30 b:=Vector([4,4,4,4,4,4,4,4,4,4]):
31 DoThomas(A,b);

Having completed the forward step, the algorithm then proceeds with the back-substitution first setting the value xn directly (see line 13). It then loops (see line 14) from the second-to-last value all the way to the first setting in the values in the solution vector according to Eq. 25.49.

Having implemented DoThomas we then proceed by solving the example taken from Eq. 32.48 using the indicated values for A and bsi25_e (see line 30). Executing line 31 yields the solution

vx=(20364856606056483620)

si241_e

which is correct. In section 32.5 we will detail the use of this solution and its physical interpretation.

25.3.8.4 Numerical Stability

As with all numerical methods, it is useful to consider the criteria for numerical stability. The critical step for the numerical stability is the division operation in Eq. 25.46 and Eq. 25.47. For all cases in which the denominator results in very small values, the algorithm is prone to becoming numerically unstable. Without going into details, we will obtain a stable scheme whenever

|βi||αi|+|γi|

si242_e

In these cases, the denominator will be large and therefore the division operation will result in a continuous decrease in magnitude. In fact, many applications yield tridiagonal matrices which fulfill these conditions, including our example. Our implementation does not check for numerical stability. In cases where stability is a problem, LU-decomposition with pivoting should be applied which will yield stable solutions.

25.3.8.5 Summary

The Thomas algorithm is a very fast and efficient algorithm for solving linear systems of equations involving tridiagonal matrices. It is often a significantly faster alternative to using a full LU-decomposition. The Thomas algorithm is numerically quite stable and in many applications it is a good first choice to solve a tridiagonal matrix problem.

25.3.9 Export to CoreFunctions.txt

The two functions DoLUDecompositionSequentialDensePivot and DoLUSubstitutionDensePivot are very useful methods for solving linear systems of equations, which will come in handy for problems we will study later. We will therefore export these functions into CoreFunctions.txt in order to access them later. In addition we will also add the function DoThomas that we will use later on for solving tridiagonal systems of equations.

25.3.10 Summary

In this section, we have discussed a modification of the LU-decomposition which is more memory efficient as it operates on the input matrix A directly and does not require the additional matrices L and U created during the regular LU-decomposition. This is the reason why this scheme is usually the preferred method implemented in numerical solvers. We also implemented pivoting and substitution for the sequential dense LU-decomposition, which gives us a very nice method to solve almost any type of linear system of equations. In addition we also studied the Thomas algorithm, which is a very useful numerical method for solving linear systems of equations involving tridiagonal matrices.

25.4 Summary

In this section, we have discussed analytical as well as numerical methods for solving linear systems of equations. As we will see, having methods available to quickly solve linear systems of equations is paramount for many numerical methods. Most of the methods discussed in this section will be required in the following sections when studying the most commonly used numerical methods in fluid mechanics.

References

[1] Bashforth F., Adams J.C. An attempt to test the theories of capillary action: by comparing the theoretical and measured forms of drops of fluid. University Press; 1883 (cit. on pp. 499, 500).

[2] Runge C. Über die numerische Auflösung von Differentialgleichungen. In: Math. Ann. 1895;46:167–178 (cit. on p. 500).

[3] Heun K. Neue Methoden zur approximativen Integration der Differentialgleichungen einer unabhängigen Veränderlichen. In: Z. Math. Phys. 1900;45:23–38 (cit. on p. 500).

[4] Kutta W. Beitrag zur näherungsweisen Integration totaler Differentialgleichungen. In: Z. Math. Phys. 1901;46:435–453 (cit. on p. 500).

[5] Moulton F.R. New methods in exterior ballistics. 1926 Chicago, (cit. on p. 500).

[6] Butcher J. Numerical methods for ordinary differential equations in the 20th century. In: Journal of Computational and Applied Mathematics. 2000;125(1–2):1–29 (cit. on p. 500).

[7] Cramer G. Introduction à l’analyse des lignes courbes algébriques. Frères Cramer & Cl. Philibert; 1750 (cit. on p. 504).

[8] Jacobi C. Ueber eine neue Auflösungsart der bei der Methode der kleinsten Quadrate vorkommenden lineären Gleichungen. In: Astronomische Nachrichten. 1845;22(20):297–306 (cit. on p. 508).

[9] von Seidel L. Abhandlungen der Mathematisch-Physikalischen Klasse der Königlich Bayerischen Akademie der Wissenschaften; . Ueber ein Verfahren, die Gleichungen, auf welche die Methode der kleinsten Quadrate führt, sowie lineäre Gleichungen überhaupt, durch successive Annäherung aufzulösen. 1874;11 (cit. on p. 512).

[10] Turing A.M. Rounding-off errors in matrix processes. In: The Quarterly Journal of Mechanics and Applied Mathematics. 1948;1(1):287–308 (cit. on p. 517).

[11] Thomas L. Using a computer to solve problems in physics. In: Applications of Digital Computers. 1963;44–45 (cit. on p. 531).


1 Carl Runge was a German mathematician who made important contributions to numerics. Runge was the son of a German diplomat and spent most of his youth in Havana before moving to Munich to study philosophy and mathematics. Runge’s paper on the numerical solution of differential equations [2] is considered one of the two essential papers (only second to the seminal paper of Adams and Bashforth [1]) igniting the field of numerics.

2 Karl Heun was a German mathematician and is most renowned for his work in numerics. He raised the order of the method originally described by Runge to the order of 3 [3]. Kutta would later raise the order to 4 creating one of the most famous methods in numerics, i.e., the fourth-order Runge-Kutta method.

3 Wilhelm Kutta was a German mathematician. He may be most renowned for his improvements of the numerical methods first outlined by Runge and later improved by Heun. Kutta was the first to suggest a fourth-order method, which is generally known as the fourth-order Runge-Kutta method that is the most important scheme of all the Runge-Kutta methods [4].

4 Forest Moulton was an American mathematician and astronomer. He may be most renowned for his work on numerical methods extending concepts originally introduced by John Adams. Moulton’s equations are generally referred to as Adams-Moulton formulas that were first published in 1926 [5].

1 Gabriel Cramer was a Swiss mathematician most renowned for his work on linear systems of equations. The Cramer rule for matrix inversion carries his name, although the method seems to have been already developed by Gottfried Leibniz and Seki Kowa. Cramer gave the first complete description of the process in 1750 showing how an n × n system of equations can be solved [7].

2 Pierre Sarrus was a French mathematician who developed the Sarrus rule which is a convenient mnemonic for calculating the determinant of matrices.

1 Carl Jacobi was a German mathematician who made important contributions to number theory and differential calculus. Among others he introduced the Jacobi method in 1845, which is one of the most fundamental methods for solving linear systems of equations numerically [8].

1 Philipp Ludwig von Seidel was a German mathematician who made important contributions to numerical methods coining the method which is now known as the Gauß-Seidel method [9]. Seidel was professor at the Ludwig-Maximilians University of Munich and had several noteworthy students, Max Planck being one of them.

1 Alan Turing, born in London in 1923 is one of those people you cannot place into a category. He made significant contribution to mathematics, algorithms, cryptanalysis, and logics, among many other things. Turing is best known for his influential work on the early design of computation machines. He was one of the key scientists in the cryptanalysis team at Bletchley Park where the German cryptography instrument, the Enigma was eventually broken. For breaking the Enigma code, Turing devised one of the first “computers”. The Turing Award, the equivalent of the Nobel Prize for contributions to modern computing and algorithms, is named after him.

1 Llewellyn Thomas was a British physicist who may be best known for the Thomas algorithm, which is a very efficient version of the Gauß-Jordan elimination for solving linear systems of equations with tridiagonal matrices [11].

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset