]> gitweb.michael.orlitzky.com - numerical-analysis.git/blob - src/Linear/QR.hs
ea72958d74163c30679ce75231b5a48a8f0f45dd
[numerical-analysis.git] / src / Linear / QR.hs
1 {-# LANGUAGE NoImplicitPrelude #-}
2 {-# LANGUAGE ScopedTypeVariables #-}
3
4 -- | QR factorization via Givens rotations.
5 --
6 module Linear.QR (
7 givens_rotator,
8 qr )
9 where
10
11 import qualified Algebra.Ring as Ring ( C )
12 import qualified Algebra.Algebraic as Algebraic ( C )
13 import Data.Vector.Fixed ( ifoldl )
14 import Data.Vector.Fixed.Cont ( Arity )
15 import Debug.Trace
16 import NumericPrelude hiding ( (*) )
17
18 import Linear.Matrix (
19 Mat(..),
20 (*),
21 (!!!),
22 construct,
23 identity_matrix,
24 transpose )
25
26
27 -- | Construct a givens rotation matrix that will operate on row @i@
28 -- and column @j@. This is done to create zeros in some column of
29 -- the target matrix. You must also supply that column's @i@th and
30 -- @j@th entries as arguments.
31 --
32 -- Examples (Watkins, p. 193):
33 --
34 -- >>> import Normed ( Normed(..) )
35 -- >>> import Linear.Vector ( Vec2, Vec3 )
36 -- >>> import Linear.Matrix ( Mat2, Mat3, fromList, frobenius_norm )
37 -- >>> import qualified Data.Vector.Fixed as V ( map )
38 --
39 -- >>> let m = givens_rotator 0 1 1 1 :: Mat2 Double
40 -- >>> let m2 = fromList [[1, -1],[1, 1]] :: Mat2 Double
41 -- >>> m == (1 / (sqrt 2) :: Double) *> m2
42 -- True
43 --
44 -- >>> let m = fromList [[2,3],[5,7]] :: Mat2 Double
45 -- >>> let rot = givens_rotator 0 1 2.0 5.0 :: Mat2 Double
46 -- >>> ((transpose rot) * m) !!! (1,0) < 1e-12
47 -- True
48 -- >>> let (Mat rows) = rot
49 -- >>> let (Mat cols) = transpose rot
50 -- >>> V.map norm rows :: Vec2 Double
51 -- fromList [1.0,1.0]
52 -- >>> V.map norm cols :: Vec2 Double
53 -- fromList [1.0,1.0]
54 --
55 -- >>> let m = fromList [[12,-51,4],[6,167,-68],[-4,24,-41]] :: Mat3 Double
56 -- >>> let rot = givens_rotator 1 2 6 (-4) :: Mat3 Double
57 -- >>> let ex_rot_r1 = [1,0,0] :: [Double]
58 -- >>> let ex_rot_r2 = [0,0.83205,-0.55470] :: [Double]
59 -- >>> let ex_rot_r3 = [0, 0.55470, 0.83205] :: [Double]
60 -- >>> let ex_rot = fromList [ex_rot_r1, ex_rot_r2, ex_rot_r3] :: Mat3 Double
61 -- >>> frobenius_norm ((transpose rot) - ex_rot) < 1e-4
62 -- True
63 -- >>> ((transpose rot) * m) !!! (2,0) == 0
64 -- True
65 -- >>> let (Mat rows) = rot
66 -- >>> let (Mat cols) = transpose rot
67 -- >>> V.map norm rows :: Vec3 Double
68 -- fromList [1.0,1.0,1.0]
69 -- >>> V.map norm cols :: Vec3 Double
70 -- fromList [1.0,1.0,1.0]
71 --
72 givens_rotator :: forall m a. (Eq a, Ring.C a, Algebraic.C a, Arity m)
73 => Int -> Int -> a -> a -> Mat m m a
74 givens_rotator i j xi xj =
75 construct f
76 where
77 xnorm = sqrt $ xi^2 + xj^2
78 c = if xnorm == (fromInteger 0) then (fromInteger 1) else xi / xnorm
79 s = if xnorm == (fromInteger 0) then (fromInteger 0) else xj / xnorm
80
81 f :: Int -> Int -> a
82 f y z
83 | y == i && z == i = c
84 | y == j && z == j = c
85 | y == i && z == j = negate s
86 | y == j && z == i = s
87 | y == z = fromInteger 1
88 | otherwise = fromInteger 0
89
90
91 -- | Compute the QR factorization of a matrix (using Givens
92 -- rotations). This is accomplished with two folds: the first one
93 -- traverses the columns of the matrix from left to right, and the
94 -- second traverses the entries of the column from top to
95 -- bottom.
96 --
97 -- The state that is passed through the fold is the current (q,r)
98 -- factorization. We keep the pair updated by multiplying @q@ and
99 -- @r@ by the new rotator (or its transpose).
100 --
101 -- Examples:
102 --
103 -- >>> import Linear.Matrix
104 --
105 -- >>> let m = fromList [[1,2],[1,3]] :: Mat2 Double
106 -- >>> let (q,r) = qr m
107 -- >>> let c = (1 / (sqrt 2 :: Double))
108 -- >>> let ex_q = c *> (fromList [[1,-1],[1,1]] :: Mat2 Double)
109 -- >>> let ex_r = c *> (fromList [[2,5],[0,1]] :: Mat2 Double)
110 -- >>> frobenius_norm (q - ex_q) == 0
111 -- True
112 -- >>> frobenius_norm (r - ex_r) == 0
113 -- True
114 -- >>> let m' = q*r
115 -- >>> frobenius_norm (m - m') < 1e-10
116 -- True
117 -- >>> is_upper_triangular' 1e-10 r
118 -- True
119 --
120 -- >>> let m = fromList [[2,3],[5,7]] :: Mat2 Double
121 -- >>> let (q,r) = qr m
122 -- >>> frobenius_norm (m - (q*r)) < 1e-12
123 -- True
124 -- >>> is_upper_triangular' 1e-10 r
125 -- True
126 --
127 -- >>> let m = fromList [[12,-51,4],[6,167,-68],[-4,24,-41]] :: Mat3 Double
128 -- >>> let (q,r) = qr m
129 -- >>> frobenius_norm (m - (q*r)) < 1e-12
130 -- True
131 -- >>> is_upper_triangular' 1e-10 r
132 -- True
133 --
134 qr :: forall m n a. (Arity m, Arity n, Eq a, Algebraic.C a, Ring.C a, Show a)
135 => Mat m n a -> (Mat m m a, Mat m n a)
136 qr matrix =
137 ifoldl col_function initial_qr columns
138 where
139 Mat columns = transpose matrix
140 initial_qr = (identity_matrix, matrix)
141
142 -- | Process the column and spit out the current QR
143 -- factorization. In the first column, we want to get rotators
144 -- Q12, Q13, Q14,... In the second column, we want rotators Q23,
145 -- Q24, Q25,...
146 col_function (q,r) col_idx col =
147 ifoldl (f col_idx) (q,r) col
148
149 -- | Process the entries in a column, doing basically the same
150 -- thing as col_dunction does. It updates the QR factorization,
151 -- maybe, and returns the current one.
152 f col_idx (q,r) idx _ -- ignore the current element
153 | idx <= col_idx = (q,r)
154 -- trace ("---------------\nidx: " ++ (show idx) ++ ";\ncol_idx: " ++ (show col_idx) ++ "; leaving it alone") (q,r) -- leave it alone.
155 | otherwise = (q*rotator, (transpose rotator)*r)
156 -- trace ("---------------\nidx: " ++ (show idx) ++ ";\ncol_idx: " ++ (show col_idx) ++ ";\nupdating Q and R;\nq: " ++ (show q) ++ ";\nr " ++ (show r) ++ ";\nnew q: " ++ (show $ q*rotator) ++ ";\nnew r: " ++ (show $ (transpose rotator)*r) ++ ";\ny: " ++ (show y) ++ ";\nr[i,j]: " ++ (show (r !!! (col_idx, col_idx))))
157 -- (q*rotator, (transpose rotator)*r)
158 where
159 y = r !!! (idx, col_idx)
160 rotator :: Mat m m a
161 rotator = givens_rotator col_idx idx (r !!! (col_idx, col_idx)) y