Skip to content

Commit 9cd3cc8

Browse files
committed
Added Gaussian Elimination in Haskell
1 parent 6c69955 commit 9cd3cc8

File tree

2 files changed

+76
-11
lines changed

2 files changed

+76
-11
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Data.Array
2+
import Data.List (intercalate)
3+
4+
type Matrix a = Array (Int, Int) a
5+
6+
cols :: Matrix a -> [Int]
7+
cols m = let ((_, c1), (_, cn)) = bounds m in [c1..cn]
8+
9+
swapRows :: Int -> Int -> Matrix a -> Matrix a
10+
swapRows r1 r2 m
11+
| r1 == r2 = m
12+
| otherwise = m // concat [ [((r2, c), m!(r1, c)), ((r1, c), m!(r2, c))]
13+
| c <- cols m]
14+
15+
multRow :: (Num a) => Int -> a -> Matrix a -> Matrix a
16+
multRow r a m = m // [((r, k), a * m!(r, k)) | k <- cols m ]
17+
18+
combRows :: (Eq a, Fractional a) => (Int, Int) -> a -> Int -> Matrix a -> Matrix a
19+
combRows (r, c) a t m
20+
| m!(t, c) == 0 = m
21+
| otherwise = m // [((t, k), a * m!(t, k)/(m!(t, c)) - m!(r, k))
22+
| k <- cols m ]
23+
24+
toEchelon :: (Ord a, Fractional a) => Matrix a -> Matrix a
25+
toEchelon mat = go (r1, c1) mat
26+
where
27+
((r1, c1), (rn, cn)) = bounds mat
28+
go (r, c) m
29+
| c == cn = m
30+
| pivot == 0 = go (r, c + 1) m
31+
| otherwise = go (r + 1, c + 1)
32+
$ foldr (combRows (r, c) pivot) (swapRows r target m) [r+1..rn]
33+
where (pivot, target) = maximum [ (m!(k, c), k) | k <- [r..rn]]
34+
35+
toReducedEchelon :: (Fractional a, Eq a) => Matrix a -> Matrix a
36+
toReducedEchelon mat = foldr go mat (echelonPath (r1, c1))
37+
where
38+
((r1, c1), (rn, cn)) = bounds mat
39+
echelonPath (r, c)
40+
| r > rn || c>=cn = []
41+
| mat!(r, c) == 0 = echelonPath (r, c + 1)
42+
| otherwise = (r, c) : echelonPath (r + 1, c + 1)
43+
go (r, c) m = foldr (combRows (r, c) 1) (multRow r (1/(m!(r, c))) m) [r1..r-1]
44+
45+
46+
printM :: (Show a) => Matrix a -> String
47+
printM m =
48+
let ((r1, c1), (rn, cn)) = bounds m
49+
in unlines [ intercalate "\t" [ take 7 $ show $ m!(r, c) | c <- [c1..cn] ]
50+
| r <- [r1..rn] ]
51+
52+
main :: IO ()
53+
main = do
54+
let m = listArray ((1,1),(3,4)) [2,3,4,6,1,2,3,4,3,-4,0,10]
55+
putStrLn "Original Matrix:"
56+
putStrLn $ printM m
57+
putStrLn "Echelon form"
58+
putStrLn $ printM $ toEchelon m
59+
putStrLn "Reduced echelon form"
60+
putStrLn $ printM $ toReducedEchelon $ toEchelon m

chapters/matrix_methods/gaussian_elimination/gaussian_elimination.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,12 @@ $$
8181
$$
8282

8383

84-
and it has a particular name: _Row Eschelon Form_. Basically, any matrix can be considered in row eschelon form if
84+
and it has a particular name: _Row Echelon Form_. Basically, any matrix can be considered in row echelon form if
8585

8686
1. All non-zero rows are above rows of all zeros
8787
2. The leading coefficient or _pivot_ (the first non-zero element in every row when reading from left to right) is right of the pivot of the row above it.
8888

89-
Now, Row Eschelon Form is nice, but wouldn't it be even better if our system of equations looked simply like this
89+
Now, Row Echelon Form is nice, but wouldn't it be even better if our system of equations looked simply like this
9090

9191

9292
$$
@@ -112,9 +112,9 @@ $$
112112
$$
113113

114114

115-
And again has a special name * **Reduced** Row Eschelon Form*. Now, it seems obvious to point out that if we remove the values to the right of the equals sign \($$=$$\), Row Eschelon Form is an upper triangular matrix, while Reduced Row Eschelon Form is diagonal. This might not be important now, but it will play an important role in future discussions, so keep it buzzing in the back of your brain.
115+
And again has a special name * **Reduced** Row Echelon Form*. Now, it seems obvious to point out that if we remove the values to the right of the equals sign \($$=$$\), Row Echelon Form is an upper triangular matrix. This might not be important now, but it will play an important role in future discussions, so keep it buzzing in the back of your brain.
116116

117-
For now, I hope the motivation is clear: we want to convert a matrix into Row Eschelon and (potentially) Reduced Row Eschelon Form to make large systems of equations trivial to solve, so we need some method to do that. What is that method called? \(Hint: It's the title of this section\)
117+
For now, I hope the motivation is clear: we want to convert a matrix into Row Echelon and (potentially) Reduced Row Echelon Form to make large systems of equations trivial to solve, so we need some method to do that. What is that method called? \(Hint: It's the title of this section\)
118118

119119
That's right! _Gaussian Elimination_
120120

@@ -128,7 +128,7 @@ In the end, reducing large systems of equations boils down to a game you play on
128128
2. You can multiply any row by a non-zero scale value
129129
3. You can add any row to a multiple of any other row
130130

131-
That's it. Before continuing, I suggest you try to recreate the Row Eschelon matrix we made above. That is, do the following:
131+
That's it. Before continuing, I suggest you try to recreate the Row Echelon matrix we made above. That is, do the following:
132132

133133
$$
134134
\left[
@@ -150,7 +150,7 @@ $$
150150

151151
There are plenty of different strategies you could use to do this, and no one strategy is better than the rest. Personally, I usually try to multiply each row in the matrix by different values and add rows together until the first column is all the same value, and then I subtract the first row from all subsequent rows. I then do the same thing for the following columns.
152152

153-
After you get an upper triangular matrix, the next step is diagonalizing to create the Reduced Row Eschelon Form. In other words, we do the following:
153+
After you get an upper triangular matrix, the next step is diagonalizing to create the Reduced Row Echelon Form. In other words, we do the following:
154154

155155
$$
156156
\left[
@@ -170,7 +170,7 @@ $$
170170
\right]
171171
$$
172172

173-
Here, the idea is similar to above. You can do basically anything you want. My strategy is usually the same as before, but starts from the right-most column and subtracts upwards instead of downwards.
173+
Here, the idea is similar to above. The strategy is the same as before, but starts from the right-most column and subtracts upwards instead of downwards.
174174

175175
## The Algorithm
176176

@@ -257,19 +257,23 @@ In code, this looks like:
257257
{% method %}
258258
{% sample lang="jl" %}
259259
[import:1-42, lang:"julia"](code/julia/gaussian_elimination.jl)
260+
{% sample lang="hs" %}
261+
[import:4-33, lang:"haskell"](code/haskell/gaussianElimination.hs)
260262
{% endmethod %}
261263

262-
As with all code, it takes time to fully absorb what is going on and why everything is happening; however, I have tried to comment the above psuedocode with the necessary steps. Let me know if anything is unclear!
264+
As with all code, it takes time to fully absorb what is going on and why everything is happening; however, I have tried to comment the above pseudocode with the necessary steps. Let me know if anything is unclear!
263265

264-
Now, to be clear: this algorithm creates an upper-triangular matrix. In other words, it only creates a matrix in *Row Eschelon Form*, not * **Reduced** Row Eschelon Form*! So what do we do from here? Well, we could create another step to further reduce the matrix, but another method would be to use *Back-Substitution*.
266+
Now, to be clear: this algorithm creates an upper-triangular matrix. In other words, it only creates a matrix in *Row Echelon Form*, not * **Reduced** Row Echelon Form*! So what do we do from here? Well, we could create another step to further reduce the matrix, but another method would be to use *Back-Substitution*.
265267

266268
The back-substitution method is precisely what we said above.
267-
If we have a matrix in Row-Eschelon Form, we can directly solve for $$z$$, and then plug that value in to find $$y$$ and then plug both of those values in to find $$x$$!
269+
If we have a matrix in Row Echelon Form, we can directly solve for $$z$$, and then plug that value in to find $$y$$ and then plug both of those values in to find $$x$$!
268270
Even though this seems straightforward, the pseudocode might not be as simple as you thought!
269271

270272
{% method %}
271273
{% sample lang="jl" %}
272274
[import:44-64, lang:"julia"](code/julia/gaussian_elimination.jl)
275+
{% sample lang="hs" %}
276+
[import:35-43, lang:"haskell"](code/haskell/gaussianElimination.hs)
273277
{% endmethod %}
274278

275279
Now, as for what's next... Well, we are in for a treat! The above algorithm clearly has 3 `for` loops, and will thus have a complexity of $$\sim O(n^3)$$, which is abysmal! If we can reduce the matrix to a specifically **tridiagonal** matrix, we can actually solve the system in $$\sim O(n)$$! How? Well, we can use an algorithm known as the _Tri-Diagonal Matrix Algorithm_ \(TDMA\) also known as the _Thomas Algorithm_.
@@ -281,6 +285,8 @@ The full code can be seen here:
281285
{% method %}
282286
{% sample lang="jl" %}
283287
[import, lang:"julia"](code/julia/gaussian_elimination.jl)
288+
{% sample lang="hs" %}
289+
[import, lang:"haskell"](code/haskell/gaussianElimination.hs)
284290
{% endmethod %}
285291

286292

@@ -306,4 +312,3 @@ $$
306312
\newcommand{\bfomega}{\boldsymbol{\omega}}
307313
\newcommand{\bftau}{\boldsymbol{\tau}}
308314
$$
309-

0 commit comments

Comments
 (0)