Skip to content

Commit 4faebad

Browse files
committed
special case power!(A, 0/1)
1 parent 5ad48ee commit 4faebad

4 files changed

Lines changed: 58 additions & 1 deletion

File tree

src/implementations/power.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ power!(A::AbstractMatrix, p::Real, out, alg::DefaultAlgorithm) = power!(A, p, ou
3030
# -------
3131
initialize_output(::typeof(power!), A::AbstractMatrix, p::Real, ::AbstractAlgorithm) = A
3232

33+
3334
# Implementation
3435
# --------------
3536
function power!(A::AbstractMatrix, p::Real, powA, alg::MatrixFunctionViaLA)
3637
check_input(power!, A, p, powA, alg)
3738
isempty(alg.kwargs) || throw(ArgumentError("`MatrixFunctionViaLA` does not accept keyword arguments for `power`"))
39+
iszero(p) && return one!(powA)
40+
isone(p) && ((powA === A || copy!(powA, A)); return powA)
3841
powAc = A^p
3942
if eltype(powAc) <: Complex && !(eltype(powA) <: Complex)
4043
# `LinearAlgebra` computes fractional powers of real matrices in complex
@@ -53,6 +56,8 @@ end
5356

5457
function power!(A::AbstractMatrix, p::Real, powA, alg::MatrixFunctionViaEigh)
5558
check_input(power!, A, p, powA, alg)
59+
iszero(p) && return one!(powA)
60+
isone(p) && ((powA === A || copy!(powA, A)); return powA)
5661
D, V = eigh_full!(A, alg.eigh_alg)
5762
diag_alg = DiagonalAlgorithm(; domain_atol = alg.domain_atol)
5863
if isinteger(p)
@@ -68,6 +73,8 @@ end
6873

6974
function power!(A::AbstractMatrix, p::Real, powA, alg::MatrixFunctionViaEig)
7075
check_input(power!, A, p, powA, alg)
76+
iszero(p) && return one!(powA)
77+
isone(p) && ((powA === A || copy!(powA, A)); return powA)
7178
D, V = eig_full!(A, alg.eig_alg)
7279
diag_alg = DiagonalAlgorithm(; domain_atol = alg.domain_atol)
7380
if eltype(A) <: Real
@@ -85,6 +92,8 @@ end
8592
# --------------
8693
function power!(A::AbstractMatrix, p::Real, powA, alg::DiagonalAlgorithm)
8794
check_input(power!, A, p, powA, alg)
95+
iszero(p) && return one!(powA)
96+
isone(p) && ((powA === A || copy!(powA, A)); return powA)
8897
λ = diagview(powA)
8998
copyto!(λ, diagview(A))
9099
if isinteger(p)

src/interface/power.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ Compute the matrix power `powA = A^p` of the square matrix `A`.
1111
For integer `p` this is defined for any square matrix (invertible if `p < 0`);
1212
for fractional `p` the principal power `exp(p * log(A))` is computed.
1313
14+
The exponents `p = 0` and `p = 1` are resolved directly as `I` and `A`, without computing any
15+
decomposition, so they apply to every square matrix regardless of the algorithm selected.
16+
1417
The scalar type of the output matches that of the input.
1518
As a consequence, for fractional `p`, a real matrix with eigenvalues on the negative
1619
real axis, for which the principal power is complex, leads to a `DomainError`; pass a

test/matrixfunctions/power.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ if !is_buildkite
3535
)
3636
TestSuite.test_power(T, (m, m))
3737
TestSuite.test_power_algs(T, (m, m), LAPACK_EIG_ALGS)
38+
TestSuite.test_power_trivial(T, (m, m), LAPACK_EIG_ALGS)
39+
TestSuite.test_power_trivial(T, (m, m), LAPACK_EIGH_ALGS; test_rejected_input = true)
3840
TestSuite.test_power_hermitian(T, (m, m), LAPACK_EIG_ALGS; exact_hermiticity = false)
3941
TestSuite.test_power_hermitian(T, (m, m), LAPACK_EIGH_ALGS)
4042
TestSuite.test_power_reference(T, (m, m))
@@ -50,6 +52,8 @@ if !is_buildkite
5052
GS_ALGS = (MatrixFunctionViaEig(QRIteration(; driver = GS())),)
5153
GLA_ALGS = (MatrixFunctionViaEigh(QRIteration(; driver = GLA())),)
5254
TestSuite.test_power_algs(T, (24, 24), GS_ALGS)
55+
TestSuite.test_power_trivial(T, (24, 24), GS_ALGS)
56+
TestSuite.test_power_trivial(T, (24, 24), GLA_ALGS; test_rejected_input = true)
5357
TestSuite.test_power_hermitian(T, (24, 24), GS_ALGS; exact_hermiticity = false)
5458
TestSuite.test_power_hermitian(T, (24, 24), GLA_ALGS)
5559
TestSuite.test_power_domain(T, (md, md), GS_ALGS)
@@ -63,6 +67,7 @@ if !is_buildkite
6367
test_spectrum = !(T in DiagonalOnlyFloats)
6468
TestSuite.test_power(AT, m)
6569
TestSuite.test_power_algs(AT, m, (DiagonalAlgorithm(),))
70+
TestSuite.test_power_trivial(AT, m, (DiagonalAlgorithm(),))
6671
TestSuite.test_power_hermitian(AT, m, (DiagonalAlgorithm(),); test_spectrum)
6772
TestSuite.test_power_reference(AT, m; test_hermitian = !(T in GenericFloats))
6873
TestSuite.test_power_domain(AT, md, (DiagonalAlgorithm(),); test_singular = true)
@@ -82,11 +87,13 @@ if CUDA.functional()
8287
MatrixFunctionViaEigh(DivideAndConquer()),
8388
)
8489
TestSuite.test_power_hermitian(CuMatrix{T}, (m, m), CUDA_EIGH_ALGS)
90+
TestSuite.test_power_trivial(CuMatrix{T}, (m, m), CUDA_EIGH_ALGS; test_rejected_input = true)
8591
TestSuite.test_power_domain(CuMatrix{T}, (md, md), CUDA_EIGH_ALGS; hermitian_output = true)
8692

8793
AT = Diagonal{T, CuVector{T}}
8894
TestSuite.test_power(AT, m)
8995
TestSuite.test_power_algs(AT, m, (DiagonalAlgorithm(),))
96+
TestSuite.test_power_trivial(AT, m, (DiagonalAlgorithm(),))
9097
TestSuite.test_power_hermitian(AT, m, (DiagonalAlgorithm(),))
9198
TestSuite.test_power_domain(AT, md, (DiagonalAlgorithm(),); test_singular = true)
9299
end
@@ -102,11 +109,13 @@ if AMDGPU.functional()
102109
MatrixFunctionViaEigh(DivideAndConquer()),
103110
)
104111
TestSuite.test_power_hermitian(ROCMatrix{T}, (m, m), ROC_EIGH_ALGS)
112+
TestSuite.test_power_trivial(ROCMatrix{T}, (m, m), ROC_EIGH_ALGS; test_rejected_input = true)
105113
TestSuite.test_power_domain(ROCMatrix{T}, (md, md), ROC_EIGH_ALGS; hermitian_output = true)
106114

107115
AT = Diagonal{T, ROCVector{T}}
108116
TestSuite.test_power(AT, m)
109117
TestSuite.test_power_algs(AT, m, (DiagonalAlgorithm(),))
118+
TestSuite.test_power_trivial(AT, m, (DiagonalAlgorithm(),))
110119
TestSuite.test_power_hermitian(AT, m, (DiagonalAlgorithm(),))
111120
TestSuite.test_power_domain(AT, md, (DiagonalAlgorithm(),); test_singular = true)
112121
end

test/testsuite/matrixfunctions/power.jl

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using TestExtras
22
using LinearAlgebra: LinearAlgebra, I, SingularException
3-
using MatrixAlgebraKit: ishermitian
3+
using MatrixAlgebraKit: ishermitian, one!
44

55
# `power` takes the exponent as a second positional argument, and splits into an integer branch
66
# (repeated multiplication, defined for any square matrix) and a fractional branch (the principal
@@ -71,6 +71,42 @@ function test_power_algs(T::Type, sz, algs; ps = (2, -1), qs = (1 // 2, -1 // 4)
7171
end
7272
end
7373

74+
# `A^0 = I` and `A^1 = A` hold for every square matrix, and both are short-circuited before any
75+
# decomposition is computed, so the results are exact rather than merely approximate.
76+
#
77+
# `test_rejected_input = true` for algorithms that cannot handle a general matrix at all (the
78+
# `eigh`-based ones): feeding them input they would reject proves the decomposition really is
79+
# skipped, rather than being computed and happening to give the right answer.
80+
function test_power_trivial(T::Type, sz, algs; test_rejected_input = false, kwargs...)
81+
R = real(eltype(T))
82+
summary_str = testargs_summary(T, sz)
83+
return @testset "power trivial exponents algorithm $alg $summary_str" for alg in algs
84+
A = instantiate_offaxis_matrix(T, sz)
85+
Id = one!(deepcopy(A))
86+
87+
@testset "integer p = $p" for p in (0, 1)
88+
powA = @testinferred power(A, p, alg)
89+
@test powA == (iszero(p) ? Id : A)
90+
# in-place, where the output aliases the input
91+
@test power!(deepcopy(A), p, alg) == (iszero(p) ? Id : A)
92+
end
93+
@testset "float p = $p" for p in (zero(R), one(R))
94+
powA = @testinferred power(A, p, alg)
95+
@test powA == (iszero(p) ? Id : A)
96+
end
97+
98+
if test_rejected_input
99+
B = instantiate_smallnorm_matrix(T, sz)
100+
IdB = one!(deepcopy(B))
101+
# confirm the premise: this algorithm cannot decompose `B`
102+
@test_throws DomainError power(B, 2, alg)
103+
# yet the trivial exponents never reach the decomposition
104+
@test power(B, 0, alg) == IdB
105+
@test power(B, 1, alg) == B
106+
end
107+
end
108+
end
109+
74110
# See the corresponding comment in `squareroot.jl` for `exact_hermiticity`.
75111
function test_power_hermitian(
76112
T::Type, sz, algs;

0 commit comments

Comments
 (0)