Skip to content

Commit 9a72847

Browse files
committed
Make the algorithm for real powers of a matrix robust
1 parent 1f97591 commit 9a72847

File tree

4 files changed

+313
-91
lines changed

4 files changed

+313
-91
lines changed

base/linalg/dense.jl

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,17 +169,50 @@ kron(a::Vector, b::Vector)=vec(kron(reshape(a,length(a),1),reshape(b,length(b),1
169169
kron(a::Matrix, b::Vector)=kron(a,reshape(b,length(b),1))
170170
kron(a::Vector, b::Matrix)=kron(reshape(a,length(a),1),b)
171171

172-
^(A::Matrix, p::Integer) = p < 0 ? inv(A^-p) : Base.power_by_squaring(A,p)
173-
174-
function ^(A::Matrix, p::Number)
172+
# Matrix power
173+
^(A::AbstractMatrix, p::Integer) = p < 0 ? Base.power_by_squaring(inv(A),-p) : Base.power_by_squaring(A,p)
174+
function ^(A::AbstractMatrix, p::Real)
175+
# For integer powers, use repeated squaring
175176
if isinteger(p)
176177
return A^Integer(real(p))
177178
end
178-
chksquare(A)
179-
v, X = eig(A)
180-
any(v.<0) && (v = complex(v))
181-
Xinv = ishermitian(A) ? X' : inv(X)
182-
scale(X, v.^p)*Xinv
179+
180+
# If possible, use diagonalization
181+
if issym(A)
182+
return full(Symmetric(A)^p)
183+
end
184+
if ishermitian(A)
185+
return full(Hermitian(A)^p)
186+
end
187+
188+
# Otherwise, use Schur decomposition
189+
n = chksquare(A)
190+
if istriu(A)
191+
retmat = full(UpperTriangular(A)^p)
192+
d = diag(A)
193+
else
194+
S,Q,d = schur(complex(A))
195+
R = UpperTriangular(S)^p
196+
retmat = Q * R * Q'
197+
end
198+
199+
# Check whether the matrix has nonpositive real eigs
200+
np_real_eigs = false
201+
for i = 1:n
202+
if imag(d[i]) < eps() && real(d[i]) <= 0
203+
np_real_eigs = true
204+
break
205+
end
206+
end
207+
if np_real_eigs
208+
warn("Matrix with nonpositive real eigenvalues, a nonprincipal matrix power will be returned.")
209+
end
210+
211+
if isreal(A) && ~np_real_eigs
212+
return real(retmat)
213+
else
214+
return retmat
215+
end
183216
end
184217

185218
# Matrix exponential
@@ -286,6 +319,9 @@ expm(x::Number) = exp(x)
286319

287320
function logm(A::StridedMatrix)
288321
# If possible, use diagonalization
322+
if issym(A)
323+
return full(logm(Symmetric(A)))
324+
end
289325
if ishermitian(A)
290326
return full(logm(Hermitian(A)))
291327
end

base/linalg/diagonal.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,14 @@ end
118118
# identity matrices via eye(Diagonal{type},n)
119119
eye{T}(::Type{Diagonal{T}}, n::Int) = Diagonal(ones(T,n))
120120

121-
expm(D::Diagonal) = Diagonal(exp(D.diag))
122-
logm(D::Diagonal) = Diagonal(log(D.diag))
123-
sqrtm(D::Diagonal) = Diagonal(sqrt(D.diag))
121+
# Matrix functions
122+
^(D::Diagonal, p::Integer) = Diagonal((D.diag).^p)
123+
^(D::Diagonal, p::Real) = Diagonal((D.diag).^p)
124+
for (funm, func) in ([:expm,:exp], [:sqrtm,:sqrt], [:logm,:log])
125+
@eval begin
126+
($funm)(D::Diagonal) = Diagonal(($func)(D.diag))
127+
end
128+
end
124129

125130
#Linear solver
126131
function A_ldiv_B!(D::Diagonal, B::StridedVecOrMat)

base/linalg/symmetric.jl

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,54 @@ function svdvals!{T<:Real,S}(A::Union{Hermitian{T,S}, Symmetric{T,S}, Hermitian{
131131
return sort!(vals, rev = true)
132132
end
133133

134-
#Matrix-valued functions
134+
#Matrix functions
135+
function ^(A::Symmetric, p::Integer)
136+
if p < 0
137+
return Symmetric(Base.power_by_squaring(inv(A), -p))
138+
else
139+
return Symmetric(Base.power_by_squaring(A, p))
140+
end
141+
end
142+
function ^(A::Symmetric, p::Real)
143+
F = eigfact(full(A))
144+
if isposdef(F)
145+
retmat = (F.vectors * Diagonal((F.values).^p)) * F.vectors'
146+
else
147+
retmat = (F.vectors * Diagonal((complex(F.values)).^p)) * F.vectors'
148+
end
149+
return Symmetric(retmat)
150+
end
151+
function ^(A::Hermitian, p::Integer)
152+
n = chksquare(A)
153+
if p < 0
154+
retmat = Base.power_by_squaring(inv(A), -p)
155+
else
156+
retmat = Base.power_by_squaring(A, p)
157+
end
158+
for i = 1:n
159+
retmat[i,i] = real(retmat[i,i])
160+
end
161+
return Hermitian(retmat)
162+
end
163+
function ^{T}(A::Hermitian{T}, p::Real)
164+
n = chksquare(A)
165+
F = eigfact(A)
166+
if isposdef(F)
167+
retmat = (F.vectors * Diagonal((F.values).^p)) * F.vectors'
168+
if T <: Real
169+
return Hermitian(retmat)
170+
else
171+
for i = 1:n
172+
retmat[i,i] = real(retmat[i,i])
173+
end
174+
return Hermitian(retmat)
175+
end
176+
else
177+
retmat = (F.vectors * Diagonal((complex(F.values).^p))) * F.vectors'
178+
return retmat
179+
end
180+
end
181+
135182
function expm(A::Symmetric)
136183
F = eigfact(full(A))
137184
return Symmetric((F.vectors * Diagonal(exp(F.values))) * F.vectors')
@@ -151,9 +198,7 @@ function expm{T}(A::Hermitian{T})
151198
end
152199

153200
for (funm, func) in ([:logm,:log], [:sqrtm,:sqrt])
154-
155201
@eval begin
156-
157202
function ($funm)(A::Symmetric)
158203
F = eigfact(full(A))
159204
if isposdef(F)
@@ -182,7 +227,5 @@ for (funm, func) in ([:logm,:log], [:sqrtm,:sqrt])
182227
return retmat
183228
end
184229
end
185-
186230
end
187-
188231
end

0 commit comments

Comments
 (0)