Jul 24, 2015

linear algebra examples

linear systems

The library includes several solvers for the linear system $AX=B$. The choice depends on the properties of the coefficient matrix $A$.

For well conditioned determined (square) systems we can use linearSolve, based on the LU decomposition.

import Numeric.LinearAlgebra

a = (2><2)
 [ 1, 2
 , 3, 4 ] :: Matrix R

b = (2><1)
 [ 5
 , 6 :: R ]
λ> linearSolve a b
Just (2><1)
 [ -3.9999999999999987
 ,   4.499999999999999 ]

This solver returns a Maybe type because it fails when $A$ is singular.

λ> linearSolve ((2><2) [1,2,3,6]) b
Nothing

We test a bigger system with random elements and multiple right-hand sides:

λ> a <- randn 100 100
λ> dispShort 10 10 2 a
100x100
-0.39   1.54   0.87  -0.11   0.06   0.60  -0.89  .. -0.71   0.38
 0.49   0.82  -0.30   0.05   1.01  -0.43  -0.31  ..  1.19   1.64
-2.03  -1.40   1.73   0.45   0.32  -1.38   1.09  .. -0.67   0.40
-0.86  -1.47   1.38   0.08  -0.28   1.12  -0.88  .. -0.54   1.10
 0.07  -0.04   0.12  -2.70   0.62  -0.41  -0.30  .. -1.71   1.46
 1.46  -1.10   1.02   0.45  -0.82   0.04   0.08  .. -0.00  -0.44
-1.63  -0.18  -1.06  -0.07  -0.89  -0.11   0.18  .. -1.26   3.03
  :      :      :      :      :      :      :    ::   :      :  
 0.01   0.88   0.54  -2.06   1.62  -0.38  -1.66  ..  0.35   1.12
 0.80  -0.66   2.54   0.59  -1.18   0.11  -1.11  ..  0.10  -0.33
λ> b <- randn 100 5
λ> b
100x5
 0.09   0.27   1.16  -1.85  -0.71
-0.90   1.74   1.99  -0.81   1.79
 0.45  -0.53   1.19   0.15  -0.30
-2.37   0.16   1.09   0.45  -1.51
-0.28   0.97  -0.62  -0.95  -1.21
 0.21  -1.89  -0.67   0.27   0.38
 0.80  -0.14   0.20   1.88   1.17
  :      :      :      :      :  
-0.13  -0.03  -0.95   0.24  -2.36
 0.66   0.38   0.86  -0.45   0.46
λ> let Just x = linearSolve a b
λ> x
100x5
 1.65   1.02    0.28    0.16   0.65
-0.63   0.05   -0.36   -1.84  -0.33
 2.13   3.02    6.76   11.46   3.84
 9.87   8.74   21.81   34.80  11.61
 9.31   9.11   17.88   30.35  10.41
-2.61  -2.23   -4.77   -8.13  -3.11
-2.30  -3.23   -4.88   -7.99  -3.09
  :      :       :       :      :  
-5.69  -4.25  -10.52  -16.54  -5.61
 1.58   2.11    4.73    9.19   2.86
λ> norm_Frob (a<>x-b)
4.048112530322873e-12

For overdetermined systems we can use linearSolveLS, which computes the least squares solution. We test it with a random system of 200 equations, 100 unknowns, and 10 right-hand sides.

λ> a <- randn 200 100
λ> b <- rand 200 10
λ> let x = linearSolveLS a b
λ> x
100x10
 0.04   0.00   0.06   0.05   0.07   0.06   0.01   0.12   0.12   0.01
-0.02  -0.02  -0.08  -0.03  -0.03  -0.05  -0.05  -0.02  -0.06  -0.04
 0.05  -0.01   0.01  -0.03   0.00  -0.05   0.04  -0.01  -0.07  -0.02
-0.03  -0.01  -0.05  -0.03  -0.01   0.01  -0.04   0.03  -0.01  -0.06
-0.00  -0.04  -0.02   0.00  -0.03  -0.04  -0.01  -0.00  -0.07  -0.06
-0.12  -0.15  -0.16  -0.10  -0.17  -0.14  -0.17  -0.13  -0.20  -0.15
-0.03   0.00   0.05   0.01   0.04   0.04  -0.01   0.03   0.04   0.01
  :      :      :      :      :      :      :      :      :      :  
-0.03  -0.03  -0.04  -0.00  -0.02   0.06   0.02   0.02  -0.06  -0.04
-0.09  -0.07  -0.09  -0.07  -0.08  -0.10  -0.07  -0.10  -0.11  -0.07
λ> norm_Frob (a<>x - b)
19.055875304607525

If the system is symmetric positive definite we can use cholSolve. As an example, we solve the above overconstrained system using the normal equations: $A^TA X = A^T B$.

λ> let Just ch = mbChol (mTm a)

The function mTm a computes $A^TA$ and stores it in the Herm wrapper requied by the Cholesky decomposition. The result is returned in a Maybe type because this function can fail if the matrix is not positive definite.

λ> let x = cholSolve ch (tr a <> b)
λ> x
100x10
 0.04   0.00   0.06   0.05   0.07   0.06   0.01   0.12   0.12   0.01
-0.02  -0.02  -0.08  -0.03  -0.03  -0.05  -0.05  -0.02  -0.06  -0.04
 0.05  -0.01   0.01  -0.03   0.00  -0.05   0.04  -0.01  -0.07  -0.02
-0.03  -0.01  -0.05  -0.03  -0.01   0.01  -0.04   0.03  -0.01  -0.06
-0.00  -0.04  -0.02   0.00  -0.03  -0.04  -0.01  -0.00  -0.07  -0.06
-0.12  -0.15  -0.16  -0.10  -0.17  -0.14  -0.17  -0.13  -0.20  -0.15
-0.03   0.00   0.05   0.01   0.04   0.04  -0.01   0.03   0.04   0.01
  :      :      :      :      :      :      :      :      :      :  
-0.03  -0.03  -0.04  -0.00  -0.02   0.06   0.02   0.02  -0.06  -0.04
-0.09  -0.07  -0.09  -0.07  -0.08  -0.10  -0.07  -0.10  -0.11  -0.07

We obtain the same solution and residual:

λ> norm_Frob (a<>x - b)
19.055875304607525

Finally, if the coefficient matrix is rank-deficient, we must use the most general (and slower) solver (<\>), based on the SVD, similar to the backslash operator of Matlab/Octave.

λ> let a = (5><3)[1..] :: Matrix R
λ> a
(5><3)
 [  1.0,  2.0,  3.0
 ,  4.0,  5.0,  6.0
 ,  7.0,  8.0,  9.0
 , 10.0, 11.0, 12.0
 , 13.0, 14.0, 15.0 ]
λ> rank a
2
λ> let b = vector [1,3,-2,10,5]

The regular least squares solver (silently) fails

λ> linearSolveLS a (asColumn b)
(3><1)
 [ -2.1224492054274448e15
 ,  4.2448984108548815e15
 ,  -2.122449205427437e15 ]

but the more powerful <\> obtains the correct solution:

λ> let x = a <\> b 
λ> x
fromList [0.46666666666666734,0.16666666666666718,-0.13333333333333416]
λ> norm_2 (a#>x - b)
7.661592523751182

The operator <\> also admits multiple right-hand sides as columns of a matrix.

Subdetermined systems can also be solved with linearSolveLS (full rank) and <\> (general). In this case we obtain the minimum norm solution.

SVD

There are several variants of the singular value decomposition, depending on how many singular vectors must be computed.

λ> let a = (5><3) [1..] :: Matrix R
λ> a
(5><3)
 [  1.0,  2.0,  3.0
 ,  4.0,  5.0,  6.0
 ,  7.0,  8.0,  9.0
 , 10.0, 11.0, 12.0
 , 13.0, 14.0, 15.0 ]

This version computes the full set of left and right singular vectors:

λ> let (u,s,v) = svd a
λ> disp 3 u
5x5
-0.101   0.768  -0.016  -0.418  -0.474
-0.249   0.488   0.534   0.277   0.581
-0.396   0.208  -0.815   0.110   0.353
-0.543  -0.072   0.090   0.622  -0.552
-0.690  -0.352   0.206  -0.591   0.092
λ> s
fromList [35.18264833189422,1.4769076999800912,9.904891284970545e-16]
λ>  diagRect 0 s 5 3
5x3
35.183  0.000  0.000
 0.000  1.477  0.000
 0.000  0.000  0.000
 0.000  0.000  0.000
 0.000  0.000  0.000
λ>  v
3x3
-0.519  -0.751  -0.408
-0.576  -0.046   0.816
-0.632   0.659  -0.408
λ>  u <> diagRect 0 s 5 3 <> tr v
5x3
 1.000   2.000   3.000
 4.000   5.000   6.000
 7.000   8.000   9.000
10.000  11.000  12.000
13.000  14.000  15.000

The "thin" svd computes only the min (rows, columns) singular vectors:

λ> let (u,s,v) = thinSVD a
λ>  u
5x3
-0.101   0.768  -0.016
-0.249   0.488   0.534
-0.396   0.208  -0.815
-0.543  -0.072   0.090
-0.690  -0.352   0.206
λ>  asRow s
1x3
35.183  1.477  0.000
λ>   v
3x3
-0.519  -0.751  -0.408
-0.576  -0.046   0.816
-0.632   0.659  -0.408
λ>   u <> diag s <> tr v
5x3
 1.000   2.000   3.000
 4.000   5.000   6.000
 7.000   8.000   9.000
10.000  11.000  12.000
13.000  14.000  15.000

The "compact" svd returns the singular vectors corresponding to nonzero singular values:

λ> let (u,s,v) = compactSVD a
λ>    u
5x2
-0.101   0.768
-0.249   0.488
-0.396   0.208
-0.543  -0.072
-0.690  -0.352
λ>   asRow s
1x2
35.183  1.477
λ>    v
3x2
-0.519  -0.751
-0.576  -0.046
-0.632   0.659
λ>    u <> diag s <> tr v
5x3
 1.000   2.000   3.000
 4.000   5.000   6.000
 7.000   8.000   9.000
10.000  11.000  12.000
13.000  14.000  15.000

There are also functions to compute only the left or the right singular vectors.

eigenvalues and eigenvectors

The eigenvalues of a general real matrix can be complex:

λ> let m = matrix 2 [1,-3,4,-1]
λ> m
2x2
1  -3
4  -1
λ> let (l,v) = eig m
λ> l
fromList [0.0 :+ 3.316624790355399,0.0 :+ (-3.316624790355399)]
λ> v
(2><2)
 [ 0.18898223650461363 :+ 0.6267831705280087, 0.18898223650461363 :+ (-0.6267831705280087)
 ,                 0.7559289460184544 :+ 0.0,                 0.7559289460184544 :+ (-0.0) ]

If the matrix is symmetric the eigenvalues are real.

λ> let ms = (m + tr m)/2
λ> ms
(2><2)
 [ 1.0,  0.5
 , 0.5, -1.0 ]
λ> eig ms
(fromList [1.118033988749895 :+ 0.0,(-1.118033988749895) :+ 0.0],(2><2)
 [  0.9732489894677301 :+ 0.0, (-0.22975292054736118) :+ 0.0
 , 0.22975292054736118 :+ 0.0,     0.9732489894677301 :+ 0.0 ])

In this case it is much better to use eigSH, which is more efficient and directly obtains a real result. To avoid symmetry tests, now this function only admits arguments contained in the Herm wrapper, constructed by functions like sym and mTm which always obtain real symmetric or complex Hermitian results.

λ> let ms' = sym m
λ> ms'
Herm (2><2)
 [ 1.0,  0.5
 , 0.5, -1.0 ]
λ> eigSH ms'
(fromList [1.118033988749895,-1.118033988749895],(2><2)
 [  -0.9732489894677301, 0.22975292054736116
 , -0.22975292054736116, -0.9732489894677301 ])

Cholesky

The Cholesky decomposition is used to efficiently solve symmetric positive definite linear systems. It also requires a Herm argument. Since it may fail (if the matrix is not positive definite), it produces a Maybe result.

λ> mbChol ms'
Nothing
λ> let mp = mTm m
λ> mp
Herm (2><2)
 [ 17.0, -7.0
 , -7.0, 10.0 ]
λ> mbChol mp
Just (2><2)
 [ 4.123105625617661, -1.697749375254331
 ,               0.0, 2.6678918753996625 ]
λ> mTm <$> mbChol mp
Just (Herm (2><2)
 [ 17.0, -7.0
 , -7.0, 10.0 ])

QR decomposition ! anchor qr

Let's test the qr decomposition on a random matrix.

λ> m <- randn 5 3
λ>  m
5x3
 2.992   1.041   0.596
 1.036   1.698  -1.023
 1.072   0.706  -0.777
 0.654  -0.585  -0.604
-1.701   0.656   0.134
λ> let (q,r) = qr m
λ>  q
5x5
-0.786  -0.093   0.548   0.175   0.206
-0.272  -0.696  -0.428   0.262  -0.435
-0.282  -0.199  -0.430  -0.646   0.528
-0.172   0.383  -0.528   0.627   0.390
 0.447  -0.566   0.230   0.300   0.580
λ>  r
5x3
-3.807  -1.085  0.192
 0.000  -2.015  0.503
 0.000   0.000  1.448
 0.000   0.000  0.000
 0.000   0.000  0.000
λ>  q <> r - m
5x3
 0.000  -0.000  -0.000
 0.000   0.000  -0.000
 0.000  -0.000  -0.000
 0.000  -0.000   0.000
-0.000   0.000  -0.000

overdetermined homogeneous system

We can easily find $\mathrm{argmin}_x ||Cx||^2$, $||x||=1$.

c = (5><4)
 [ 10, 1,  1, 1
 , 1, 10,  1, 1
 , 1,  1, 10, 1
 , 1,  1,  1, 1
 , 1,  2,  2, 1] :: Matrix R
λ> nullspace c
(4><0)
 []
λ> singularValues c
fromList [12.632795935579493,9.03279188134824,9.0,0.9061664738090339]

The solution is the right singular vector corresponding to the smallest singular value:

λ> (last . toColumns . snd . rightSV) c
fromList [-8.934175780982971e-2,-9.575718118885315e-2,-9.575718118885444e-2,0.9867518304077171]

This is already available in the library as null1.

λ> null1 c
fromList [-8.934175780982971e-2,-9.575718118885315e-2,-9.575718118885444e-2,0.9867518304077171]
λ> norm_2 (a #> null1 a)
4.636427468134552e-15

other examples

We can use the QR decomposition or the singular vectors of a random matrix to create a "random rotation":

randRot n = snd . rightSV <$> randn n n
λ> disp 3 =<< randRot 5
5x5
-0.034   0.451  -0.325   0.274  -0.784
-0.332  -0.061   0.865   0.216  -0.303
-0.144   0.364  -0.046   0.769   0.503
 0.375  -0.722  -0.110   0.535  -0.199
 0.853   0.373   0.364  -0.011   0.023
λ> r <- randRot 5
λ> disp 3 r
5x5
 0.200   0.880   0.216  -0.369   0.042
 0.396   0.022  -0.862  -0.257  -0.183
-0.799   0.332  -0.319   0.129  -0.366
 0.310  -0.030   0.283   0.161  -0.893
-0.263  -0.337   0.168  -0.869  -0.184
λ> det r
-1.0000000000000016

This can be used to create a random matrix of given rank and dimensions. We define the function using applicative style:

randRank r (m,n) = (<>) <$> a <*> b
  where
    a = takeColumns r <$> randRot m
    b = takeRows    r <$> randRot n
λ> r <- randRank 2 (6,4)
λ> disp 3 r
6x4
 0.065   0.007   0.093   0.203
-0.671   0.084  -0.292  -0.574
-0.228   0.080   0.117   0.295
-0.490   0.168   0.237   0.604
-0.152   0.037   0.008   0.040
 0.427  -0.099  -0.007  -0.074
λ> rank r
2
λ> rcond r
6.046179037234281e-18
λ> singularValues r
fromList [1.0000000000000002,0.9999999999999997,1.1012104895297248e-16,6.046179037234282e-18]

A similar result can be obtained from the left and right singular vectors of a single random matrix:

randRank' r (n,m) = cutsv r <$> randn n m
  where
    cutsv r x = u' <> tr v'
      where
        (u,s,v) = svd x
        u' = takeColumns r u
        v' = takeColumns r v
λ> r <- randRank' 2 (3,5)
λ> disp 3 r
3x5
-0.296  -0.323   0.292  -0.587  -0.576
 0.061   0.101  -0.026   0.139   0.121
-0.077   0.600   0.765   0.183  -0.114
λ> singularValues r
fromList [0.9999999999999998,0.9999999999999998,5.201849001304443e-17]