Skip to content

Commit e66d3ae

Browse files
Add Singular Value Decomposition and Cholesky Factorization
- Implement SingularValueDecompositionProblem and SingularValueDecompositionSolution - Create kernel method for SVD using eigen and QR decomposition - Add CholeskyFactorizationProblem and CholeskyFactorizationSolution - Implement Cholesky factorization with lower triangular matrix - Update test suites to verify SVD and Cholesky decomposition - Export new decomposition types for easy access
1 parent c8aec3f commit e66d3ae

File tree

4 files changed

+124
-4
lines changed

4 files changed

+124
-4
lines changed

src/eig.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,52 @@ end
116116

117117
kernel(prob::EigenProblemMixin) = Eigen.kernel(prob)
118118
export Eigen
119+
120+
struct SingularValueDecompositionProblem <: ProblemMixin
121+
a::AbstractMatrix
122+
tol::Real
123+
end
124+
125+
struct SingularValueDecompositionSolution <: SolutionMixin
126+
u::AbstractMatrix
127+
s::AbstractMatrix
128+
v::AbstractMatrix
129+
130+
function SingularValueDecompositionSolution(u, s, v)
131+
s = Diagonal(s)
132+
@assert isapprox(I, u' * u) "u must be orthogonal"
133+
@assert isapprox(I, v' * v) "v must be orthogonal"
134+
135+
soln = new(u, s, v)
136+
return soln
137+
end
138+
end
139+
140+
# we cheat by importing eigen and qr from LinearAlgebra
141+
import LinearAlgebra: eigen, qr
142+
function kernel(prob::SingularValueDecompositionProblem)
143+
a = prob.a
144+
tol = prob.tol
145+
146+
n = size(a, 1)
147+
e, c = eigen(a' * a)
148+
q, r = qr(a * c) |> (x -> (x.Q, x.R))
149+
150+
s = zeros(n)
151+
rs = zeros(n, n)
152+
153+
for (i, ei) in enumerate(e)
154+
@assert ei >= (tol * tol) "s2 must be non-negative"
155+
s[i] = sqrt(ei)
156+
rs[:, i] = r[:, i] / s[i]
157+
end
158+
159+
u = Matrix(q)
160+
v = c * rs
161+
162+
soln = SingularValueDecompositionSolution(u, s, v)
163+
return soln
164+
end
165+
166+
SingularValueDecomposition = SingularValueDecompositionProblem
167+
export SingularValueDecomposition

src/lu.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,43 @@ kernel(prob::LUFactorizationProblemMixin) = LU.kernel(prob)
152152

153153
LUFactorization = LU.GaussianEliminationV2
154154
export LUFactorization
155+
156+
struct CholeskyFactorizationProblem <: ProblemMixin
157+
a::AbstractMatrix
158+
tol::Real
159+
function CholeskyFactorizationProblem(a, tol=1e-10)
160+
prob = new(a, tol)
161+
return prob
162+
end
163+
end
164+
165+
struct CholeskyFactorizationSolution <: SolutionMixin
166+
l::AbstractMatrix
167+
function CholeskyFactorizationSolution(l)
168+
@assert istril(l) "matrix must be lower triangular"
169+
soln = new(l)
170+
return soln
171+
end
172+
end
173+
174+
function kernel(prob::CholeskyFactorizationProblem)
175+
tol = prob.tol
176+
l = copy(prob.a)
177+
n = size(l, 1)
178+
179+
for i in 1:n
180+
@assert l[i, i] > tol "Cholesky factorization failed"
181+
l[i, i] = sqrt(l[i, i])
182+
l[i+1:n, i] /= l[i, i]
183+
184+
li = l[i+1:n, i]
185+
l[i+1:n, i+1:n] -= li * li'
186+
end
187+
188+
soln = CholeskyFactorizationSolution(tril(l))
189+
return soln
190+
end
191+
192+
CholeskyFactorization = CholeskyFactorizationProblem
193+
CholeskyDecomposition = CholeskyFactorization
194+
export CholeskyFactorization, CholeskyDecomposition

test/eig.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import SimpleLinearAlgebra.Eigen
77
# Make the first eigenvalue significantly larger
88
a = a * a' # Create a symmetric positive-definite matrix
99

10-
prob = Eigen.PowerMethod(a, 1000, 1e-8)
11-
tol = sqrt(prob.tol * n)
10+
prob = Eigen.PowerMethod(a, 1000, 1e-10)
11+
tol = sqrt(prob.tol) * n
1212

1313
soln = kernel(prob)
1414
e = soln.e[1]
@@ -24,12 +24,28 @@ end
2424
# Make the first eigenvalue significantly larger
2525
a = a * a' # Create a symmetric positive-definite matrix
2626

27-
prob = Eigen.RayleighQuotient(a, 1000, 1e-8)
28-
tol = sqrt(prob.tol * n)
27+
prob = Eigen.RayleighQuotient(a, 1000, 1e-10)
28+
tol = sqrt(prob.tol) * n
2929

3030
soln = kernel(prob)
3131
e = soln.e[1]
3232
c = soln.c[:, 1]
3333

3434
@test isapprox(a * c, e * c, atol=tol)
3535
end
36+
37+
@testset "Singular Value Decomposition" begin
38+
n = 4
39+
a = randn(n, n)
40+
a = a * a' # Create a symmetric positive-definite matrix
41+
42+
prob = SingularValueDecomposition(a, 1e-8)
43+
tol = prob.tol
44+
45+
soln = kernel(prob)
46+
u = soln.u
47+
s = soln.s
48+
v = soln.v
49+
50+
@test isapprox(a, u * s * v', atol=tol)
51+
end

test/lu.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,18 @@ end
7878
@test isapprox(pa, l * u, atol=tol) # Verify PA = LU
7979
@test isapprox(p * a, l * u, atol=tol)
8080
end
81+
82+
@testset "Cholesky factorization" begin
83+
n = 10
84+
q, r = rand(n, n) |> qr
85+
a = q * Diagonal(rand(n)) * q'
86+
87+
prob = CholeskyFactorization(a)
88+
tol = prob.tol
89+
90+
soln = kernel(prob)
91+
l = soln.l
92+
93+
@test istril(l)
94+
@test isapprox(a, l * l', atol=tol)
95+
end

0 commit comments

Comments
 (0)