Code
%%julia
using LinearAlgebra
x = [3., 4.]
r = hypot(x...)
c, s = x[1]/r, x[2]/r
G = [c s; -s c]
G*x2-element Vector{Float64}:
5.0
-4.440892098500626e-16
Householder reflections zero out an entire column below the diagonal at once. Sometimes we want to zero out a single entry.
When zeroing out a single entry in a given column, we just need a 2x2 matrix that uses one nonzero entry to zero out the other one. We can embed this matrix into an identity matrix so that it only acts on the rows with these two nonzero entries.
%%julia
using LinearAlgebra
x = [3., 4.]
r = hypot(x...)
c, s = x[1]/r, x[2]/r
G = [c s; -s c]
G*x2-element Vector{Float64}:
5.0
-4.440892098500626e-16
To act on rows \(i\) and \(j\) of an \(m\times n\) matrix \(\mathbf{A}\), embed the columns of \(\mathbf{G}\) in to the \(i\)th and \(j\)th columns of an identity matrix. Only those two rows of \(\mathbf{A}\) change.
%%julia
x = [0, 0, 3., 0, 4., 0]
G = Matrix{Float64}(I, 6, 6)
G[3,3] = c
G[3,5] = s
G[5,3] = -s
G[5,5] = c
G6×6 Matrix{Float64}:
1.0 0.0 0.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0 0.0 0.0
0.0 0.0 0.6 0.0 0.8 0.0
0.0 0.0 0.0 1.0 0.0 0.0
0.0 0.0 -0.8 0.0 0.6 0.0
0.0 0.0 0.0 0.0 0.0 1.0
%%julia
G*x6-element Vector{Float64}:
0.0
0.0
5.0
0.0
-4.440892098500626e-16
0.0
In the worst case (fully dense matrix), we use rotations which use the entry above to eliminate the entry below all through the lower-triangular part of the matrix.
With \(\mathbf{G}_1, \mathbf{G}_2, \ldots, \mathbf{G}_N\) the (embedded) rotations used, \[\mathbf{G}_N \cdots \mathbf{G}_1 \mathbf{A} = \mathbf{R} \quad\Longrightarrow\quad \mathbf{Q} = \mathbf{G}_1^\top \cdots \mathbf{G}_N^\top.\]
%%julia
"""
qrgivens(A)
QR factorization by Givens rotations. Returns Q and R.
"""
function qrgivens(A)
m,n = size(A)
Q = Matrix(1.0I,m,m)
R = float(copy(A))
for j in 1:n
for i in m:-1:j+1
a,b = R[i-1,j], R[i,j]
r = hypot(a,b)
r == 0 && continue
c,s = a/r, b/r
G = [c s; -s c]
R[i-1:i,:] = G*R[i-1:i,:]
Q[:,i-1:i] = Q[:,i-1:i]*G'
end
end
return Q, triu(R)
endqrgivens
%%julia
A = rand(1.:9.,6,4)
Q,R = qrgivens(A)
@show opnorm(Q*R - A)
@show opnorm(Q'*Q - I);opnorm(Q * R - A) = 5.803502081424711e-15
opnorm(Q' * Q - I) = 5.736192209707644e-16
For a square matrix this is \(2n^3\), about \(1.5\times\) the cost of Householder (\(\tfrac43 n^3\)). So why use Givens?
Givens rotations exploit structure. If a column already has few nonzeros below the diagonal, we only rotate away those entries and we avoid fill-in!
For example, an upper Hessenberg matrix has one subdiagonal entry per column, so its QR factorization takes \(n-1\) rotations and only \(O(n^2)\) flops.
Householder reflections and Givens rotations construct a QR factorization with square \(\mathbf{Q}\) formed as a product of orthogonal matrices. Gram-Schmidt computes the thin QR factorization \(\mathbf{A} = \hat{\mathbf{Q}}\hat{\mathbf{R}}\) by orthogonalizing the columns of \(\mathbf{A}\) one at a time. This can be advantageous when \(\mathbf{A}\) is very thin and tall.
Let \[\mathbf{A} = \begin{bmatrix} 2 & 0 & 3 \\ 2 & 3 & 3 \\ 1 & 3 & 6 \end{bmatrix}.\]
Working one column at a time:
Check that \(\mathbf{A} = \mathbf{Q}\mathbf{R}\), where \(\mathbf{R}\) is the upper triangular matrix of the numbers you recorded.
At step \(k\), start with \(\mathbf{v}^{(0)} = \mathbf{a}_k\) and orthogonalize against previous columns of \(\mathbf{Q}\) one at a time:
\[\mathbf{v}^{(j)} = \mathbf{v}^{(j-1)} - r_{jk}\,\mathbf{q}_j, \qquad j = 1,\dots,k-1.\]
Then \(r_{kk} = \|\mathbf{v}^{(k-1)}\|\) and \(\mathbf{q}_k = \mathbf{v}^{(k-1)}/r_{kk}\).
Now, how do we compute \(r_{jk}\)?
These are identical in exact arithmetic, since \(\mathbf{q}_j \perp \mathbf{q}_1,\dots,\mathbf{q}_{j-1}\).
%%julia
function cgs(A)
m,n = size(A)
Q = zeros(m,n)
R = zeros(n,n)
for k in 1:n
v = A[:,k]
for j in 1:k-1
R[j,k] = Q[:,j]'*A[:,k]
v -= R[j,k]*Q[:,j]
end
R[k,k] = norm(v)
Q[:,k] = v/R[k,k]
end
return Q, R
end
function mgs(A)
m,n = size(A)
Q = zeros(m,n)
R = zeros(n,n)
for k in 1:n
v = A[:,k]
for j in 1:k-1
R[j,k] = Q[:,j]'*v
v -= R[j,k]*Q[:,j]
end
R[k,k] = norm(v)
Q[:,k] = v/R[k,k]
end
return Q, R
endmgs (generic function with 1 method)
Gram-Schmidt costs \(\sim 2mn^2\) flops, the same as Householder QR, but it is not backward stable. The danger is loss of orthogonality: when columns of \(\mathbf{A}\) are nearly dependent, subtracting projections causes cancellation and the computed \(\hat{\mathbf{Q}}\) is not orthonormal.
We test with a Läuchli matrix, whose columns are nearly parallel.
%%julia
ε = 1e-8
A = [ones(1,3); ε*I(3)]
Qc,_ = cgs(A)
Qm,_ = mgs(A)
Qh = Matrix(qr(A).Q)
@show opnorm(Qc'*Qc - I)
@show opnorm(Qm'*Qm - I)
@show opnorm(Qh'*Qh - I);opnorm(Qc' * Qc - I) = 0.5000000000000001
opnorm(Qm' * Qm - I) = 8.164965844469921e-9
opnorm(Qh' * Qh - I) = 4.486834531857287e-16
| Method | Flops (\(m \times n\)) | Stable? | Best for |
|---|---|---|---|
| Householder | \(2mn^2 - \tfrac23 n^3\) | Yes | dense matrices |
| Givens | \(3mn^2 - n^3\) | Yes | sparse / structured matrices |
| Gram-Schmidt | \(2mn^2\) | No (MGS: only mildly) | building orthonormal bases one vector at a time |