StatProfilerHTML.jl report
Generated on Thu, 21 Dec 2023 12:59:22
File source code
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 (21 %) samples spent in similar
60 (100 %) (incl.) when called from copy_similar line 416
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