Skip to content

Commit 59c1ce9

Browse files
sethaxenElOceanografo
authored andcommittedMay 4, 2021
Compute real matrix logarithm and matrix square root using real arithmetic (JuliaLang#39973)
* Add failing test * Add sylvester methods for small matrices * Add 2x2 real matrix square root * Add real square root of quasitriangular matrix * Simplify 2x2 real square root * Rename functions to use quasitriu * Avoid NaNs when eigenvalues are all zero * Reuse ranges * Add clarifying comments * Unify real and complex matrix square root * Add reference for real sqrt * Move quasitriu auxiliary functions to triangular.jl * Ensure loops are type-stable and use simd * Remove duplicate computation * Correctly promote for dimensionful A * Use simd directive * Test that UpperTriangular is returned by sqrt * Test sqrt for UnitUpperTriangular * Test that return type is complex when input type is * Test that output is complex when input is * Add failing test * Separate type-stable from type-unstable part * Use generic sqrt_quasitriu for sqrt triu * Avoid redundant matmul * Clarify comment * Return complex output for complex input * Call log_quasitriu * Add failing test for log type-inferrability * Realify or complexify as necessary * Call sqrt_quasitriu directly * Refactor sqrt_diag! * Simplify utility function * Add comment * Compute accurate block-diagonal * Compute superdiagonal for quasi triu A0 * Compute accurate block superdiagonal * Avoid full LU decomposition in inner loop * Avoid promotion to improve type-stability * Modify return type if necessary * Clarify comment * Add comments * Call log_quasitriu on quasitriu matrices * Document quasi-triangular algorithm * Remove test This matrix has eigenvalues to close to zero that its eltype is not stable * Rearrange definition * Add compatibility for unit triangular matrices * Release constraints on tests * Separate copying of A from log computation * Revert "Separate copying of A from log computation" This reverts commit 23becc5. * Use Givens rotations * Compute Schur in-place when possible * Always allocate a copy * Fix block indexing * Compute sqrt in-place * Overwrite AmI * Reduce allocations in Pade approximation * Use T * Don't unnecessarily unwrap * Test remaining log branches * Add additional matrix square root tests * Separate type-unstable from type-stable part This substantially reduces allocations for some reason * Use Ref instead of a Vector * Eliminate allocation in checksquare * Refactor param choosing code to own function * Comment section * Use more descriptive variable name * Reuse temporaries * Add reference * More accurately describe condition
1 parent f0e13fa commit 59c1ce9

File tree

5 files changed

+743
-187
lines changed

5 files changed

+743
-187
lines changed
 

‎stdlib/LinearAlgebra/src/dense.jl

+82-43
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ function rcswap!(i::Integer, j::Integer, X::StridedMatrix{<:Number})
679679
end
680680

681681
"""
682-
log(A{T}::StridedMatrix{T})
682+
log(A::StridedMatrix)
683683
684684
If `A` has no negative real eigenvalue, compute the principal matrix logarithm of `A`, i.e.
685685
the unique matrix ``X`` such that ``e^X = A`` and ``-\\pi < Im(\\lambda) < \\pi`` for all
@@ -688,9 +688,10 @@ matrix function is returned whenever possible.
688688
689689
If `A` is symmetric or Hermitian, its eigendecomposition ([`eigen`](@ref)) is
690690
used, if `A` is triangular an improved version of the inverse scaling and squaring method is
691-
employed (see [^AH12] and [^AHR13]). For general matrices, the complex Schur form
692-
([`schur`](@ref)) is computed and the triangular algorithm is used on the
693-
triangular factor.
691+
employed (see [^AH12] and [^AHR13]). If `A` is real with no negative eigenvalues, then
692+
the real Schur form is computed. Otherwise, the complex Schur form is computed. Then
693+
the upper (quasi-)triangular algorithm in [^AHR13] is used on the upper (quasi-)triangular
694+
factor.
694695
695696
[^AH12]: Awad H. Al-Mohy and Nicholas J. Higham, "Improved inverse scaling and squaring algorithms for the matrix logarithm", SIAM Journal on Scientific Computing, 34(4), 2012, C153-C169. [doi:10.1137/110852553](https://doi.org/10.1137/110852553)
696697
@@ -713,27 +714,28 @@ function log(A::StridedMatrix)
713714
# If possible, use diagonalization
714715
if ishermitian(A)
715716
logHermA = log(Hermitian(A))
716-
return isa(logHermA, Hermitian) ? copytri!(parent(logHermA), 'U', true) : parent(logHermA)
717-
end
718-
719-
# Use Schur decomposition
720-
n = checksquare(A)
721-
if istriu(A)
722-
return triu!(parent(log(UpperTriangular(complex(A)))))
723-
else
724-
if isreal(A)
725-
SchurF = schur(real(A))
717+
return ishermitian(logHermA) ? copytri!(parent(logHermA), 'U', true) : parent(logHermA)
718+
elseif istriu(A)
719+
return triu!(parent(log(UpperTriangular(A))))
720+
elseif isreal(A)
721+
SchurF = schur(real(A))
722+
if istriu(SchurF.T)
723+
logA = SchurF.Z * log(UpperTriangular(SchurF.T)) * SchurF.Z'
726724
else
727-
SchurF = schur(A)
728-
end
729-
if !istriu(SchurF.T)
730-
SchurS = schur(complex(SchurF.T))
731-
logT = SchurS.Z * log(UpperTriangular(SchurS.T)) * SchurS.Z'
732-
return SchurF.Z * logT * SchurF.Z'
733-
else
734-
R = log(UpperTriangular(complex(SchurF.T)))
735-
return SchurF.Z * R * SchurF.Z'
725+
# real log exists whenever all eigenvalues are positive
726+
is_log_real = !any(x -> isreal(x) && real(x) 0, SchurF.values)
727+
if is_log_real
728+
logA = SchurF.Z * log_quasitriu(SchurF.T) * SchurF.Z'
729+
else
730+
SchurS = schur!(complex(SchurF.T))
731+
Z = SchurF.Z * SchurS.Z
732+
logA = Z * log(UpperTriangular(SchurS.T)) * Z'
733+
end
736734
end
735+
return eltype(A) <: Complex ? complex(logA) : logA
736+
else
737+
SchurF = schur(A)
738+
return SchurF.vectors * log(UpperTriangular(SchurF.T)) * SchurF.vectors'
737739
end
738740
end
739741

@@ -755,13 +757,21 @@ defaults to machine precision scaled by `size(A,1)`.
755757
Otherwise, the square root is determined by means of the
756758
Björck-Hammarling method [^BH83], which computes the complex Schur form ([`schur`](@ref))
757759
and then the complex square root of the triangular factor.
760+
If a real square root exists, then an extension of this method [^H87] that computes the real
761+
Schur form and then the real square root of the quasi-triangular factor is instead used.
758762
759763
[^BH83]:
760764
761765
Åke Björck and Sven Hammarling, "A Schur method for the square root of a matrix",
762766
Linear Algebra and its Applications, 52-53, 1983, 127-140.
763767
[doi:10.1016/0024-3795(83)80010-X](https://doi.org/10.1016/0024-3795(83)80010-X)
764768
769+
[^H87]:
770+
771+
Nicholas J. Higham, "Computing real square roots of a real matrix",
772+
Linear Algebra and its Applications, 88-89, 1987, 405-430.
773+
[doi:10.1016/0024-3795(87)90118-2](https://doi.org/10.1016/0024-3795(87)90118-2)
774+
765775
# Examples
766776
```jldoctest
767777
julia> A = [4 0; 0 4]
@@ -775,31 +785,32 @@ julia> sqrt(A)
775785
0.0 2.0
776786
```
777787
"""
778-
function sqrt(A::StridedMatrix{<:Real})
779-
if issymmetric(A)
780-
return copytri!(parent(sqrt(Symmetric(A))), 'U')
781-
end
782-
n = checksquare(A)
783-
if istriu(A)
784-
return triu!(parent(sqrt(UpperTriangular(A))))
785-
else
786-
SchurF = schur(complex(A))
787-
R = triu!(parent(sqrt(UpperTriangular(SchurF.T)))) # unwrapping unnecessary?
788-
return SchurF.vectors * R * SchurF.vectors'
789-
end
790-
end
791-
function sqrt(A::StridedMatrix{<:Complex})
788+
function sqrt(A::StridedMatrix{T}) where {T<:Union{Real,Complex}}
792789
if ishermitian(A)
793790
sqrtHermA = sqrt(Hermitian(A))
794-
return isa(sqrtHermA, Hermitian) ? copytri!(parent(sqrtHermA), 'U', true) : parent(sqrtHermA)
795-
end
796-
n = checksquare(A)
797-
if istriu(A)
791+
return ishermitian(sqrtHermA) ? copytri!(parent(sqrtHermA), 'U', true) : parent(sqrtHermA)
792+
elseif istriu(A)
798793
return triu!(parent(sqrt(UpperTriangular(A))))
794+
elseif isreal(A)
795+
SchurF = schur(real(A))
796+
if istriu(SchurF.T)
797+
sqrtA = SchurF.Z * sqrt(UpperTriangular(SchurF.T)) * SchurF.Z'
798+
else
799+
# real sqrt exists whenever no eigenvalues are negative
800+
is_sqrt_real = !any(x -> isreal(x) && real(x) < 0, SchurF.values)
801+
# sqrt_quasitriu uses LAPACK functions for non-triu inputs
802+
if typeof(sqrt(zero(T))) <: BlasFloat && is_sqrt_real
803+
sqrtA = SchurF.Z * sqrt_quasitriu(SchurF.T) * SchurF.Z'
804+
else
805+
SchurS = schur!(complex(SchurF.T))
806+
Z = SchurF.Z * SchurS.Z
807+
sqrtA = Z * sqrt(UpperTriangular(SchurS.T)) * Z'
808+
end
809+
end
810+
return eltype(A) <: Complex ? complex(sqrtA) : sqrtA
799811
else
800812
SchurF = schur(A)
801-
R = triu!(parent(sqrt(UpperTriangular(SchurF.T)))) # unwrapping unnecessary?
802-
return SchurF.vectors * R * SchurF.vectors'
813+
return SchurF.vectors * sqrt(UpperTriangular(SchurF.T)) * SchurF.vectors'
803814
end
804815
end
805816

@@ -1526,6 +1537,34 @@ function sylvester(A::StridedMatrix{T},B::StridedMatrix{T},C::StridedMatrix{T})
15261537
end
15271538
sylvester(A::StridedMatrix{T}, B::StridedMatrix{T}, C::StridedMatrix{T}) where {T<:Integer} = sylvester(float(A), float(B), float(C))
15281539

1540+
Base.@propagate_inbounds function _sylvester_2x1!(A, B, C)
1541+
b = B[1]
1542+
a21, a12 = A[2, 1], A[1, 2]
1543+
m11 = b + A[1, 1]
1544+
m22 = b + A[2, 2]
1545+
d = m11 * m22 - a12 * a21
1546+
c1, c2 = C
1547+
C[1] = (a12 * c2 - m22 * c1) / d
1548+
C[2] = (a21 * c1 - m11 * c2) / d
1549+
return C
1550+
end
1551+
Base.@propagate_inbounds function _sylvester_1x2!(A, B, C)
1552+
a = A[1]
1553+
b21, b12 = B[2, 1], B[1, 2]
1554+
m11 = a + B[1, 1]
1555+
m22 = a + B[2, 2]
1556+
d = m11 * m22 - b21 * b12
1557+
c1, c2 = C
1558+
C[1] = (b21 * c2 - m22 * c1) / d
1559+
C[2] = (b12 * c1 - m11 * c2) / d
1560+
return C
1561+
end
1562+
function _sylvester_2x2!(A, B, C)
1563+
_, scale = LAPACK.trsyl!('N', 'N', A, B, C)
1564+
rmul!(C, -inv(scale))
1565+
return C
1566+
end
1567+
15291568
sylvester(a::Union{Real,Complex}, b::Union{Real,Complex}, c::Union{Real,Complex}) = -c / (a + b)
15301569

15311570
# AX + XA' + C = 0

‎stdlib/LinearAlgebra/src/lapack.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -6449,15 +6449,15 @@ for (fn, elty, relty) in ((:dtrsyl_, :Float64, :Float64),
64496449
B::AbstractMatrix{$elty}, C::AbstractMatrix{$elty}, isgn::Int=1)
64506450
require_one_based_indexing(A, B, C)
64516451
chkstride1(A, B, C)
6452-
m, n = checksquare(A, B)
6452+
m, n = checksquare(A), checksquare(B)
64536453
lda = max(1, stride(A, 2))
64546454
ldb = max(1, stride(B, 2))
64556455
m1, n1 = size(C)
64566456
if m != m1 || n != n1
64576457
throw(DimensionMismatch("dimensions of A, ($m,$n), and C, ($m1,$n1), must match"))
64586458
end
64596459
ldc = max(1, stride(C, 2))
6460-
scale = Vector{$relty}(undef, 1)
6460+
scale = Ref{$relty}()
64616461
info = Ref{BlasInt}()
64626462
ccall((@blasfunc($fn), libblastrampoline), Cvoid,
64636463
(Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, Ref{BlasInt},
@@ -6467,7 +6467,7 @@ for (fn, elty, relty) in ((:dtrsyl_, :Float64, :Float64),
64676467
A, lda, B, ldb, C, ldc,
64686468
scale, info, 1, 1)
64696469
chklapackerror(info[])
6470-
C, scale[1]
6470+
C, scale[]
64716471
end
64726472
end
64736473
end

‎stdlib/LinearAlgebra/src/triangular.jl

+490-137
Large diffs are not rendered by default.

‎stdlib/LinearAlgebra/test/dense.jl

+137-3
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ end
148148
asym = a + a' # symmetric indefinite
149149
asymsq = sqrt(asym)
150150
@test asymsq*asymsq asym
151+
if eltype(a) <: Real # real square root
152+
apos = a * a
153+
@test sqrt(apos)^2 apos
154+
@test eltype(sqrt(apos)) <: Real
155+
# test that real but Complex input produces Complex output
156+
@test sqrt(complex(apos)) sqrt(apos)
157+
@test eltype(sqrt(complex(apos))) <: Complex
158+
end
151159
end
152160

153161
@testset "Powers" begin
@@ -708,9 +716,6 @@ end
708716
A11 = convert(Matrix{elty}, [3 2; -5 -3])
709717
@test exp(log(A11)) A11
710718

711-
A12 = convert(Matrix{elty}, [1 -1; 1 -1])
712-
@test typeof(log(A12)) == Array{ComplexF64, 2}
713-
714719
A13 = convert(Matrix{elty}, [2 0; 0 2])
715720
@test typeof(log(A13)) == Array{elty, 2}
716721

@@ -723,6 +728,7 @@ end
723728
0.2310490602 0.1969543025 1.363756107])
724729
@test log(A1) logA1
725730
@test exp(log(A1)) A1
731+
@test typeof(log(A1)) == Matrix{elty}
726732

727733
A4 = convert(Matrix{elty}, [1/2 1/3 1/4 1/5+eps();
728734
1/3 1/4 1/5 1/6;
@@ -734,6 +740,134 @@ end
734740
0.2414170219 0.5865285289 3.318413247 -5.444632124])
735741
@test log(A4) logA4
736742
@test exp(log(A4)) A4
743+
@test typeof(log(A4)) == Matrix{elty}
744+
745+
# real triu matrix
746+
A5 = convert(Matrix{elty}, [1 2 3; 0 4 5; 0 0 6]) # triu
747+
logA5 = convert(Matrix{elty}, [0.0 0.9241962407465937 0.5563245488984037;
748+
0.0 1.3862943611198906 1.0136627702704109;
749+
0.0 0.0 1.791759469228055])
750+
@test log(A5) logA5
751+
@test exp(log(A5)) A5
752+
@test typeof(log(A5)) == Matrix{elty}
753+
754+
# real quasitriangular schur form with 2 2x2 blocks, 2 1x1 blocks, and all positive eigenvalues
755+
A6 = convert(Matrix{elty}, [2 3 2 2 3 1;
756+
1 3 3 2 3 1;
757+
3 3 3 1 1 2;
758+
2 1 2 2 2 2;
759+
1 1 2 2 3 1;
760+
2 2 2 2 1 3])
761+
@test exp(log(A6)) A6
762+
@test typeof(log(A6)) == Matrix{elty}
763+
764+
# real quasitriangular schur form with a negative eigenvalue
765+
A7 = convert(Matrix{elty}, [1 3 3 2 2 2;
766+
1 2 1 3 1 2;
767+
3 1 2 3 2 1;
768+
3 1 2 2 2 1;
769+
3 1 3 1 2 1;
770+
1 1 3 1 1 3])
771+
@test exp(log(A7)) A7
772+
@test typeof(log(A7)) == Matrix{complex(elty)}
773+
774+
if elty <: Complex
775+
A8 = convert(Matrix{elty}, [1 + 1im 1 + 1im 1 - 1im;
776+
1 + 1im -1 + 1im 1 + 1im;
777+
1 - 1im 1 + 1im -1 - 1im])
778+
logA8 = convert(
779+
Matrix{elty},
780+
[0.9478628953131517 + 1.3725201223387407im -0.2547157147532057 + 0.06352318334299434im 0.8560050197863862 - 1.0471975511965979im;
781+
-0.2547157147532066 + 0.06352318334299467im -0.16285783922644065 + 0.2617993877991496im 0.2547157147532063 + 2.1579182857361894im;
782+
0.8560050197863851 - 1.0471975511965974im 0.25471571475320665 + 2.1579182857361903im 0.9478628953131519 - 0.8489213467404436im],
783+
)
784+
@test log(A8) logA8
785+
@test exp(log(A8)) A8
786+
@test typeof(log(A8)) == Matrix{elty}
787+
end
788+
end
789+
790+
@testset "matrix logarithm is type-inferrable" for elty in (Float32,Float64,ComplexF32,ComplexF64)
791+
A1 = randn(elty, 4, 4)
792+
@inferred Union{Matrix{elty},Matrix{complex(elty)}} log(A1)
793+
end
794+
795+
@testset "Additional matrix square root tests" for elty in (Float64, ComplexF64)
796+
A11 = convert(Matrix{elty}, [3 2; -5 -3])
797+
@test sqrt(A11)^2 A11
798+
799+
A13 = convert(Matrix{elty}, [2 0; 0 2])
800+
@test typeof(sqrt(A13)) == Array{elty, 2}
801+
802+
T = elty == Float64 ? Symmetric : Hermitian
803+
@test typeof(sqrt(T(A13))) == T{elty, Array{elty, 2}}
804+
805+
A1 = convert(Matrix{elty}, [4 2 0; 1 4 1; 1 1 4])
806+
sqrtA1 = convert(Matrix{elty}, [1.971197119306979 0.5113118387140085 -0.03301921523780871;
807+
0.23914631173809942 1.9546875116880718 0.2556559193570036;
808+
0.23914631173810008 0.22263670411919556 1.9877067269258815])
809+
@test sqrt(A1) sqrtA1
810+
@test sqrt(A1)^2 A1
811+
@test typeof(sqrt(A1)) == Matrix{elty}
812+
813+
A4 = convert(Matrix{elty}, [1/2 1/3 1/4 1/5+eps();
814+
1/3 1/4 1/5 1/6;
815+
1/4 1/5 1/6 1/7;
816+
1/5 1/6 1/7 1/8])
817+
sqrtA4 = convert(
818+
Matrix{elty},
819+
[0.590697761556362 0.3055006800405779 0.19525404749300546 0.14007621469988107;
820+
0.30550068004057784 0.2825388389385975 0.21857572599211642 0.17048692323164674;
821+
0.19525404749300565 0.21857572599211622 0.21155429252242863 0.18976816626246887;
822+
0.14007621469988046 0.17048692323164724 0.1897681662624689 0.20075085592778794],
823+
)
824+
@test sqrt(A4) sqrtA4
825+
@test sqrt(A4)^2 A4
826+
@test typeof(sqrt(A4)) == Matrix{elty}
827+
828+
# real triu matrix
829+
A5 = convert(Matrix{elty}, [1 2 3; 0 4 5; 0 0 6]) # triu
830+
sqrtA5 = convert(Matrix{elty}, [1.0 0.6666666666666666 0.6525169217864183;
831+
0.0 2.0 1.1237243569579454;
832+
0.0 0.0 2.449489742783178])
833+
@test sqrt(A5) sqrtA5
834+
@test sqrt(A5)^2 A5
835+
@test typeof(sqrt(A5)) == Matrix{elty}
836+
837+
# real quasitriangular schur form with 2 2x2 blocks, 2 1x1 blocks, and all positive eigenvalues
838+
A6 = convert(Matrix{elty}, [2 3 2 2 3 1;
839+
1 3 3 2 3 1;
840+
3 3 3 1 1 2;
841+
2 1 2 2 2 2;
842+
1 1 2 2 3 1;
843+
2 2 2 2 1 3])
844+
@test sqrt(A6)^2 A6
845+
@test typeof(sqrt(A6)) == Matrix{elty}
846+
847+
# real quasitriangular schur form with a negative eigenvalue
848+
A7 = convert(Matrix{elty}, [1 3 3 2 2 2;
849+
1 2 1 3 1 2;
850+
3 1 2 3 2 1;
851+
3 1 2 2 2 1;
852+
3 1 3 1 2 1;
853+
1 1 3 1 1 3])
854+
@test sqrt(A7)^2 A7
855+
@test typeof(sqrt(A7)) == Matrix{complex(elty)}
856+
857+
if elty <: Complex
858+
A8 = convert(Matrix{elty}, [1 + 1im 1 + 1im 1 - 1im;
859+
1 + 1im -1 + 1im 1 + 1im;
860+
1 - 1im 1 + 1im -1 - 1im])
861+
sqrtA8 = convert(
862+
Matrix{elty},
863+
[1.2559748527474284 + 0.6741878819930323im 0.20910077991005582 + 0.24969165051825476im 0.591784212275146 - 0.6741878819930327im;
864+
0.2091007799100553 + 0.24969165051825515im 0.3320953202361413 + 0.2915044496279425im 0.33209532023614136 + 1.0568713143581219im;
865+
0.5917842122751455 - 0.674187881993032im 0.33209532023614147 + 1.0568713143581223im 0.7147787526012315 - 0.6323750828833452im],
866+
)
867+
@test sqrt(A8) sqrtA8
868+
@test sqrt(A8)^2 A8
869+
@test typeof(sqrt(A8)) == Matrix{elty}
870+
end
737871
end
738872

739873
@testset "issue #7181" begin

‎stdlib/LinearAlgebra/test/triangular.jl

+31-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo
198198
end
199199

200200
#exp/log
201-
if (elty1 == Float64 || elty1 == ComplexF64) && (t1 == UpperTriangular || t1 == LowerTriangular)
201+
if elty1 (Float32,Float64,ComplexF32,ComplexF64)
202202
@test exp(Matrix(log(A1))) A1
203203
end
204204

@@ -496,10 +496,40 @@ end
496496
# Matrix square root
497497
Atn = UpperTriangular([-1 1 2; 0 -2 2; 0 0 -3])
498498
Atp = UpperTriangular([1 1 2; 0 2 2; 0 0 3])
499+
Atu = UnitUpperTriangular([1 1 2; 0 1 2; 0 0 1])
499500
@test sqrt(Atn) |> t->t*t Atn
501+
@test sqrt(Atn) isa UpperTriangular
500502
@test typeof(sqrt(Atn)[1,1]) <: Complex
501503
@test sqrt(Atp) |> t->t*t Atp
504+
@test sqrt(Atp) isa UpperTriangular
502505
@test typeof(sqrt(Atp)[1,1]) <: Real
506+
@test typeof(sqrt(complex(Atp))[1,1]) <: Complex
507+
@test sqrt(Atu) |> t->t*t Atu
508+
@test sqrt(Atu) isa UnitUpperTriangular
509+
@test typeof(sqrt(Atu)[1,1]) <: Real
510+
@test typeof(sqrt(complex(Atu))[1,1]) <: Complex
511+
512+
@testset "check matrix logarithm type-inferrable" for elty in (Float32,Float64,ComplexF32,ComplexF64)
513+
A = UpperTriangular(exp(triu(randn(elty, n, n))))
514+
@inferred Union{typeof(A),typeof(complex(A))} log(A)
515+
@test exp(Matrix(log(A))) A
516+
if elty <: Real
517+
@test typeof(log(A)) <: UpperTriangular{elty}
518+
@test typeof(log(complex(A))) <: UpperTriangular{complex(elty)}
519+
@test isreal(log(complex(A)))
520+
@test log(complex(A)) log(A)
521+
end
522+
523+
Au = UnitUpperTriangular(exp(triu(randn(elty, n, n), 1)))
524+
@inferred Union{typeof(A),typeof(complex(A))} log(Au)
525+
@test exp(Matrix(log(Au))) Au
526+
if elty <: Real
527+
@test typeof(log(Au)) <: UpperTriangular{elty}
528+
@test typeof(log(complex(Au))) <: UpperTriangular{complex(elty)}
529+
@test isreal(log(complex(Au)))
530+
@test log(complex(Au)) log(Au)
531+
end
532+
end
503533

504534
Areal = randn(n, n)/2
505535
Aimg = randn(n, n)/2

0 commit comments

Comments
 (0)
Please sign in to comment.