Skip to content

Commit 0630a3a

Browse files
Merge pull request #76 from JuliaLinearAlgebra/rms-jordan
Add the Jordan canonical form
2 parents cbc6178 + 892043b commit 0630a3a

File tree

6 files changed

+586
-6
lines changed

6 files changed

+586
-6
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "MatrixFactorizations"
22
uuid = "a3b82374-2e81-5b9e-98ce-41277c0e4c87"
3-
version = "3.1.2"
3+
version = "3.1.3"
44

55
[deps]
66
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"

README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ MatrixFactorizations.QL{Float64,Array{Float64,2}}
2323
Q factor:
2424
5×5 MatrixFactorizations.QLPackedQ{Float64,Array{Float64,2}}:
2525
0.155574 -0.112555 -0.686362 -0.701064 0.0233276
26-
0.363037 -0.0583277 -0.0329428 0.152682 0.916736
27-
-0.670742 0.294355 -0.547867 0.347157 0.206843
28-
-0.61094 -0.566041 0.324432 -0.353128 0.276396
29-
0.144425 -0.759527 -0.349868 0.489877 -0.199681
26+
0.363037 -0.0583277 -0.0329428 0.152682 0.916736
27+
-0.670742 0.294355 -0.547867 0.347157 0.206843
28+
-0.61094 -0.566041 0.324432 -0.353128 0.276396
29+
0.144425 -0.759527 -0.349868 0.489877 -0.199681
3030
L factor:
3131
5×5 Array{Float64,2}:
3232
-1.75734 0.0 0.0 0.0 0.0
@@ -38,3 +38,35 @@ L factor:
3838
julia> b = randn(5); ql(A) \ b A \ b
3939
true
4040
```
41+
42+
## Jordan canonical form
43+
44+
Every square matrix has a Jordan canonical form. Although Jordan blocks are unstable
45+
with respect to perturbations, the Jordan canonical form of a rational matrix with
46+
rational eigenvalues can be found exactly:
47+
```julia
48+
49+
julia> A = [0 0 1 7 -1; -5 -6 -6 -35 5; 1 1 -7 7 -1; 0 0 0 -9 0; 2 1 -5 -42 -3];
50+
51+
julia> λ = [-9, -7, -7, -1, -1//1];
52+
53+
julia> V, J = jordan(A, λ)
54+
Jordan{Rational{Int64}, Matrix{Rational{Int64}}, Matrix{Rational{Int64}}}
55+
Generalized eigenvectors:
56+
5×5 Matrix{Rational{Int64}}:
57+
0 0 0 1 0
58+
0 1 0 0 -1
59+
0 1 -1 0 0
60+
1 0 0 0 0
61+
7 1 -1 1 -1
62+
Jordan normal form:
63+
5×5 Matrix{Rational{Int64}}:
64+
-9 0 0 0 0
65+
0 -7 1 0 0
66+
0 0 -7 0 0
67+
0 0 0 -1 1
68+
0 0 0 0 -1
69+
70+
julia> A == V*J/V
71+
true
72+
```

src/MatrixFactorizations.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import ArrayLayouts: reflector!, reflectorApply!, materialize!, @_layoutlmul, @_
3434

3535
export ul, ul!, ql, ql!, qrunblocked, qrunblocked!, UL, QL,
3636
reversecholesky, reversecholesky!, ReverseCholesky,
37-
lulinv, lulinv!, LULinv
37+
lulinv, lulinv!, LULinv, jordan, Jordan
3838

3939
const AdjointQtype = isdefined(LinearAlgebra, :AdjointQ) ? LinearAlgebra.AdjointQ : Adjoint
4040
const AbstractQtype = AbstractQ <: AbstractMatrix ? AbstractMatrix : AbstractQ
@@ -125,5 +125,6 @@ include("rq.jl")
125125
include("polar.jl")
126126
include("reversecholesky.jl")
127127
include("lulinv.jl")
128+
include("jordan.jl")
128129

129130
end #module

src/jordan.jl

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
"""
2+
Jordan <: Factorization
3+
4+
Jordan canonical form of a square matrix `A = VJV⁻¹`. This
5+
is the return type of [`jordan`](@ref), the corresponding Jordan factorization function.
6+
7+
The individual components of the factorization `F::Jordan` can be accessed via [`getfield`](@ref):
8+
9+
| Component | Description |
10+
|:----------|:-----------------------------|
11+
| `F.V` | `V` generalized eigenvectors |
12+
| `F.J` | `J` Jordan normal form |
13+
14+
Iterating the factorization produces the components `F.V` and `F.J`.
15+
16+
# Examples
17+
```jldoctest
18+
julia> A = [5 4 2 1; 0 1 -1 -1; -1 -1 3 0; 1 1 -1 2//1]
19+
20+
julia> λ = [1, 2, 4, 4//1] # you will almost certainly need extremely accurate eigenvalues to proceed
21+
22+
julia> F = jordan(A, λ)
23+
Jordan{Rational{Int64}, Matrix{Rational{Int64}}, Matrix{Rational{Int64}}}
24+
Generalized eigenvectors:
25+
4×4 Matrix{Rational{Int64}}:
26+
1 1 -1 -1
27+
-1 -1 0 0
28+
0 0 1 0
29+
0 1 -1 0
30+
Jordan normal form:
31+
4×4 Matrix{Rational{Int64}}:
32+
1 0 0 0
33+
0 2 0 0
34+
0 0 4 1
35+
0 0 0 4
36+
37+
julia> A*F.V == F.V*F.J
38+
true
39+
40+
julia> V, J = jordan(A, λ); # destructuring via iteration
41+
42+
julia> V == F.V && J == F.J
43+
true
44+
```
45+
"""
46+
struct Jordan{T, R <: AbstractMatrix{T}, S <: AbstractMatrix{T}} <: Factorization{T}
47+
V::R
48+
J::S
49+
end
50+
51+
Jordan{T}(V::AbstractMatrix, J::AbstractMatrix) where T = Jordan(convert(AbstractMatrix{T}, V), convert(AbstractMatrix{T}, J))
52+
Jordan{T}(F::Jordan) where T = Jordan{T}(F.V, F.J)
53+
54+
iterate(F::Jordan) = (F.V, Val(:J))
55+
iterate(F::Jordan, ::Val{:J}) = (F.J, Val(:done))
56+
iterate(F::Jordan, ::Val{:done}) = nothing
57+
58+
function show(io::IO, mime::MIME{Symbol("text/plain")}, F::Jordan)
59+
summary(io, F); println(io)
60+
println(io, "Generalized eigenvectors:")
61+
show(io, mime, F.V)
62+
println(io, "\nJordan normal form:")
63+
show(io, mime, F.J)
64+
end
65+
66+
# dangerous because Jordan blocks are unstable with respect to small perturbations
67+
jordan(A::AbstractMatrix) = jordan(A, eigvals(A))
68+
69+
function jordan(A::AbstractMatrix{S}, λ::AbstractVector{T}) where {S, T}
70+
V = promote_type(S, T)
71+
jordan(convert(AbstractMatrix{V}, A), convert(AbstractVector{V}, λ))
72+
end
73+
74+
function jordan(A::AbstractMatrix{T}, λ::AbstractVector{T}) where T
75+
PLEP, B = block_diagonalize(A, λ)
76+
F, J = block_diagonal_to_jordan(B)
77+
V = PLEP*F
78+
return Jordan(V, J)
79+
end
80+
81+
function triangular_to_psychologically_block_diagonal(U::UpperTriangular{T, <: AbstractMatrix{T}}) where T
82+
n = checksquare(U)
83+
R = deepcopy(U)
84+
EACC = UpperTriangular(Matrix{T}(I, n, n))
85+
# note that this is sensitive to the order of conjugation.
86+
for j in 2:n
87+
for i in j-1:-1:1
88+
if R[i, i] != R[j, j]
89+
E = UpperTriangular(Matrix{T}(I, n, n))
90+
E[i, j] = R[i, j]/(R[j, j] - R[i, i])
91+
R = E\R*E
92+
EACC = EACC*E
93+
end
94+
end
95+
end
96+
return EACC, R
97+
end
98+
99+
function psychologically_block_diagonal_to_block_diagonal(R::UpperTriangular{T, <: AbstractMatrix{T}}) where T
100+
p = sortperm(diag(R))
101+
n = length(p)
102+
P = Matrix{Int}(I, n, n)[:, p]
103+
B = R[p, p]
104+
P, B
105+
end
106+
107+
function triangular_to_block_diagonal(U::UpperTriangular{T, <: AbstractMatrix{T}}) where T
108+
E, R = triangular_to_psychologically_block_diagonal(U)
109+
p = sortperm(diag(R))
110+
E[:, p], R[p, p]
111+
end
112+
113+
function block_diagonalize(A::AbstractMatrix{T}, λ::AbstractVector{T}) where T
114+
F = lulinv(A, λ)
115+
PLEP, B = triangular_to_block_diagonal(UpperTriangular(F.factors))
116+
lmul!(UnitLowerTriangular(F.factors), PLEP)
117+
F.P*PLEP, B
118+
end
119+
120+
function determine_block_sizes(B::AbstractMatrix{T}) where T
121+
n = checksquare(B)
122+
if n == 1
123+
m = [1]
124+
return m
125+
else
126+
m = Int[]
127+
i = 1
128+
while i < n
129+
t = 1
130+
while (i < n-1) && (B[i, i] == B[i+1, i+1])
131+
i += 1
132+
t += 1
133+
end
134+
if i == n-1
135+
if B[i, i] == B[i+1, i+1]
136+
t += 1
137+
push!(m, t)
138+
else
139+
push!(m, t)
140+
push!(m, 1)
141+
end
142+
else
143+
push!(m, t)
144+
end
145+
i += 1
146+
end
147+
return m
148+
end
149+
end
150+
151+
function block_diagonal_to_jordan(B::Matrix{T}) where T
152+
n = checksquare(B)
153+
m = determine_block_sizes(B)
154+
cm = cumsum(m)
155+
pushfirst!(cm, 0)
156+
F = zeros(T, n, n)
157+
J = zeros(T, n, n)
158+
for i in 1:length(m)
159+
ir = cm[i]+1:cm[i+1]
160+
FB, JB = upper_triangular_block_to_jordan_blocks(B[ir, ir])
161+
F[ir, ir] .= FB
162+
J[ir, ir] .= JB
163+
end
164+
return F, J
165+
end
166+
167+
# Contract: B_{i, j} = 0 for i > j. B_{i, i} = B_{j, j} for all i ≠ j.
168+
function upper_triangular_block_to_jordan_blocks(B::Matrix{T}) where T
169+
n = checksquare(B)
170+
J = deepcopy(B)
171+
FACC = Matrix{T}(I, n, n)
172+
for j in 2:n
173+
# In column j, we shall introduce zeros in any row with a 1 in the {i, i+1} position.
174+
F = Matrix{T}(I, n, n)
175+
for i in 1:j-2
176+
if !iszero(J[i, i+1])
177+
F[i+1, j] = -J[i, j]
178+
end
179+
end
180+
J = F\J*F
181+
FACC = FACC*F
182+
while count(!iszero, J[1:j-1, j]) > 1
183+
# Next, we identify the first and next nonzeros in the last column. By the first step, they are across from the last row of a Jordan block.
184+
i1 = 1
185+
while i1 < j
186+
if !iszero(J[i1, j])
187+
break
188+
else
189+
i1 += 1
190+
end
191+
end
192+
i2 = i1+1
193+
while i2 < j
194+
if !iszero(J[i2, j])
195+
break
196+
else
197+
i2 += 1
198+
end
199+
end
200+
# With i1 and i2, we find the sizes of the corresponding Jordan blocks, s and t.
201+
i1s = i1
202+
while i1s > 1
203+
if iszero(J[i1s-1, i1s])
204+
break
205+
else
206+
i1s -= 1
207+
end
208+
end
209+
i2s = i2
210+
while i2s > 1
211+
if iszero(J[i2s-1, i2s])
212+
break
213+
else
214+
i2s -= 1
215+
end
216+
end
217+
i1r = i1s:i1
218+
i2r = i2s:i2
219+
s = length(i1r)
220+
t = length(i2r)
221+
# Eliminate one of the nonzeros or the other.
222+
if s t
223+
# eliminate α
224+
F = Matrix{T}(I, n, n)
225+
α = J[i1, j]
226+
β = J[i2, j]
227+
γ = α/β
228+
F[i1r, i2-s+1:i2] .= Matrix{T}*I, s, s)
229+
J = F\J*F
230+
FACC = FACC*F
231+
else
232+
# eliminate β
233+
F = Matrix{T}(I, n, n)
234+
α = J[i1, j]
235+
β = J[i2, j]
236+
γ = β/α
237+
F[i2r, i1-t+1:i1] .= Matrix{T}*I, t, t)
238+
J = F\J*F
239+
FACC = FACC*F
240+
end
241+
end
242+
# Next, we must permute to get the final nonzero to the bottom, if there is one at all.
243+
i1 = 1
244+
while i1 < j
245+
if !iszero(J[i1, j])
246+
break
247+
else
248+
i1 += 1
249+
end
250+
end
251+
if i1 < j-1
252+
# With i1, we find the size of the corresponding Jordan block, s.
253+
i1s = i1
254+
while i1s > 1
255+
if iszero(J[i1s-1, i1s])
256+
break
257+
else
258+
i1s -= 1
259+
end
260+
end
261+
i1r = i1s:i1
262+
s = length(i1r)
263+
p = [1:i1s-1; i1+1:j-1; i1r; j:n]
264+
#P = Matrix{T}(I, n, n)[:, p]
265+
#J = P'J*P
266+
#FACC = FACC*P
267+
J = J[p, p]
268+
FACC = FACC[:, p]
269+
end
270+
# Finally, we must scale off the nonzero entry to 1 to get it to conform to a Jordan block.
271+
if !iszero(J[j-1, j])
272+
F = Matrix{T}(I, n, n)
273+
F[j, j] = inv(J[j-1, j])
274+
J = F\J*F
275+
FACC = FACC*F
276+
end
277+
end
278+
return FACC, J
279+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ include("test_rq.jl")
3737
include("test_polar.jl")
3838
include("test_reversecholesky.jl")
3939
include("test_lulinv.jl")
40+
include("test_jordan.jl")
4041

4142
include("test_banded.jl")

0 commit comments

Comments
 (0)