Skip to content

Commit 53fe1cd

Browse files
sanderdemeyerlkdvosJutho
authored
Feature: Exponential (#94)
* implement exponential work in progress * update on exponential * remove comment * comments change some input tests remove redundant comment include ComplexF16 in tests fix unchanged test names and docs improve allocations * change name of decompositions.jl to matrixfunctions.jl * revert name change * general comments change name to `MatrixFunctionViaEig` etc change `decompositions` to `matrixfunctions` add default algorithm for Diagonal matrices add input checks add @testthrows to catch non-hermitian matrices being given to MatrixFunctionViaEigh change default exponential algorithm to e.g. `MatrixFunctionViaEig` of the default `eig_alg` * bug fix Name change in `MatrixAlgebraKit.jl` fix formatting * avoid allocation in diagonal case * include exponentiali(tau, A) * remove simple test case and make the test more general * fix formatting * add docs * remove a bunch of allocations and clean up * introduce `map_diagonal` to simplify and relax types * rework tests * revert wrong filename changes * avoid running non-GPU tests through buildkite * correct wrong in-place assumptions * fixes part II * exponentialr instead of exponentiali * fix default algorithms * fix default algorithms 2 * Fix merge * fix default exponential algorithm * add packages to tests * Update ext/MatrixAlgebraKitGenericSchurExt.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update test/genericlinearalgebra/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update test/genericlinearalgebra/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * change name of `exponentialr` to `exponential` * fix formatting * Fix `check_scalar` * Update `check_scalar` * remove redundant `ishermitian` checks * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * change from two-argument to one-argument `check_input` * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl * Remove undefined exports * Commit suggestions * Update src/interface/matrixfunctions.jl Co-authored-by: Jutho <Jutho@users.noreply.github.com> * Update src/interface/matrixfunctions.jl Co-authored-by: Jutho <Jutho@users.noreply.github.com> * Update src/interface/matrixfunctions.jl Co-authored-by: Jutho <Jutho@users.noreply.github.com> * Update src/interface/exponential.jl Co-authored-by: Jutho <Jutho@users.noreply.github.com> * Update exponential.jl * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Fix typo in suggested change * Update exponential via MatrixFunctionViaEigh * Update src/interface/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/interface/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update src/implementations/exponential.jl Co-authored-by: Lukas Devos <ldevos98@gmail.com> * Update exponential.jl --------- Co-authored-by: Lukas Devos <ldevos98@gmail.com> Co-authored-by: Jutho <Jutho@users.noreply.github.com>
1 parent 0dfcb52 commit 53fe1cd

10 files changed

Lines changed: 402 additions & 1 deletion

File tree

ext/MatrixAlgebraKitGenericSchurExt.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ function MatrixAlgebraKit.default_eig_algorithm(
1414
return QRIteration(; driver, kwargs...)
1515
end
1616

17+
function MatrixAlgebraKit.default_exponential_algorithm(
18+
type::Type{T}; kwargs...
19+
) where {T <: StridedMatrix{<:GSFloat}}
20+
eig_alg = MatrixAlgebraKit.default_eig_algorithm(type; kwargs...)
21+
return MatrixFunctionViaEig(eig_alg)
22+
end
23+
1724
function geev!(::GS, A::AbstractMatrix, Dd::AbstractVector, V::AbstractMatrix; kwargs...)
1825
D, Vmat = GenericSchur.eigen!(A)
1926
copyto!(Dd, D)

src/MatrixAlgebraKit.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export left_polar, right_polar
3030
export left_polar!, right_polar!
3131
export left_orth, right_orth, left_null, right_null
3232
export left_orth!, right_orth!, left_null!, right_null!
33+
export exponential, exponential!
3334

3435
export Householder, Native_HouseholderQR, Native_HouseholderLQ
3536
export DivideAndConquer, SafeDivideAndConquer, QRIteration, Bisection, Jacobi, SVDViaPolar
@@ -40,6 +41,7 @@ export LAPACK_HouseholderQR, LAPACK_HouseholderLQ, LAPACK_Simple, LAPACK_Expert,
4041
export GLA_HouseholderQR, GLA_QRIteration, GS_QRIteration
4142
export LQViaTransposedQR
4243
export PolarViaSVD, PolarNewton
44+
export MatrixFunctionViaLA, MatrixFunctionViaEig, MatrixFunctionViaEigh
4345
export DefaultAlgorithm
4446
export DiagonalAlgorithm
4547
export NativeBlocked
@@ -96,9 +98,12 @@ include("common/utility.jl")
9698

9799
include("yalapack.jl")
98100
include("algorithms.jl")
101+
99102
include("interface/projections.jl")
100103
include("interface/decompositions.jl")
101104
include("interface/truncation.jl")
105+
include("interface/matrixfunctions.jl")
106+
102107
include("interface/qr.jl")
103108
include("interface/lq.jl")
104109
include("interface/svd.jl")
@@ -108,6 +113,7 @@ include("interface/gen_eig.jl")
108113
include("interface/schur.jl")
109114
include("interface/polar.jl")
110115
include("interface/orthnull.jl")
116+
include("interface/exponential.jl")
111117

112118
include("implementations/projections.jl")
113119
include("implementations/truncation.jl")
@@ -120,6 +126,7 @@ include("implementations/gen_eig.jl")
120126
include("implementations/schur.jl")
121127
include("implementations/polar.jl")
122128
include("implementations/orthnull.jl")
129+
include("implementations/exponential.jl")
123130

124131
include("common/gauge.jl") # needs to be defined after the functions are
125132

src/common/view.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,26 @@ See also [`diagview`](@ref).
2020

2121
diagonal(v::AbstractVector) = Diagonal(v)
2222

23+
"""
24+
map_diagonal!(f, dst, src...)
25+
26+
Map the scalar function `f` over all elements of the diagonal of `src...`, returning
27+
a diagonal result.
28+
29+
See also [`map_diagonal!`](@ref).
30+
"""
31+
map_diagonal(f, src, srcs...) = diagonal(f.(diagview(src), map(diagview, srcs)...))
32+
33+
"""
34+
map_diagonal!(f, dst, src...)
35+
36+
Map the scalar function `f` over all elements of the diagonal of `src...`,
37+
into the diagonal elements of destination `dst`.
38+
39+
See also [`map_diagonal`](@ref).
40+
"""
41+
map_diagonal!(f, dst, src, srcs...) = (diagview(dst) .= f.(diagview(src), map(diagview, srcs)...); dst)
42+
2343
# triangularind
2444
function lowertriangularind(A::AbstractMatrix)
2545
Base.require_one_based_indexing(A)

src/implementations/exponential.jl

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Inputs
2+
# ------
3+
function copy_input(::typeof(exponential), A::AbstractMatrix)
4+
return copy!(similar(A, float(eltype(A))), A)
5+
end
6+
7+
copy_input(::typeof(exponential), A::Diagonal) = copy(A)
8+
copy_input(::typeof(exponential), (τ, A)::Tuple{Number, AbstractMatrix}) = (τ, copy!(similar(A, float(eltype(A))), A))
9+
copy_input(::typeof(exponential), (τ, A)::Tuple{Number, Diagonal}) = τ, copy(A)
10+
11+
function check_input(::typeof(exponential!), A::AbstractMatrix, expA::AbstractMatrix, alg::AbstractAlgorithm)
12+
m = LinearAlgebra.checksquare(A)
13+
@check_size(expA, (m, m))
14+
@check_scalar(expA, A)
15+
return nothing
16+
end
17+
18+
function check_input(::typeof(exponential!), A::AbstractMatrix, expA::AbstractMatrix, ::DiagonalAlgorithm)
19+
m = LinearAlgebra.checksquare(A)
20+
@assert isdiag(A)
21+
@assert expA isa Diagonal
22+
@check_size(expA, (m, m))
23+
@check_scalar(expA, A)
24+
return nothing
25+
end
26+
27+
function check_input(::typeof(exponential!), (τ, A)::Tuple{Number, AbstractMatrix}, expA::AbstractMatrix, alg::AbstractAlgorithm)
28+
m = LinearAlgebra.checksquare(A)
29+
@check_size(expA, (m, m))
30+
@check_scalar(expA, A, (τ isa Real) ? identity : complex)
31+
return nothing
32+
end
33+
34+
function check_input(::typeof(exponential!), (τ, A)::Tuple{Number, AbstractMatrix}, expA::AbstractMatrix, ::DiagonalAlgorithm)
35+
m = LinearAlgebra.checksquare(A)
36+
@assert isdiag(A)
37+
@assert expA isa Diagonal
38+
@check_size(expA, (m, m))
39+
@check_scalar(expA, A, (τ isa Real) ? identity : complex)
40+
return nothing
41+
end
42+
43+
# Outputs
44+
# -------
45+
initialize_output(::typeof(exponential!), A::AbstractMatrix, ::AbstractAlgorithm) = A
46+
initialize_output(::typeof(exponential!), (τ, A)::Tuple{T, AbstractMatrix}, ::AbstractAlgorithm) where {T <: Real} = A
47+
initialize_output(::typeof(exponential!), (τ, A)::Tuple{Number, AbstractMatrix}, ::AbstractAlgorithm) = complex(A)
48+
49+
# Implementation
50+
# --------------
51+
function exponential!(A::AbstractMatrix, expA::AbstractMatrix, alg::MatrixFunctionViaLA)
52+
check_input(exponential!, A, expA, alg)
53+
A = LinearAlgebra.exp!(A)
54+
A === expA || copy!(expA, A)
55+
return expA
56+
end
57+
58+
exponential!(A, expA, alg::MatrixFunctionViaEigh) = exponential!((one(eltype(A)), A), expA, alg)
59+
exponential!(A::AbstractMatrix, expA::AbstractMatrix, alg::MatrixFunctionViaEig) = exponential!((one(eltype(A)), A), expA, alg)
60+
61+
function exponential!((τ, A)::Tuple{Number, AbstractMatrix}, expA::AbstractMatrix, alg::AbstractAlgorithm)
62+
expA .= A .* τ
63+
return exponential!(expA, expA, alg)
64+
end
65+
66+
function exponential!((τ, A)::Tuple{Number, AbstractMatrix}, expA::AbstractMatrix, alg::MatrixFunctionViaEigh)
67+
check_input(exponential!, (τ, A), expA, alg)
68+
D, V = eigh_full!(A, alg.eigh_alg)
69+
if eltype(A) <: Real
70+
if eltype(τ) <: Real
71+
VexpD = rmul!(V, exponential!((τ / 2, D), D))
72+
else
73+
VexpD = V * exponential((τ / 2, D))
74+
end
75+
return mul!(expA, VexpD, transpose(VexpD))
76+
else
77+
if eltype(τ) <: Real
78+
VexpD = V * exponential!((τ, D), D)
79+
else
80+
VexpD = V * exponential((τ, D))
81+
end
82+
return mul!(expA, VexpD, V')
83+
end
84+
end
85+
86+
function exponential!((τ, A)::Tuple{Number, AbstractMatrix}, expA::AbstractMatrix, alg::MatrixFunctionViaEig)
87+
check_input(exponential!, (τ, A), expA, alg)
88+
D, V = eig_full!(A, alg.eig_alg)
89+
if eltype(A) <: Real && eltype(τ) <: Real
90+
VexpD = V * exponential!((τ, D), D)
91+
expAc = rdiv!(VexpD, LinearAlgebra.lu!(V))
92+
return expA .= real.(expAc)
93+
else
94+
expA .= V .* transpose(diagview(exponential!((τ, D), D)))
95+
return rdiv!(expA, LinearAlgebra.lu!(V))
96+
end
97+
end
98+
99+
# Diagonal logic
100+
# --------------
101+
function exponential!(A, expA, alg::DiagonalAlgorithm)
102+
check_input(exponential!, A, expA, alg)
103+
return map_diagonal!(exp, expA, A)
104+
end
105+
106+
function exponential!((τ, A)::Tuple{Number, AbstractMatrix}, expA::AbstractMatrix, alg::DiagonalAlgorithm)
107+
check_input(exponential!, (τ, A), expA, alg)
108+
return map_diagonal!(x -> exp(x * τ), expA, A)
109+
end

src/interface/exponential.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Exponential functions
2+
# --------------
3+
4+
"""
5+
exponential(A; kwargs...) -> expA
6+
exponential(A, alg::AbstractAlgorithm) -> expA
7+
exponential!(A, [expA]; kwargs...) -> expA
8+
exponential!(A, [expA], alg::AbstractAlgorithm) -> expA
9+
exponential((τ, A); kwargs...) -> expτA
10+
exponential((τ, A), alg::AbstractAlgorithm) -> expτA
11+
exponential!((τ, A), [expA]; kwargs...) -> expτA
12+
exponential!((τ, A), [expA], alg::AbstractAlgorithm) -> expτA
13+
14+
Compute the exponential of the square matrix `A` or `τ * A`,
15+
16+
!!! note
17+
The bang method `exponential!` optionally accepts the output structure and
18+
possibly destroys the input matrix `A`. Always use the return value of the function
19+
as it may not always be possible to use the provided `expA` as output.
20+
"""
21+
@functiondef exponential
22+
23+
# Algorithm selection
24+
# -------------------
25+
default_exponential_algorithm(A; kwargs...) = default_exponential_algorithm(typeof(A); kwargs...)
26+
function default_exponential_algorithm(T::Type; kwargs...)
27+
return MatrixFunctionViaLA(; kwargs...)
28+
end
29+
function default_exponential_algorithm(::Type{T}; kwargs...) where {T <: Diagonal}
30+
return DiagonalAlgorithm(; kwargs...)
31+
end
32+
33+
function default_algorithm(::typeof(exponential!), ::Type{A}; kwargs...) where {A}
34+
return default_exponential_algorithm(A; kwargs...)
35+
end
36+
37+
function default_algorithm(::typeof(exponential!), ::Tuple{A, B}; kwargs...) where {A, B}
38+
return default_exponential_algorithm(B; kwargs...)
39+
end

src/interface/matrixfunctions.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# ================================
2+
# EXPONENTIAL ALGORITHMS
3+
# ================================
4+
"""
5+
MatrixFunctionViaLA()
6+
7+
Algorithm type to denote finding the exponential of `A` via the implementation of `LinearAlgebra`.
8+
"""
9+
@algdef MatrixFunctionViaLA
10+
11+
"""
12+
MatrixFunctionViaEigh(eigh_alg)
13+
14+
Algorithm type for computing a function of a matrix by computing its hermitian eigenvalue decomposition and applying the function to the eigenvalues.
15+
The `eigh_alg` specifies which hermitian eigendecomposition implementation to use.
16+
"""
17+
struct MatrixFunctionViaEigh{A <: AbstractAlgorithm} <: AbstractAlgorithm
18+
eigh_alg::A
19+
end
20+
function Base.show(io::IO, alg::MatrixFunctionViaEigh)
21+
print(io, "MatrixFunctionViaEigh(")
22+
_show_alg(io, alg.eigh_alg)
23+
return print(io, ")")
24+
end
25+
26+
"""
27+
MatrixFunctionViaEig(eig_alg)
28+
29+
Algorithm type for computing a function of a matrix by computing its eigenvalue decomposition and applying the function to the eigenvalues.
30+
The `eig_alg` specifies which eigendecomposition implementation to use.
31+
"""
32+
struct MatrixFunctionViaEig{A <: AbstractAlgorithm} <: AbstractAlgorithm
33+
eig_alg::A
34+
end
35+
function Base.show(io::IO, alg::MatrixFunctionViaEig)
36+
print(io, "MatrixFunctionViaEig(")
37+
_show_alg(io, alg.eig_alg)
38+
return print(io, ")")
39+
end

src/matrixfunctions.jl

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/exponential.jl

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using MatrixAlgebraKit
2+
using Test
3+
using TestExtras
4+
using StableRNGs
5+
using MatrixAlgebraKit: diagview
6+
using LinearAlgebra
7+
using LinearAlgebra: exp
8+
9+
BLASFloats = (Float32, Float64, ComplexF32, ComplexF64)
10+
GenericFloats = (Float16, ComplexF16, BigFloat, Complex{BigFloat})
11+
12+
@testset "exponential! for T = $T" for T in BLASFloats
13+
rng = StableRNG(123)
14+
m = 54
15+
16+
A = LinearAlgebra.normalize!(randn(rng, T, m, m))
17+
Ac = copy(A)
18+
expA = LinearAlgebra.exp(A)
19+
20+
expA2 = @constinferred exponential(A)
21+
@test expA expA2
22+
@test A == Ac
23+
24+
algs = (MatrixFunctionViaLA(), MatrixFunctionViaEig(LAPACK_Simple()))
25+
@testset "algorithm $alg" for alg in algs
26+
expA2 = @constinferred exponential(A, alg)
27+
@test expA expA2
28+
@test A == Ac
29+
end
30+
31+
@test_throws DomainError exponential(A; alg = MatrixFunctionViaEigh(LAPACK_QRIteration()))
32+
end
33+
34+
@testset "exponential! for T = $T" for T in BLASFloats
35+
rng = StableRNG(123)
36+
m = 54
37+
38+
A = randn(rng, T, m, m)
39+
τ = randn(rng, T)
40+
Ac = copy(A)
41+
42+
= A * τ
43+
expAτ = LinearAlgebra.exp(Aτ)
44+
45+
expAτ2 = @constinferred exponential((τ, A))
46+
@test expAτ expAτ2
47+
@test A == Ac
48+
49+
algs = (MatrixFunctionViaLA(), MatrixFunctionViaEig(LAPACK_Simple()))
50+
@testset "algorithm $alg" for alg in algs
51+
expAτ2 = @constinferred exponential((τ, A), alg)
52+
@test expAτ expAτ2
53+
@test A == Ac
54+
end
55+
56+
@test_throws DomainError exponential((τ, A); alg = MatrixFunctionViaEigh(LAPACK_QRIteration()))
57+
end
58+
59+
@testset "exponential! for Diagonal{$T}" for T in (BLASFloats..., GenericFloats...)
60+
rng = StableRNG(123)
61+
m = 54
62+
63+
A = Diagonal(randn(rng, T, m))
64+
τ = randn(rng, T)
65+
Ac = copy(A)
66+
67+
expA = LinearAlgebra.exp(A)
68+
69+
expA2 = @constinferred exponential(A)
70+
@test expA expA2
71+
@test A == Ac
72+
end
73+
74+
@testset "exponential! for Diagonal{$T}" for T in (BLASFloats..., GenericFloats...)
75+
rng = StableRNG(123)
76+
m = 1
77+
78+
A = Diagonal(randn(rng, T, m))
79+
τ = randn(rng, T)
80+
Ac = copy(A)
81+
82+
= A * τ
83+
expAτ = LinearAlgebra.exp(Aτ)
84+
85+
expAτ2 = @constinferred exponential((τ, A))
86+
@test expAτ expAτ2
87+
@test A == Ac
88+
end

0 commit comments

Comments
 (0)