Line | Exclusive | Inclusive | Code |
---|---|---|---|
1 | # This file is a part of Julia. License is MIT: https://julialang.org/license | ||
2 | |||
3 | # Symmetric and Hermitian matrices | ||
4 | struct Symmetric{T,S<:AbstractMatrix{<:T}} <: AbstractMatrix{T} | ||
5 | data::S | ||
6 | uplo::Char | ||
7 | |||
8 | function Symmetric{T,S}(data, uplo::Char) where {T,S<:AbstractMatrix{<:T}} | ||
9 | require_one_based_indexing(data) | ||
10 | (uplo != 'U' && uplo != 'L') && throw_uplo() | ||
11 | new{T,S}(data, uplo) | ||
12 | end | ||
13 | end | ||
14 | """ | ||
15 | Symmetric(A, uplo=:U) | ||
16 | |||
17 | Construct a `Symmetric` view of the upper (if `uplo = :U`) or lower (if `uplo = :L`) | ||
18 | triangle of the matrix `A`. | ||
19 | |||
20 | `Symmetric` views are mainly useful for real-symmetric matrices, for which | ||
21 | specialized algorithms (e.g. for eigenproblems) are enabled for `Symmetric` types. | ||
22 | More generally, see also [`Hermitian(A)`](@ref) for Hermitian matrices `A == A'`, which | ||
23 | is effectively equivalent to `Symmetric` for real matrices but is also useful for | ||
24 | complex matrices. (Whereas complex `Symmetric` matrices are supported but have few | ||
25 | if any specialized algorithms.) | ||
26 | |||
27 | To compute the symmetric part of a real matrix, or more generally the Hermitian part `(A + A') / 2` of | ||
28 | a real or complex matrix `A`, use [`hermitianpart`](@ref). | ||
29 | |||
30 | # Examples | ||
31 | ```jldoctest | ||
32 | julia> A = [1 2 3; 4 5 6; 7 8 9] | ||
33 | 3×3 Matrix{Int64}: | ||
34 | 1 2 3 | ||
35 | 4 5 6 | ||
36 | 7 8 9 | ||
37 | |||
38 | julia> Supper = Symmetric(A) | ||
39 | 3×3 Symmetric{Int64, Matrix{Int64}}: | ||
40 | 1 2 3 | ||
41 | 2 5 6 | ||
42 | 3 6 9 | ||
43 | |||
44 | julia> Slower = Symmetric(A, :L) | ||
45 | 3×3 Symmetric{Int64, Matrix{Int64}}: | ||
46 | 1 4 7 | ||
47 | 4 5 8 | ||
48 | 7 8 9 | ||
49 | |||
50 | julia> hermitianpart(A) | ||
51 | 3×3 Hermitian{Float64, Matrix{Float64}}: | ||
52 | 1.0 3.0 5.0 | ||
53 | 3.0 5.0 7.0 | ||
54 | 5.0 7.0 9.0 | ||
55 | ``` | ||
56 | |||
57 | Note that `Supper` will not be equal to `Slower` unless `A` is itself symmetric (e.g. if | ||
58 | `A == transpose(A)`). | ||
59 | """ | ||
60 | function Symmetric(A::AbstractMatrix, uplo::Symbol=:U) | ||
61 | checksquare(A) | ||
62 | return symmetric_type(typeof(A))(A, char_uplo(uplo)) | ||
63 | end | ||
64 | |||
65 | """ | ||
66 | symmetric(A, uplo=:U) | ||
67 | |||
68 | Construct a symmetric view of `A`. If `A` is a matrix, `uplo` controls whether the upper | ||
69 | (if `uplo = :U`) or lower (if `uplo = :L`) triangle of `A` is used to implicitly fill the | ||
70 | other one. If `A` is a `Number`, it is returned as is. | ||
71 | |||
72 | If a symmetric view of a matrix is to be constructed of which the elements are neither | ||
73 | matrices nor numbers, an appropriate method of `symmetric` has to be implemented. In that | ||
74 | case, `symmetric_type` has to be implemented, too. | ||
75 | """ | ||
76 | symmetric(A::AbstractMatrix, uplo::Symbol) = Symmetric(A, uplo) | ||
77 | symmetric(A::Number, ::Symbol) = A | ||
78 | |||
79 | """ | ||
80 | symmetric_type(T::Type) | ||
81 | |||
82 | The type of the object returned by `symmetric(::T, ::Symbol)`. For matrices, this is an | ||
83 | appropriately typed `Symmetric`, for `Number`s, it is the original type. If `symmetric` is | ||
84 | implemented for a custom type, so should be `symmetric_type`, and vice versa. | ||
85 | """ | ||
86 | function symmetric_type(::Type{T}) where {S, T<:AbstractMatrix{S}} | ||
87 | return Symmetric{Union{S, promote_op(transpose, S), symmetric_type(S)}, T} | ||
88 | end | ||
89 | function symmetric_type(::Type{T}) where {S<:Number, T<:AbstractMatrix{S}} | ||
90 | return Symmetric{S, T} | ||
91 | end | ||
92 | function symmetric_type(::Type{T}) where {S<:AbstractMatrix, T<:AbstractMatrix{S}} | ||
93 | return Symmetric{AbstractMatrix, T} | ||
94 | end | ||
95 | symmetric_type(::Type{T}) where {T<:Number} = T | ||
96 | |||
97 | struct Hermitian{T,S<:AbstractMatrix{<:T}} <: AbstractMatrix{T} | ||
98 | data::S | ||
99 | uplo::Char | ||
100 | |||
101 | function Hermitian{T,S}(data, uplo::Char) where {T,S<:AbstractMatrix{<:T}} | ||
102 | require_one_based_indexing(data) | ||
103 | (uplo != 'U' && uplo != 'L') && throw_uplo() | ||
104 | new{T,S}(data, uplo) | ||
105 | end | ||
106 | end | ||
107 | """ | ||
108 | Hermitian(A, uplo=:U) | ||
109 | |||
110 | Construct a `Hermitian` view of the upper (if `uplo = :U`) or lower (if `uplo = :L`) | ||
111 | triangle of the matrix `A`. | ||
112 | |||
113 | To compute the Hermitian part of `A`, use [`hermitianpart`](@ref). | ||
114 | |||
115 | # Examples | ||
116 | ```jldoctest | ||
117 | julia> A = [1 2+2im 3-3im; 4 5 6-6im; 7 8+8im 9] | ||
118 | 3×3 Matrix{Complex{Int64}}: | ||
119 | 1+0im 2+2im 3-3im | ||
120 | 4+0im 5+0im 6-6im | ||
121 | 7+0im 8+8im 9+0im | ||
122 | |||
123 | julia> Hupper = Hermitian(A) | ||
124 | 3×3 Hermitian{Complex{Int64}, Matrix{Complex{Int64}}}: | ||
125 | 1+0im 2+2im 3-3im | ||
126 | 2-2im 5+0im 6-6im | ||
127 | 3+3im 6+6im 9+0im | ||
128 | |||
129 | julia> Hlower = Hermitian(A, :L) | ||
130 | 3×3 Hermitian{Complex{Int64}, Matrix{Complex{Int64}}}: | ||
131 | 1+0im 4+0im 7+0im | ||
132 | 4+0im 5+0im 8-8im | ||
133 | 7+0im 8+8im 9+0im | ||
134 | |||
135 | julia> hermitianpart(A) | ||
136 | 3×3 Hermitian{ComplexF64, Matrix{ComplexF64}}: | ||
137 | 1.0+0.0im 3.0+1.0im 5.0-1.5im | ||
138 | 3.0-1.0im 5.0+0.0im 7.0-7.0im | ||
139 | 5.0+1.5im 7.0+7.0im 9.0+0.0im | ||
140 | ``` | ||
141 | |||
142 | Note that `Hupper` will not be equal to `Hlower` unless `A` is itself Hermitian (e.g. if `A == adjoint(A)`). | ||
143 | |||
144 | All non-real parts of the diagonal will be ignored. | ||
145 | |||
146 | ```julia | ||
147 | Hermitian(fill(complex(1,1), 1, 1)) == fill(1, 1, 1) | ||
148 | ``` | ||
149 | """ | ||
150 | function Hermitian(A::AbstractMatrix, uplo::Symbol=:U) | ||
151 | n = checksquare(A) | ||
152 | return hermitian_type(typeof(A))(A, char_uplo(uplo)) | ||
153 | end | ||
154 | |||
155 | """ | ||
156 | hermitian(A, uplo=:U) | ||
157 | |||
158 | Construct a hermitian view of `A`. If `A` is a matrix, `uplo` controls whether the upper | ||
159 | (if `uplo = :U`) or lower (if `uplo = :L`) triangle of `A` is used to implicitly fill the | ||
160 | other one. If `A` is a `Number`, its real part is returned converted back to the input | ||
161 | type. | ||
162 | |||
163 | If a hermitian view of a matrix is to be constructed of which the elements are neither | ||
164 | matrices nor numbers, an appropriate method of `hermitian` has to be implemented. In that | ||
165 | case, `hermitian_type` has to be implemented, too. | ||
166 | """ | ||
167 | hermitian(A::AbstractMatrix, uplo::Symbol) = Hermitian(A, uplo) | ||
168 | hermitian(A::Number, ::Symbol) = convert(typeof(A), real(A)) | ||
169 | |||
170 | """ | ||
171 | hermitian_type(T::Type) | ||
172 | |||
173 | The type of the object returned by `hermitian(::T, ::Symbol)`. For matrices, this is an | ||
174 | appropriately typed `Hermitian`, for `Number`s, it is the original type. If `hermitian` is | ||
175 | implemented for a custom type, so should be `hermitian_type`, and vice versa. | ||
176 | """ | ||
177 | function hermitian_type(::Type{T}) where {S, T<:AbstractMatrix{S}} | ||
178 | return Hermitian{Union{S, promote_op(adjoint, S), hermitian_type(S)}, T} | ||
179 | end | ||
180 | function hermitian_type(::Type{T}) where {S<:Number, T<:AbstractMatrix{S}} | ||
181 | return Hermitian{S, T} | ||
182 | end | ||
183 | function hermitian_type(::Type{T}) where {S<:AbstractMatrix, T<:AbstractMatrix{S}} | ||
184 | return Hermitian{AbstractMatrix, T} | ||
185 | end | ||
186 | hermitian_type(::Type{T}) where {T<:Number} = T | ||
187 | |||
188 | _unwrap(A::Hermitian) = parent(A) | ||
189 | _unwrap(A::Symmetric) = parent(A) | ||
190 | |||
191 | for (S, H) in ((:Symmetric, :Hermitian), (:Hermitian, :Symmetric)) | ||
192 | @eval begin | ||
193 | $S(A::$S) = A | ||
194 | function $S(A::$S, uplo::Symbol) | ||
195 | if A.uplo == char_uplo(uplo) | ||
196 | return A | ||
197 | else | ||
198 | throw(ArgumentError("Cannot construct $($S); uplo doesn't match")) | ||
199 | end | ||
200 | end | ||
201 | $S(A::$H) = $S(A, sym_uplo(A.uplo)) | ||
202 | function $S(A::$H, uplo::Symbol) | ||
203 | if A.uplo == char_uplo(uplo) | ||
204 | if $H === Hermitian && !(eltype(A) <: Real) && | ||
205 | any(!isreal, A.data[i] for i in diagind(A.data)) | ||
206 | |||
207 | throw(ArgumentError("Cannot construct $($S)($($H))); diagonal contains complex values")) | ||
208 | end | ||
209 | return $S(A.data, sym_uplo(A.uplo)) | ||
210 | else | ||
211 | throw(ArgumentError("Cannot construct $($S); uplo doesn't match")) | ||
212 | end | ||
213 | end | ||
214 | end | ||
215 | end | ||
216 | |||
217 | convert(::Type{T}, m::Union{Symmetric,Hermitian}) where {T<:Symmetric} = m isa T ? m : T(m)::T | ||
218 | convert(::Type{T}, m::Union{Symmetric,Hermitian}) where {T<:Hermitian} = m isa T ? m : T(m)::T | ||
219 | |||
220 | const HermOrSym{T, S} = Union{Hermitian{T,S}, Symmetric{T,S}} | ||
221 | const RealHermSym{T<:Real,S} = Union{Hermitian{T,S}, Symmetric{T,S}} | ||
222 | const RealHermSymComplexHerm{T<:Real,S} = Union{Hermitian{T,S}, Symmetric{T,S}, Hermitian{Complex{T},S}} | ||
223 | const RealHermSymComplexSym{T<:Real,S} = Union{Hermitian{T,S}, Symmetric{T,S}, Symmetric{Complex{T},S}} | ||
224 | |||
225 | size(A::HermOrSym, d) = size(A.data, d) | ||
226 | size(A::HermOrSym) = size(A.data) | ||
227 | @inline function Base.isassigned(A::HermOrSym, i::Int, j::Int) | ||
228 | @boundscheck checkbounds(Bool, A, i, j) || return false | ||
229 | @inbounds if i == j || ((A.uplo == 'U') == (i < j)) | ||
230 | return isassigned(A.data, i, j) | ||
231 | else | ||
232 | return isassigned(A.data, j, i) | ||
233 | end | ||
234 | end | ||
235 | |||
236 | @inline function getindex(A::Symmetric, i::Integer, j::Integer) | ||
237 | @boundscheck checkbounds(A, i, j) | ||
238 | @inbounds if i == j | ||
239 | return symmetric(A.data[i, j], sym_uplo(A.uplo))::symmetric_type(eltype(A.data)) | ||
240 | elseif (A.uplo == 'U') == (i < j) | ||
241 | return A.data[i, j] | ||
242 | else | ||
243 | return transpose(A.data[j, i]) | ||
244 | end | ||
245 | end | ||
246 | @inline function getindex(A::Hermitian, i::Integer, j::Integer) | ||
247 | @boundscheck checkbounds(A, i, j) | ||
248 | @inbounds if i == j | ||
249 | return hermitian(A.data[i, j], sym_uplo(A.uplo))::hermitian_type(eltype(A.data)) | ||
250 | elseif (A.uplo == 'U') == (i < j) | ||
251 | return A.data[i, j] | ||
252 | else | ||
253 | return adjoint(A.data[j, i]) | ||
254 | end | ||
255 | end | ||
256 | |||
257 | function setindex!(A::Symmetric, v, i::Integer, j::Integer) | ||
258 | i == j || throw(ArgumentError("Cannot set a non-diagonal index in a symmetric matrix")) | ||
259 | setindex!(A.data, v, i, j) | ||
260 | end | ||
261 | |||
262 | function setindex!(A::Hermitian, v, i::Integer, j::Integer) | ||
263 | if i != j | ||
264 | throw(ArgumentError("Cannot set a non-diagonal index in a Hermitian matrix")) | ||
265 | elseif !isreal(v) | ||
266 | throw(ArgumentError("Cannot set a diagonal entry in a Hermitian matrix to a nonreal value")) | ||
267 | else | ||
268 | setindex!(A.data, v, i, j) | ||
269 | end | ||
270 | end | ||
271 | |||
272 | diag(A::Symmetric) = symmetric.(diag(parent(A)), sym_uplo(A.uplo)) | ||
273 | diag(A::Hermitian) = hermitian.(diag(parent(A)), sym_uplo(A.uplo)) | ||
274 | |||
275 | isdiag(A::HermOrSym) = isdiag(A.uplo == 'U' ? UpperTriangular(A.data) : LowerTriangular(A.data)) | ||
276 | |||
277 | # For A<:Union{Symmetric,Hermitian}, similar(A[, neweltype]) should yield a matrix with the same | ||
278 | # symmetry type, uplo flag, and underlying storage type as A. The following methods cover these cases. | ||
279 | similar(A::Symmetric, ::Type{T}) where {T} = Symmetric(similar(parent(A), T), ifelse(A.uplo == 'U', :U, :L)) | ||
280 | # If the Hermitian constructor's check ascertaining that the wrapped matrix's | ||
281 | # diagonal is strictly real is removed, the following method can be simplified. | ||
282 | function similar(A::Hermitian, ::Type{T}) where T | ||
283 | B = similar(parent(A), T) | ||
284 | for i in 1:size(B, 1) B[i, i] = 0 end | ||
285 | return Hermitian(B, ifelse(A.uplo == 'U', :U, :L)) | ||
286 | end | ||
287 | # On the other hand, similar(A, [neweltype,] shape...) should yield a matrix of the underlying | ||
288 | # storage type of A (not wrapped in a symmetry type). The following method covers these cases. | ||
289 | 60 (21 %) |
60 (100 %)
samples spent calling
similar
similar(A::Union{Symmetric,Hermitian}, ::Type{T}, dims::Dims{N}) where {T,N} = similar(parent(A), T, dims)
|
|
290 | |||
291 | # Conversion | ||
292 | function Matrix(A::Symmetric) | ||
293 | B = copytri!(convert(Matrix, copy(A.data)), A.uplo) | ||
294 | for i = 1:size(A, 1) | ||
295 | B[i,i] = symmetric(A[i,i], sym_uplo(A.uplo))::symmetric_type(eltype(A.data)) | ||
296 | end | ||
297 | return B | ||
298 | end | ||
299 | function Matrix(A::Hermitian) | ||
300 | B = copytri!(convert(Matrix, copy(A.data)), A.uplo, true) | ||
301 | for i = 1:size(A, 1) | ||
302 | B[i,i] = hermitian(A[i,i], sym_uplo(A.uplo))::hermitian_type(eltype(A.data)) | ||
303 | end | ||
304 | return B | ||
305 | end | ||
306 | Array(A::Union{Symmetric,Hermitian}) = convert(Matrix, A) | ||
307 | |||
308 | parent(A::HermOrSym) = A.data | ||
309 | Symmetric{T,S}(A::Symmetric{T,S}) where {T,S<:AbstractMatrix{T}} = A | ||
310 | Symmetric{T,S}(A::Symmetric) where {T,S<:AbstractMatrix{T}} = Symmetric{T,S}(convert(S,A.data),A.uplo) | ||
311 | AbstractMatrix{T}(A::Symmetric) where {T} = Symmetric(convert(AbstractMatrix{T}, A.data), sym_uplo(A.uplo)) | ||
312 | Hermitian{T,S}(A::Hermitian{T,S}) where {T,S<:AbstractMatrix{T}} = A | ||
313 | Hermitian{T,S}(A::Hermitian) where {T,S<:AbstractMatrix{T}} = Hermitian{T,S}(convert(S,A.data),A.uplo) | ||
314 | AbstractMatrix{T}(A::Hermitian) where {T} = Hermitian(convert(AbstractMatrix{T}, A.data), sym_uplo(A.uplo)) | ||
315 | |||
316 | copy(A::Symmetric{T,S}) where {T,S} = (B = copy(A.data); Symmetric{T,typeof(B)}(B,A.uplo)) | ||
317 | copy(A::Hermitian{T,S}) where {T,S} = (B = copy(A.data); Hermitian{T,typeof(B)}(B,A.uplo)) | ||
318 | |||
319 | function copyto!(dest::Symmetric, src::Symmetric) | ||
320 | if src.uplo == dest.uplo | ||
321 | copyto!(dest.data, src.data) | ||
322 | else | ||
323 | transpose!(dest.data, src.data) | ||
324 | end | ||
325 | return dest | ||
326 | end | ||
327 | |||
328 | function copyto!(dest::Hermitian, src::Hermitian) | ||
329 | if src.uplo == dest.uplo | ||
330 | copyto!(dest.data, src.data) | ||
331 | else | ||
332 | adjoint!(dest.data, src.data) | ||
333 | end | ||
334 | return dest | ||
335 | end | ||
336 | |||
337 | # fill[stored]! | ||
338 | fill!(A::HermOrSym, x) = fillstored!(A, x) | ||
339 | function fillstored!(A::HermOrSym{T}, x) where T | ||
340 | xT = convert(T, x) | ||
341 | if isa(A, Hermitian) | ||
342 | isreal(xT) || throw(ArgumentError("cannot fill Hermitian matrix with a nonreal value")) | ||
343 | end | ||
344 | if A.uplo == 'U' | ||
345 | fillband!(A.data, xT, 0, size(A,2)-1) | ||
346 | else # A.uplo == 'L' | ||
347 | fillband!(A.data, xT, 1-size(A,1), 0) | ||
348 | end | ||
349 | return A | ||
350 | end | ||
351 | |||
352 | Base.isreal(A::HermOrSym{<:Real}) = true | ||
353 | function Base.isreal(A::HermOrSym) | ||
354 | n = size(A, 1) | ||
355 | @inbounds if A.uplo == 'U' | ||
356 | for j in 1:n | ||
357 | for i in 1:(j - (A isa Hermitian)) | ||
358 | if !isreal(A.data[i,j]) | ||
359 | return false | ||
360 | end | ||
361 | end | ||
362 | end | ||
363 | else | ||
364 | for j in 1:n | ||
365 | for i in (j + (A isa Hermitian)):n | ||
366 | if !isreal(A.data[i,j]) | ||
367 | return false | ||
368 | end | ||
369 | end | ||
370 | end | ||
371 | end | ||
372 | return true | ||
373 | end | ||
374 | |||
375 | ishermitian(A::Hermitian) = true | ||
376 | ishermitian(A::Symmetric{<:Real}) = true | ||
377 | ishermitian(A::Symmetric{<:Complex}) = isreal(A) | ||
378 | issymmetric(A::Hermitian{<:Real}) = true | ||
379 | issymmetric(A::Hermitian{<:Complex}) = isreal(A) | ||
380 | issymmetric(A::Symmetric) = true | ||
381 | |||
382 | adjoint(A::Hermitian) = A | ||
383 | transpose(A::Symmetric) = A | ||
384 | adjoint(A::Symmetric{<:Real}) = A | ||
385 | transpose(A::Hermitian{<:Real}) = A | ||
386 | adjoint(A::Symmetric) = Adjoint(A) | ||
387 | transpose(A::Hermitian) = Transpose(A) | ||
388 | |||
389 | real(A::Symmetric{<:Real}) = A | ||
390 | real(A::Hermitian{<:Real}) = A | ||
391 | real(A::Symmetric) = Symmetric(real(A.data), sym_uplo(A.uplo)) | ||
392 | real(A::Hermitian) = Hermitian(real(A.data), sym_uplo(A.uplo)) | ||
393 | imag(A::Symmetric) = Symmetric(imag(A.data), sym_uplo(A.uplo)) | ||
394 | |||
395 | Base.copy(A::Adjoint{<:Any,<:Symmetric}) = | ||
396 | Symmetric(copy(adjoint(A.parent.data)), ifelse(A.parent.uplo == 'U', :L, :U)) | ||
397 | Base.copy(A::Transpose{<:Any,<:Hermitian}) = | ||
398 | Hermitian(copy(transpose(A.parent.data)), ifelse(A.parent.uplo == 'U', :L, :U)) | ||
399 | |||
400 | tr(A::Symmetric) = tr(A.data) # to avoid AbstractMatrix fallback (incl. allocations) | ||
401 | tr(A::Hermitian) = real(tr(A.data)) | ||
402 | |||
403 | Base.conj(A::HermOrSym) = typeof(A)(conj(A.data), A.uplo) | ||
404 | Base.conj!(A::HermOrSym) = typeof(A)(conj!(A.data), A.uplo) | ||
405 | |||
406 | # tril/triu | ||
407 | function tril(A::Hermitian, k::Integer=0) | ||
408 | if A.uplo == 'U' && k <= 0 | ||
409 | return tril!(copy(A.data'),k) | ||
410 | elseif A.uplo == 'U' && k > 0 | ||
411 | return tril!(copy(A.data'),-1) + tril!(triu(A.data),k) | ||
412 | elseif A.uplo == 'L' && k <= 0 | ||
413 | return tril(A.data,k) | ||
414 | else | ||
415 | return tril(A.data,-1) + tril!(triu!(copy(A.data')),k) | ||
416 | end | ||
417 | end | ||
418 | |||
419 | function tril(A::Symmetric, k::Integer=0) | ||
420 | if A.uplo == 'U' && k <= 0 | ||
421 | return tril!(copy(transpose(A.data)),k) | ||
422 | elseif A.uplo == 'U' && k > 0 | ||
423 | return tril!(copy(transpose(A.data)),-1) + tril!(triu(A.data),k) | ||
424 | elseif A.uplo == 'L' && k <= 0 | ||
425 | return tril(A.data,k) | ||
426 | else | ||
427 | return tril(A.data,-1) + tril!(triu!(copy(transpose(A.data))),k) | ||
428 | end | ||
429 | end | ||
430 | |||
431 | function triu(A::Hermitian, k::Integer=0) | ||
432 | if A.uplo == 'U' && k >= 0 | ||
433 | return triu(A.data,k) | ||
434 | elseif A.uplo == 'U' && k < 0 | ||
435 | return triu(A.data,1) + triu!(tril!(copy(A.data')),k) | ||
436 | elseif A.uplo == 'L' && k >= 0 | ||
437 | return triu!(copy(A.data'),k) | ||
438 | else | ||
439 | return triu!(copy(A.data'),1) + triu!(tril(A.data),k) | ||
440 | end | ||
441 | end | ||
442 | |||
443 | function triu(A::Symmetric, k::Integer=0) | ||
444 | if A.uplo == 'U' && k >= 0 | ||
445 | return triu(A.data,k) | ||
446 | elseif A.uplo == 'U' && k < 0 | ||
447 | return triu(A.data,1) + triu!(tril!(copy(transpose(A.data))),k) | ||
448 | elseif A.uplo == 'L' && k >= 0 | ||
449 | return triu!(copy(transpose(A.data)),k) | ||
450 | else | ||
451 | return triu!(copy(transpose(A.data)),1) + triu!(tril(A.data),k) | ||
452 | end | ||
453 | end | ||
454 | |||
455 | for (T, trans, real) in [(:Symmetric, :transpose, :identity), (:(Hermitian{<:Union{Real,Complex}}), :adjoint, :real)] | ||
456 | @eval begin | ||
457 | function dot(A::$T, B::$T) | ||
458 | n = size(A, 2) | ||
459 | if n != size(B, 2) | ||
460 | throw(DimensionMismatch("A has dimensions $(size(A)) but B has dimensions $(size(B))")) | ||
461 | end | ||
462 | |||
463 | dotprod = zero(dot(first(A), first(B))) | ||
464 | @inbounds if A.uplo == 'U' && B.uplo == 'U' | ||
465 | for j in 1:n | ||
466 | for i in 1:(j - 1) | ||
467 | dotprod += 2 * $real(dot(A.data[i, j], B.data[i, j])) | ||
468 | end | ||
469 | dotprod += dot(A[j, j], B[j, j]) | ||
470 | end | ||
471 | elseif A.uplo == 'L' && B.uplo == 'L' | ||
472 | for j in 1:n | ||
473 | dotprod += dot(A[j, j], B[j, j]) | ||
474 | for i in (j + 1):n | ||
475 | dotprod += 2 * $real(dot(A.data[i, j], B.data[i, j])) | ||
476 | end | ||
477 | end | ||
478 | elseif A.uplo == 'U' && B.uplo == 'L' | ||
479 | for j in 1:n | ||
480 | for i in 1:(j - 1) | ||
481 | dotprod += 2 * $real(dot(A.data[i, j], $trans(B.data[j, i]))) | ||
482 | end | ||
483 | dotprod += dot(A[j, j], B[j, j]) | ||
484 | end | ||
485 | else | ||
486 | for j in 1:n | ||
487 | dotprod += dot(A[j, j], B[j, j]) | ||
488 | for i in (j + 1):n | ||
489 | dotprod += 2 * $real(dot(A.data[i, j], $trans(B.data[j, i]))) | ||
490 | end | ||
491 | end | ||
492 | end | ||
493 | return dotprod | ||
494 | end | ||
495 | end | ||
496 | end | ||
497 | |||
498 | (-)(A::Symmetric) = Symmetric(-A.data, sym_uplo(A.uplo)) | ||
499 | (-)(A::Hermitian) = Hermitian(-A.data, sym_uplo(A.uplo)) | ||
500 | |||
501 | ## Addition/subtraction | ||
502 | for f ∈ (:+, :-), (Wrapper, conjugation) ∈ ((:Hermitian, :adjoint), (:Symmetric, :transpose)) | ||
503 | @eval begin | ||
504 | function $f(A::$Wrapper, B::$Wrapper) | ||
505 | if A.uplo == B.uplo | ||
506 | return $Wrapper($f(parent(A), parent(B)), sym_uplo(A.uplo)) | ||
507 | elseif A.uplo == 'U' | ||
508 | return $Wrapper($f(parent(A), $conjugation(parent(B))), :U) | ||
509 | else | ||
510 | return $Wrapper($f($conjugation(parent(A)), parent(B)), :U) | ||
511 | end | ||
512 | end | ||
513 | end | ||
514 | end | ||
515 | |||
516 | for f in (:+, :-) | ||
517 | @eval begin | ||
518 | $f(A::Hermitian, B::Symmetric{<:Real}) = $f(A, Hermitian(parent(B), sym_uplo(B.uplo))) | ||
519 | $f(A::Symmetric{<:Real}, B::Hermitian) = $f(Hermitian(parent(A), sym_uplo(A.uplo)), B) | ||
520 | $f(A::SymTridiagonal, B::Symmetric) = Symmetric($f(A, B.data), sym_uplo(B.uplo)) | ||
521 | $f(A::Symmetric, B::SymTridiagonal) = Symmetric($f(A.data, B), sym_uplo(A.uplo)) | ||
522 | $f(A::SymTridiagonal{<:Real}, B::Hermitian) = Hermitian($f(A, B.data), sym_uplo(B.uplo)) | ||
523 | $f(A::Hermitian, B::SymTridiagonal{<:Real}) = Hermitian($f(A.data, B), sym_uplo(A.uplo)) | ||
524 | end | ||
525 | end | ||
526 | |||
527 | *(A::HermOrSym, B::HermOrSym) = A * copyto!(similar(parent(B)), B) | ||
528 | |||
529 | function dot(x::AbstractVector, A::RealHermSymComplexHerm, y::AbstractVector) | ||
530 | require_one_based_indexing(x, y) | ||
531 | n = length(x) | ||
532 | (n == length(y) == size(A, 1)) || throw(DimensionMismatch()) | ||
533 | data = A.data | ||
534 | r = dot(zero(eltype(x)), zero(eltype(A)), zero(eltype(y))) | ||
535 | iszero(n) && return r | ||
536 | if A.uplo == 'U' | ||
537 | @inbounds for j = 1:length(y) | ||
538 | r += dot(x[j], real(data[j,j]), y[j]) | ||
539 | @simd for i = 1:j-1 | ||
540 | Aij = data[i,j] | ||
541 | r += dot(x[i], Aij, y[j]) + dot(x[j], adjoint(Aij), y[i]) | ||
542 | end | ||
543 | end | ||
544 | else # A.uplo == 'L' | ||
545 | @inbounds for j = 1:length(y) | ||
546 | r += dot(x[j], real(data[j,j]), y[j]) | ||
547 | @simd for i = j+1:length(y) | ||
548 | Aij = data[i,j] | ||
549 | r += dot(x[i], Aij, y[j]) + dot(x[j], adjoint(Aij), y[i]) | ||
550 | end | ||
551 | end | ||
552 | end | ||
553 | return r | ||
554 | end | ||
555 | |||
556 | # Scaling with Number | ||
557 | *(A::Symmetric, x::Number) = Symmetric(A.data*x, sym_uplo(A.uplo)) | ||
558 | *(x::Number, A::Symmetric) = Symmetric(x*A.data, sym_uplo(A.uplo)) | ||
559 | *(A::Hermitian, x::Real) = Hermitian(A.data*x, sym_uplo(A.uplo)) | ||
560 | *(x::Real, A::Hermitian) = Hermitian(x*A.data, sym_uplo(A.uplo)) | ||
561 | /(A::Symmetric, x::Number) = Symmetric(A.data/x, sym_uplo(A.uplo)) | ||
562 | /(A::Hermitian, x::Real) = Hermitian(A.data/x, sym_uplo(A.uplo)) | ||
563 | |||
564 | factorize(A::HermOrSym) = _factorize(A) | ||
565 | function _factorize(A::HermOrSym{T}; check::Bool=true) where T | ||
566 | TT = typeof(sqrt(oneunit(T))) | ||
567 | if isdiag(A) | ||
568 | return Diagonal(A) | ||
569 | elseif TT <: BlasFloat | ||
570 | return bunchkaufman(A; check=check) | ||
571 | else # fallback | ||
572 | return lu(A; check=check) | ||
573 | end | ||
574 | end | ||
575 | |||
576 | det(A::RealHermSymComplexHerm) = real(det(_factorize(A; check=false))) | ||
577 | det(A::Symmetric{<:Real}) = det(_factorize(A; check=false)) | ||
578 | det(A::Symmetric) = det(_factorize(A; check=false)) | ||
579 | |||
580 | \(A::HermOrSym, B::AbstractVector) = \(factorize(A), B) | ||
581 | # Bunch-Kaufman solves can not utilize BLAS-3 for multiple right hand sides | ||
582 | # so using LU is faster for AbstractMatrix right hand side | ||
583 | \(A::HermOrSym, B::AbstractMatrix) = \(isdiag(A) ? Diagonal(A) : lu(A), B) | ||
584 | |||
585 | function _inv(A::HermOrSym) | ||
586 | n = checksquare(A) | ||
587 | B = inv!(lu(A)) | ||
588 | conjugate = isa(A, Hermitian) | ||
589 | # symmetrize | ||
590 | if A.uplo == 'U' # add to upper triangle | ||
591 | @inbounds for i = 1:n, j = i:n | ||
592 | B[i,j] = conjugate ? (B[i,j] + conj(B[j,i])) / 2 : (B[i,j] + B[j,i]) / 2 | ||
593 | end | ||
594 | else # A.uplo == 'L', add to lower triangle | ||
595 | @inbounds for i = 1:n, j = i:n | ||
596 | B[j,i] = conjugate ? (B[j,i] + conj(B[i,j])) / 2 : (B[j,i] + B[i,j]) / 2 | ||
597 | end | ||
598 | end | ||
599 | B | ||
600 | end | ||
601 | # StridedMatrix restriction seems necessary due to inv! call in _inv above | ||
602 | inv(A::Hermitian{<:Any,<:StridedMatrix}) = Hermitian(_inv(A), sym_uplo(A.uplo)) | ||
603 | inv(A::Symmetric{<:Any,<:StridedMatrix}) = Symmetric(_inv(A), sym_uplo(A.uplo)) | ||
604 | |||
605 | function svd(A::RealHermSymComplexHerm; full::Bool=false) | ||
606 | vals, vecs = eigen(A) | ||
607 | I = sortperm(vals; by=abs, rev=true) | ||
608 | permute!(vals, I) | ||
609 | Base.permutecols!!(vecs, I) # left-singular vectors | ||
610 | V = copy(vecs) # right-singular vectors | ||
611 | # shifting -1 from singular values to right-singular vectors | ||
612 | @inbounds for i = 1:length(vals) | ||
613 | if vals[i] < 0 | ||
614 | vals[i] = -vals[i] | ||
615 | for j = 1:size(V,1); V[j,i] = -V[j,i]; end | ||
616 | end | ||
617 | end | ||
618 | return SVD(vecs, vals, V') | ||
619 | end | ||
620 | |||
621 | function svdvals!(A::RealHermSymComplexHerm) | ||
622 | vals = eigvals!(A) | ||
623 | for i = 1:length(vals) | ||
624 | vals[i] = abs(vals[i]) | ||
625 | end | ||
626 | return sort!(vals, rev = true) | ||
627 | end | ||
628 | |||
629 | # Matrix functions | ||
630 | ^(A::Symmetric{<:Real}, p::Integer) = sympow(A, p) | ||
631 | ^(A::Symmetric{<:Complex}, p::Integer) = sympow(A, p) | ||
632 | function sympow(A::Symmetric, p::Integer) | ||
633 | if p < 0 | ||
634 | return Symmetric(Base.power_by_squaring(inv(A), -p)) | ||
635 | else | ||
636 | return Symmetric(Base.power_by_squaring(A, p)) | ||
637 | end | ||
638 | end | ||
639 | function ^(A::Symmetric{<:Real}, p::Real) | ||
640 | isinteger(p) && return integerpow(A, p) | ||
641 | F = eigen(A) | ||
642 | if all(λ -> λ ≥ 0, F.values) | ||
643 | return Symmetric((F.vectors * Diagonal((F.values).^p)) * F.vectors') | ||
644 | else | ||
645 | return Symmetric((F.vectors * Diagonal((complex(F.values)).^p)) * F.vectors') | ||
646 | end | ||
647 | end | ||
648 | function ^(A::Symmetric{<:Complex}, p::Real) | ||
649 | isinteger(p) && return integerpow(A, p) | ||
650 | return Symmetric(schurpow(A, p)) | ||
651 | end | ||
652 | function ^(A::Hermitian, p::Integer) | ||
653 | if p < 0 | ||
654 | retmat = Base.power_by_squaring(inv(A), -p) | ||
655 | else | ||
656 | retmat = Base.power_by_squaring(A, p) | ||
657 | end | ||
658 | for i = 1:size(A,1) | ||
659 | retmat[i,i] = real(retmat[i,i]) | ||
660 | end | ||
661 | return Hermitian(retmat) | ||
662 | end | ||
663 | function ^(A::Hermitian{T}, p::Real) where T | ||
664 | isinteger(p) && return integerpow(A, p) | ||
665 | F = eigen(A) | ||
666 | if all(λ -> λ ≥ 0, F.values) | ||
667 | retmat = (F.vectors * Diagonal((F.values).^p)) * F.vectors' | ||
668 | if T <: Real | ||
669 | return Hermitian(retmat) | ||
670 | else | ||
671 | for i = 1:size(A,1) | ||
672 | retmat[i,i] = real(retmat[i,i]) | ||
673 | end | ||
674 | return Hermitian(retmat) | ||
675 | end | ||
676 | else | ||
677 | return (F.vectors * Diagonal((complex(F.values).^p))) * F.vectors' | ||
678 | end | ||
679 | end | ||
680 | |||
681 | for func in (:exp, :cos, :sin, :tan, :cosh, :sinh, :tanh, :atan, :asinh, :atanh) | ||
682 | @eval begin | ||
683 | function ($func)(A::HermOrSym{<:Real}) | ||
684 | F = eigen(A) | ||
685 | return Symmetric((F.vectors * Diagonal(($func).(F.values))) * F.vectors') | ||
686 | end | ||
687 | function ($func)(A::Hermitian{<:Complex}) | ||
688 | n = checksquare(A) | ||
689 | F = eigen(A) | ||
690 | retmat = (F.vectors * Diagonal(($func).(F.values))) * F.vectors' | ||
691 | for i = 1:n | ||
692 | retmat[i,i] = real(retmat[i,i]) | ||
693 | end | ||
694 | return Hermitian(retmat) | ||
695 | end | ||
696 | end | ||
697 | end | ||
698 | |||
699 | function cis(A::Union{RealHermSymComplexHerm,SymTridiagonal{<:Real}}) | ||
700 | F = eigen(A) | ||
701 | # The returned matrix is unitary, and is complex-symmetric for real A | ||
702 | return F.vectors .* cis.(F.values') * F.vectors' | ||
703 | end | ||
704 | |||
705 | for func in (:acos, :asin) | ||
706 | @eval begin | ||
707 | function ($func)(A::HermOrSym{<:Real}) | ||
708 | F = eigen(A) | ||
709 | if all(λ -> -1 ≤ λ ≤ 1, F.values) | ||
710 | retmat = (F.vectors * Diagonal(($func).(F.values))) * F.vectors' | ||
711 | else | ||
712 | retmat = (F.vectors * Diagonal(($func).(complex.(F.values)))) * F.vectors' | ||
713 | end | ||
714 | return Symmetric(retmat) | ||
715 | end | ||
716 | function ($func)(A::Hermitian{<:Complex}) | ||
717 | n = checksquare(A) | ||
718 | F = eigen(A) | ||
719 | if all(λ -> -1 ≤ λ ≤ 1, F.values) | ||
720 | retmat = (F.vectors * Diagonal(($func).(F.values))) * F.vectors' | ||
721 | for i = 1:n | ||
722 | retmat[i,i] = real(retmat[i,i]) | ||
723 | end | ||
724 | return Hermitian(retmat) | ||
725 | else | ||
726 | return (F.vectors * Diagonal(($func).(complex.(F.values)))) * F.vectors' | ||
727 | end | ||
728 | end | ||
729 | end | ||
730 | end | ||
731 | |||
732 | function acosh(A::HermOrSym{<:Real}) | ||
733 | F = eigen(A) | ||
734 | if all(λ -> λ ≥ 1, F.values) | ||
735 | retmat = (F.vectors * Diagonal(acosh.(F.values))) * F.vectors' | ||
736 | else | ||
737 | retmat = (F.vectors * Diagonal(acosh.(complex.(F.values)))) * F.vectors' | ||
738 | end | ||
739 | return Symmetric(retmat) | ||
740 | end | ||
741 | function acosh(A::Hermitian{<:Complex}) | ||
742 | n = checksquare(A) | ||
743 | F = eigen(A) | ||
744 | if all(λ -> λ ≥ 1, F.values) | ||
745 | retmat = (F.vectors * Diagonal(acosh.(F.values))) * F.vectors' | ||
746 | for i = 1:n | ||
747 | retmat[i,i] = real(retmat[i,i]) | ||
748 | end | ||
749 | return Hermitian(retmat) | ||
750 | else | ||
751 | return (F.vectors * Diagonal(acosh.(complex.(F.values)))) * F.vectors' | ||
752 | end | ||
753 | end | ||
754 | |||
755 | function sincos(A::HermOrSym{<:Real}) | ||
756 | n = checksquare(A) | ||
757 | F = eigen(A) | ||
758 | S, C = Diagonal(similar(A, (n,))), Diagonal(similar(A, (n,))) | ||
759 | for i in 1:n | ||
760 | S.diag[i], C.diag[i] = sincos(F.values[i]) | ||
761 | end | ||
762 | return Symmetric((F.vectors * S) * F.vectors'), Symmetric((F.vectors * C) * F.vectors') | ||
763 | end | ||
764 | function sincos(A::Hermitian{<:Complex}) | ||
765 | n = checksquare(A) | ||
766 | F = eigen(A) | ||
767 | S, C = Diagonal(similar(A, (n,))), Diagonal(similar(A, (n,))) | ||
768 | for i in 1:n | ||
769 | S.diag[i], C.diag[i] = sincos(F.values[i]) | ||
770 | end | ||
771 | retmatS, retmatC = (F.vectors * S) * F.vectors', (F.vectors * C) * F.vectors' | ||
772 | for i = 1:n | ||
773 | retmatS[i,i] = real(retmatS[i,i]) | ||
774 | retmatC[i,i] = real(retmatC[i,i]) | ||
775 | end | ||
776 | return Hermitian(retmatS), Hermitian(retmatC) | ||
777 | end | ||
778 | |||
779 | |||
780 | for func in (:log, :sqrt) | ||
781 | # sqrt has rtol arg to handle matrices that are semidefinite up to roundoff errors | ||
782 | rtolarg = func === :sqrt ? Any[Expr(:kw, :(rtol::Real), :(eps(real(float(one(T))))*size(A,1)))] : Any[] | ||
783 | rtolval = func === :sqrt ? :(-maximum(abs, F.values) * rtol) : 0 | ||
784 | @eval begin | ||
785 | function ($func)(A::HermOrSym{T}; $(rtolarg...)) where {T<:Real} | ||
786 | F = eigen(A) | ||
787 | λ₀ = $rtolval # treat λ ≥ λ₀ as "zero" eigenvalues up to roundoff | ||
788 | if all(λ -> λ ≥ λ₀, F.values) | ||
789 | retmat = (F.vectors * Diagonal(($func).(max.(0, F.values)))) * F.vectors' | ||
790 | else | ||
791 | retmat = (F.vectors * Diagonal(($func).(complex.(F.values)))) * F.vectors' | ||
792 | end | ||
793 | return Symmetric(retmat) | ||
794 | end | ||
795 | |||
796 | function ($func)(A::Hermitian{T}; $(rtolarg...)) where {T<:Complex} | ||
797 | n = checksquare(A) | ||
798 | F = eigen(A) | ||
799 | λ₀ = $rtolval # treat λ ≥ λ₀ as "zero" eigenvalues up to roundoff | ||
800 | if all(λ -> λ ≥ λ₀, F.values) | ||
801 | retmat = (F.vectors * Diagonal(($func).(max.(0, F.values)))) * F.vectors' | ||
802 | for i = 1:n | ||
803 | retmat[i,i] = real(retmat[i,i]) | ||
804 | end | ||
805 | return Hermitian(retmat) | ||
806 | else | ||
807 | retmat = (F.vectors * Diagonal(($func).(complex(F.values)))) * F.vectors' | ||
808 | return retmat | ||
809 | end | ||
810 | end | ||
811 | end | ||
812 | end | ||
813 | |||
814 | """ | ||
815 | hermitianpart(A, uplo=:U) -> Hermitian | ||
816 | |||
817 | Return the Hermitian part of the square matrix `A`, defined as `(A + A') / 2`, as a | ||
818 | [`Hermitian`](@ref) matrix. For real matrices `A`, this is also known as the symmetric part | ||
819 | of `A`; it is also sometimes called the "operator real part". The optional argument `uplo` controls the corresponding argument of the | ||
820 | [`Hermitian`](@ref) view. For real matrices, the latter is equivalent to a | ||
821 | [`Symmetric`](@ref) view. | ||
822 | |||
823 | See also [`hermitianpart!`](@ref) for the corresponding in-place operation. | ||
824 | |||
825 | !!! compat "Julia 1.10" | ||
826 | This function requires Julia 1.10 or later. | ||
827 | """ | ||
828 | hermitianpart(A::AbstractMatrix, uplo::Symbol=:U) = Hermitian(_hermitianpart(A), uplo) | ||
829 | |||
830 | """ | ||
831 | hermitianpart!(A, uplo=:U) -> Hermitian | ||
832 | |||
833 | Overwrite the square matrix `A` in-place with its Hermitian part `(A + A') / 2`, and return | ||
834 | [`Hermitian(A, uplo)`](@ref). For real matrices `A`, this is also known as the symmetric | ||
835 | part of `A`. | ||
836 | |||
837 | See also [`hermitianpart`](@ref) for the corresponding out-of-place operation. | ||
838 | |||
839 | !!! compat "Julia 1.10" | ||
840 | This function requires Julia 1.10 or later. | ||
841 | """ | ||
842 | hermitianpart!(A::AbstractMatrix, uplo::Symbol=:U) = Hermitian(_hermitianpart!(A), uplo) | ||
843 | |||
844 | _hermitianpart(A::AbstractMatrix) = _hermitianpart!(copy_similar(A, Base.promote_op(/, eltype(A), Int))) | ||
845 | _hermitianpart(a::Number) = real(a) | ||
846 | |||
847 | function _hermitianpart!(A::AbstractMatrix) | ||
848 | require_one_based_indexing(A) | ||
849 | n = checksquare(A) | ||
850 | @inbounds for j in 1:n | ||
851 | A[j, j] = _hermitianpart(A[j, j]) | ||
852 | for i in 1:j-1 | ||
853 | A[i, j] = val = (A[i, j] + adjoint(A[j, i])) / 2 | ||
854 | A[j, i] = adjoint(val) | ||
855 | end | ||
856 | end | ||
857 | return A | ||
858 | end | ||
859 | |||
860 | ## structured matrix printing ## | ||
861 | function Base.replace_in_print_matrix(A::HermOrSym,i::Integer,j::Integer,s::AbstractString) | ||
862 | ijminmax = minmax(i, j) | ||
863 | inds = A.uplo == 'U' ? ijminmax : reverse(ijminmax) | ||
864 | Base.replace_in_print_matrix(parent(A), inds..., s) | ||
865 | end |