Skip to content

Commit 1219a12

Browse files
Add QR factorization with modified Gram-Schmidt method
- Implement ModifiedGramSchmidt QR factorization method - Add new QR factorization variant to QR module - Update test suite to include modified Gram-Schmidt implementation - Rename substitution method test files for consistency - Add OMEinsum dependency to Project.toml
1 parent 58a34bc commit 1219a12

File tree

11 files changed

+187
-91
lines changed

11 files changed

+187
-91
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ version = "1.0.0-DEV"
55

66
[deps]
77
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
8+
OMEinsum = "ebe7aa44-baf0-506c-a96f-8464559b3922"
89
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
910

1011
[compat]
1112
LinearAlgebra = "1.10.0"
13+
OMEinsum = "0.8.4"
1214
Printf = "1.11.0"
1315
julia = "1.10"
1416

src/SimpleLinearAlgebra.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module SimpleLinearAlgebra
22

33
using LinearAlgebra, Printf
4-
import Printf.@sprintf # Fixed macro import
4+
using OMEinsum
55

66
abstract type ProblemMixin end
77
abstract type SolutionMixin end
@@ -14,8 +14,8 @@ module SimpleLinearAlgebra
1414
message::String
1515
end
1616

17-
include("forward-substitution.jl")
18-
include("back-substitution.jl")
17+
include("forward.jl")
18+
include("back.jl")
1919
include("lu.jl")
2020
include("qr.jl")
2121

src/back-substitution.jl renamed to src/back.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@ function kernel(prob::BackSubstitutionProblem)
2424
x = zeros(n)
2525

2626
for i in n:-1:1
27-
@assert abs(u[i, i]) > tol "matrix is singular"
28-
x[i] += b[i] / u[i, i]
27+
uii = u[i, i]
28+
@assert abs(uii) > tol "matrix is singular"
29+
x[i] += b[i] / uii
2930

3031
if i == n
3132
continue
3233
end
3334

34-
x[i] -= u[i, i+1:n]' * x[i+1:n] / u[i, i]
35+
ui = u[i, i+1:n] / uii # shape (n-i, )
36+
xi = x[i+1:n] # shape (n-i, )
37+
x[i] -= ui' * xi
3538
end
3639

3740
return BackSubstitutionSolution(x)

src/forward-substitution.jl renamed to src/forward.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,26 @@ end
1616

1717
function kernel(prob::ForwardSubstitutionProblem)
1818
l = prob.l
19+
@assert istril(l) "matrix must be lower triangular"
20+
1921
b = copy(prob.b)
2022
n = length(b)
2123
tol = prob.tol
2224

2325
x = zeros(n)
2426

2527
for i in 1:n
26-
@assert abs(l[i, i]) > tol "matrix is singular"
27-
x[i] += b[i] / l[i, i]
28+
lii = l[i, i]
29+
@assert abs(lii) > tol "matrix is singular"
30+
x[i] += b[i] / lii
2831

2932
if i == 1
3033
continue
3134
end
3235

33-
x[i] -= l[i, 1:i-1]' * x[1:i-1] / l[i, i]
36+
li = l[i, 1:i-1] / lii # shape (i-1, )
37+
xi = x[1:i-1] # shape (i-1, )
38+
x[i] -= li' * xi
3439
end
3540
return ForwardSubstitutionSolution(x)
3641
end

src/lu.jl

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,57 +16,72 @@ module LU
1616
end
1717
end
1818

19-
struct Version1 <: LUFactorizationProblemMixin
19+
struct GaussianEliminationV1 <: LUFactorizationProblemMixin
2020
a::AbstractMatrix
2121
tol::Real
22-
function Version1(a, tol=1e-10)
22+
function GaussianEliminationV1(a, tol=1e-10)
2323
prob = new(a, tol)
2424
return prob
2525
end
2626
end
2727

28-
function kernel(prob::Version1)
28+
function kernel(prob::GaussianEliminationV1)
2929
tol = prob.tol
3030
u = copy(prob.a)
3131
n = size(u, 1)
3232

3333
l = Matrix{Float64}(I, n, n)
34-
35-
for k in 1:n-1
36-
@assert abs(u[k, k]) > tol "Gaussian elimination failed"
37-
38-
m_k = Matrix{Float64}(I, n, n)
39-
m_k[k+1:n, k] = -u[k+1:n, k] / u[k, k]
40-
41-
l = l * inv(m_k)
42-
u = m_k * u
34+
# l * u = a now, we insert resolution of the identity matrix
35+
# l * inv(lk) * lk * u to make l * inv(lk) lower triangular,
36+
# and lk * u to make it upper triangular
37+
# as lk is constructed from gaussian elimination, lk * lu
38+
# will eliminate the k-th column of u
39+
# and as inv(lk) is also lower triangular, the product of
40+
# l * inv(lk) is lower triangular
41+
42+
for i in 1:n-1
43+
uii = u[i, i]
44+
@assert abs(uii) > tol "Gaussian elimination failed"
45+
46+
# lk is lower triangular matrix
47+
li = Matrix{Float64}(I, n, n)
48+
li[i+1:n, i] -= u[i+1:n, i] / uii
49+
li_inv = inv(li)
50+
51+
l = l * li_inv
52+
u = li * u
4353
end
4454

4555
soln = LUFactorizationSolution(tril(l), triu(u))
4656
return soln
4757
end
4858

49-
struct Version2 <: LUFactorizationProblemMixin
59+
struct GaussianEliminationV2 <: LUFactorizationProblemMixin
5060
a::AbstractMatrix
5161
tol::Real
52-
function Version2(a, tol=1e-10)
62+
function GaussianEliminationV2(a, tol=1e-10)
5363
prob = new(a, tol)
5464
return prob
5565
end
5666
end
5767

58-
function kernel(prob::Version2)
68+
function kernel(prob::GaussianEliminationV2)
5969
tol = prob.tol
6070
u = copy(prob.a)
6171
n = size(u, 1)
6272

6373
# initialize l to be the identity matrix
6474
l = Matrix{Float64}(I, n, n)
6575

66-
for k in 1:n-1
67-
@assert abs(u[k, k]) > tol "Gaussian elimination failed"
68-
l[k+1:n, k] = u[k+1:n, k] / u[k, k]
69-
u[k+1:n, k+1:n] -= l[k+1:n, k] * u[k, k+1:n]'
76+
for i in 1:n-1
77+
uii = u[i, i]
78+
@assert abs(uii) > tol "Gaussian elimination failed"
79+
80+
ci = u[i+1:n, i] / uii # shape (n-i, ), the i-th column of U
81+
ri = u[i, i+1:n] # shape (1, n-i), the i-th row of U
82+
83+
l[i+1:n, i] = ci
84+
u[i+1:n, i+1:n] -= ci * ri'
7085
end
7186

7287
soln = LUFactorizationSolution(tril(l), triu(u))
@@ -99,29 +114,34 @@ module LU
99114
tol = prob.tol
100115
u = copy(prob.a)
101116
n = size(u, 1)
102-
117+
118+
# initialize l to be the identity matrix
103119
l = zeros(n, n)
104120
p = collect(1:n)
105121

106-
for k in 1:n-1
107-
v, x = findmax(i->abs(u[i, k]), k:n)
108-
x += k - 1
122+
for i in 1:n-1
123+
# v is the max element, x is the index
124+
v, j = findmax(u[i:n, i])
125+
j += i - 1
109126

110-
if x != k
111-
u[k, :], u[x, :] = u[x, :], u[k, :]
112-
l[k, :], l[x, :] = l[x, :], l[k, :]
113-
p[k], p[x] = p[x], p[k]
127+
if i != j # if the max element is not on the diagonal
128+
u[i, :], u[j, :] = u[j, :], u[i, :]
129+
l[i, :], l[j, :] = l[j, :], l[i, :]
130+
p[i], p[j] = p[j], p[i]
114131
end
115-
116-
if abs(u[k, k]) < tol
132+
133+
uii = u[i, i]
134+
if abs(uii) < tol
117135
continue
118136
end
119-
120-
l[k, k] = 1.0
121-
l[k+1:n, k] = u[k+1:n, k] / u[k, k]
122-
u[k+1:n, k+1:n] -= l[k+1:n, k] * u[k, k+1:n]'
137+
138+
l[i, i] = 1.0
139+
ri = u[i, i+1:n]
140+
ci = u[i+1:n, i] / uii
141+
l[i+1:n, i] = ci
142+
u[i+1:n, i+1:n] -= ci * ri'
123143
end
124-
144+
125145
l[n, n] = 1.0
126146
soln = PartialPivotingLUFactorizationSolution(tril(l), triu(u), p)
127147
return soln
@@ -130,5 +150,5 @@ end
130150

131151
kernel(prob::LUFactorizationProblemMixin) = LU.kernel(prob)
132152

133-
LUFactorization = LU.Version2
153+
LUFactorization = LU.GaussianEliminationV2
134154
export LUFactorization

src/qr.jl

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@ module QR
3030
q = Matrix{Float64}(I, n, n)
3131
r = copy(prob.a)
3232

33-
for k in 1:n
33+
for i in 1:n
3434
# Get the column vector below and including the diagonal
35-
v = copy(r[k:n, k])
35+
vi = copy(r[i:n, i])
3636

37-
h = Matrix{Float64}(I, n, n)
38-
beta = -sign(v[1]) * norm(v)
39-
v[1] -= beta
40-
h[k:n, k:n] -= 2.0 * v * v' / dot(v, v)
37+
hi = Matrix{Float64}(I, n, n)
38+
beta = -sign(vi[1]) * norm(vi)
39+
vi[1] -= beta
40+
hi[i:n, i:n] -= 2.0 * vi * vi' / dot(vi, vi)
4141

4242
# Apply reflection to R and update Q
43-
r = h * r
44-
q = q * h
43+
r = hi * r
44+
q = q * hi
4545
end
4646

4747
soln = QRFactorizationSolution(q, triu(r))
@@ -62,22 +62,23 @@ module QR
6262
q = Matrix{Float64}(I, n, n)
6363
r = copy(prob.a)
6464

65-
for i in 1:n
66-
for j in i+1:n
67-
x, y = r[i, i], r[j, i]
68-
c = x / sqrt(x^2 + y^2)
69-
s = y / sqrt(x^2 + y^2)
65+
ij = [(i, j) for i in 1:n for j in i+1:n]
66+
for (i, j) in ij
67+
x, y = r[i, i], r[j, i]
68+
t = atan(y, x)
7069

71-
if abs(s) < prob.tol
72-
continue
73-
end
70+
if abs(y) < prob.tol
71+
continue
72+
end
7473

75-
h = Matrix{Float64}(I, n, n)
76-
h[[i, j], [i, j]] = [c -s; s c]
74+
# Manually calculate hij' * r and q * hij
75+
ri = cos(t) * r[i, :] + sin(t) * r[j, :]
76+
rj = -sin(t) * r[i, :] + cos(t) * r[j, :]
77+
r[i, :], r[j, :] = ri, rj
7778

78-
r = h' * r
79-
q = q * h
80-
end
79+
qi = cos(t) * q[:, i] + sin(t) * q[:, j]
80+
qj = -sin(t) * q[:, i] + cos(t) * q[:, j]
81+
q[:, i], q[:, j] = qi, qj
8182
end
8283
return QRFactorizationSolution(q, triu(r))
8384
end
@@ -105,12 +106,13 @@ module QR
105106
q[:, 1] /= r[1, 1]
106107

107108
for i in 2:n
108-
# q[:, i] = a[:, i]
109-
110-
for j in 1:i-1
111-
r[j, i] = dot(q[:, j], a[:, i])
112-
q[:, i] -= q[:, j] * r[j, i]
113-
end
109+
# Method 1:
110+
qi = q[:, 1:i-1] # shape (n, i-1)
111+
ai = a[:, i] # shape (n, )
112+
ri = qi' * ai # shape (i-1, )
113+
114+
r[1:i-1, i] = ri
115+
q[:, i] -= qi * ri
114116

115117
r[i, i] = norm(q[:, i])
116118
@assert r[i, i] > tol "Singular matrix"
@@ -120,6 +122,47 @@ module QR
120122

121123
return QRFactorizationSolution(q, triu(r))
122124
end
125+
126+
struct ModifiedGramSchmidt <: QRFactorizationProblem
127+
a::AbstractMatrix
128+
tol::Real
129+
function ModifiedGramSchmidt(a, tol=1e-10)
130+
prob = new(a, tol)
131+
return prob
132+
end
133+
end
134+
135+
function kernel(prob::ModifiedGramSchmidt)
136+
a = copy(prob.a)
137+
tol = prob.tol
138+
139+
n = size(a, 1)
140+
q = zeros(n, n)
141+
r = zeros(n, n)
142+
143+
for i in 1:n
144+
# vi is normalized vector of a[:, i]
145+
# is orthogonal to all the vectors
146+
# before i
147+
ni = norm(a[:, i])
148+
vi = a[:, i] / ni # shape (n, )
149+
@assert size(vi) == (n, )
150+
151+
# ai contains all the vectors
152+
# after i
153+
ai = a[:, i+1:n] # shape (n, n-i)
154+
# ri is the projection of vi into the
155+
# basis spanned by ai
156+
ri = vi' * ai # shape (n-i, )
157+
@assert size(ri) == (1, n-i)
158+
159+
r[i, i] = ni
160+
r[i, i+1:n] = ri
161+
q[:, i] = vi
162+
a[:, i+1:n] -= vi * ri
163+
end
164+
return QRFactorizationSolution(q, triu(r))
165+
end
123166
end
124167

125168
kernel(prob::QRFactorizationProblem) = QR.kernel(prob)

test/back-substitution.jl renamed to test/back.jl

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
using LinearAlgebra
2+
13
@testset "backward substitution" begin
24
n = 10
3-
u = LinearAlgebra.triu(rand(n, n))
5+
u = triu(rand(n, n))
46
b = rand(n)
57

6-
p = BackSubstitution(u, b)
7-
s = kernel(p)
8-
x = s.x
9-
tol = p.tol
8+
prob = BackSubstitution(u, b)
9+
tol = prob.tol
10+
11+
soln = kernel(prob)
12+
x = soln.x
1013

1114
@test isapprox(u * x, b, atol=tol)
1215
end

0 commit comments

Comments
 (0)