Skip to content

Commit

Permalink
Merge pull request #9 from JuliaDiffEq/v1.3
Browse files Browse the repository at this point in the history
ExponentialUtilities v1.3
  • Loading branch information
MSeeker1340 authored Oct 26, 2018
2 parents de01bcf + 1a72eab commit 9304f45
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 108 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,22 @@ In both cases, `ts` is an array of time snapshots for u, with `U[:,j] ≈ u(ts[j

Apart from keyword arguments that affect the computation of Krylov subspaces (see the Arnoldi iteration section), you can also adjust the timestepping behavior using the arguments. By setting `adaptive=true`, the time step and Krylov subsapce size adaptation scheme of Niesen & Wright is used and the relative tolerance of which can be set using the keyword parameter `tol`. The `delta` and `gamma` parameter of the adaptation scheme can also be adjusted. The `tau` parameter adjusts the size of the timestep (and for `adaptive=true`, the initial timestep). By default, it is calculated using a heuristic formula by Niesen & Wright.

### Support for matrix-free operators

You can use any object as the "matrix" `A` as long as it implements the following linear operator interface:

* `LinearAlgebra.mul!(y, A, x)` (for computing `y = A * x` in place).
* `Base.size(A, dim)`
* `LinearAlgebra.opnorm(A, p=Inf)`. If this is not implemented or the default implementation can be slow, you can manually pass in the operator norm (a rough estimate is fine) using the keyword argument `opnorm`.
* `LinearAlgebra.ishermitian(A)`. If this is not implemented or the default implementation can be slow, you can manually pass in the value using the keyword argument `ishermitian`.

## Matrix-phi function `phi`

```julia
phi(z,k[;cache]) -> [ϕ_0(z),ϕ_1(z),...,ϕ_k(z)]
```

Compute ϕ function directly. `z` can both be a scalar or a Matrix. This is used by the caching versions of the ExpRK integrators to precompute the operators.
Compute ϕ function directly. `z` can both be a scalar or a `AbstractMatrix` (note that unlike the previous functions, you *need* to use a concrete matrix). This is used by the caching versions of the ExpRK integrators to precompute the operators.

Instead of using the recurrence relation, which is numerically unstable, a formula given by Sidje is used [2].

Expand Down
9 changes: 6 additions & 3 deletions src/ExponentialUtilities.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
module ExponentialUtilities
using LinearAlgebra, SparseArrays
using LinearAlgebra: exp!
using LinearAlgebra, SparseArrays, Printf
using LinearAlgebra: exp!, BlasInt
using LinearAlgebra.LAPACK: stegr!

include("utils.jl")
include("phi.jl")
include("arnoldi.jl")
include("krylov_phiv.jl")
include("krylov_phiv_adaptive.jl")
include("krylov_expv.jl")
include("StegrWork.jl")
include("krylov_phiv_error_estimate.jl")

export phi, phi!, KrylovSubspace, arnoldi, arnoldi!, lanczos!, ExpvCache, PhivCache,
expv, expv!, phiv, phiv!, expv_timestep, expv_timestep!, phiv_timestep, phiv_timestep!,
Expand Down
34 changes: 5 additions & 29 deletions src/stegr_cache.jl → src/StegrWork.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import LinearAlgebra.BLAS: @blasfunc
import LinearAlgebra: BlasInt
module Stegr
using LinearAlgebra
using LinearAlgebra.BLAS: @blasfunc
using LinearAlgebra: BlasInt
import LinearAlgebra.LAPACK: stegr!
const liblapack = Base.liblapack_name

Expand Down Expand Up @@ -101,31 +103,5 @@ function StegrWork(::Type{T}, n::BlasInt,
sw
end

mutable struct StegrCache{T,R<:Real} <: HermitianSubspaceCache{T}
v::Vector{T} # Subspace-propagated vector
w::Vector{T}
sw::StegrWork{R}
StegrCache(::Type{T}, n::Integer) where T = new{T,real(T)}(
Vector{T}(undef, n), Vector{T}(undef, n),
StegrWork(real(T), BlasInt(n)))
end

StegrCache(Ks::KrylovSubspace{B,T,U}) where {B,T,U} =
StegrCache(T, Ks.maxiter)

"""
expT!(α, β, t, cache)
Calculate the subspace exponential `exp(t*T)` for a tridiagonal
subspace matrix `T` with `α` on the diagonal and `β` on the
super-/subdiagonal, diagonalizing via `stegr!`.
"""
function expT!::AbstractVector{R}, β::AbstractVector{R}, t::Number,
cache::StegrCache{T,R}) where {T,R<:Real}
stegr!(α, β, cache.sw)
sel = 1:length(α)
@inbounds for i = sel
cache.w[i] = exp(t*cache.sw.w[i])*cache.sw.Z[1,i]
end
mul!(@view(cache.v[sel]), @view(cache.sw.Z[sel,sel]), @view(cache.w[sel]))
export StegrWork
end
50 changes: 14 additions & 36 deletions src/arnoldi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,9 @@ function Base.show(io::IO, Ks::KrylovSubspace)
println(IOContext(io, :limit => true), getH(Ks))
end

# Helper functions that returns the real part if that is what is
# required (for Hermitian matrices), otherwise returns the value
# as-is.
coeff(::Type{T}::T) where {T} = α
coeff(::Type{U}::T) where {U<:Real,T<:Complex} = real(α)

#######################################
# Arnoldi/Lanczos with custom IOP
## High-level interface
"""
arnoldi(A,b[;m,tol,opnorm,iop]) -> Ks
Expand All @@ -78,19 +73,20 @@ The default value of 0 indicates full Arnoldi. For symmetric/Hermitian `A`,
Refer to `KrylovSubspace` for more information regarding the output.
Happy-breakdown occurs whenver `norm(v_j) < tol * opnorm(A, Inf)`, in this case
Happy-breakdown occurs whenver `norm(v_j) < tol * opnorm`, in this case
the dimension of `Ks` is smaller than `m`.
[^1]: Koskela, A. (2015). Approximating the matrix exponential of an
advection-diffusion operator using the incomplete orthogonalization method. In
Numerical Mathematics and Advanced Applications-ENUMATH 2013 (pp. 345-353).
Springer, Cham.
"""
function arnoldi(A, b; m=min(30, size(A, 1)), tol=1e-7, opnorm=LinearAlgebra.opnorm, iop=0, cache=nothing )
function arnoldi(A, b; m=min(30, size(A, 1)), kwargs...)
Ks = KrylovSubspace{eltype(b)}(length(b), m)
arnoldi!(Ks, A, b; m=m, tol=tol, opnorm=opnorm, iop=iop)
arnoldi!(Ks, A, b; m=m, kwargs...)
end

## Low-level interface
"""
arnoldi_step!(j, iop, n, A, V, H)
Expand All @@ -110,15 +106,17 @@ function arnoldi_step!(j::Integer, iop::Integer, A,
@. y /= beta
beta
end

"""
arnoldi!(Ks,A,b[;tol,m,opnorm,iop]) -> Ks
Non-allocating version of `arnoldi`.
"""
function arnoldi!(Ks::KrylovSubspace{B, T, U}, A, b::AbstractVector{T};
tol::Real=1e-7, m::Int=min(Ks.maxiter, size(A, 1)),
opnorm=LinearAlgebra.opnorm, iop::Int=0, cache=nothing) where {B, T <: Number, U <: Number}
if ishermitian(A)
ishermitian::Bool=LinearAlgebra.ishermitian(A),
opnorm=LinearAlgebra.opnorm(A,Inf), iop::Int=0) where {B, T <: Number, U <: Number}
if ishermitian
return lanczos!(Ks, A, b; tol=tol, m=m, opnorm=opnorm)
end
if m > Ks.maxiter
Expand All @@ -127,7 +125,8 @@ function arnoldi!(Ks::KrylovSubspace{B, T, U}, A, b::AbstractVector{T};
Ks.m = m # might change if happy-breakdown occurs
end
V, H = getV(Ks), getH(Ks)
vtol = tol * opnorm(A, Inf)
# vtol = tol * opnorm
vtol = tol * (opnorm isa Function ? opnorm(A,Inf) : opnorm) # backward compatibility
if iop == 0
iop = m
end
Expand Down Expand Up @@ -167,28 +166,6 @@ function lanczos_step!(j::Integer, A,
β[j]
end

macro diagview(A,d::Integer=0)
s = d<=0 ? 1+abs(d) : :(m+$d)
quote
m = size($(esc(A)),1)
@view($(esc(A))[($s):m+1:end])
end
end

"""
realview(R, V)
Returns a view of the real components of the complex vector `V`.
"""
realview(::Type{R}, V::AbstractVector{C}) where {R,C<:Complex} =
@view(reinterpret(R, V)[1:2:end])
"""
realview(R, V)
Returns a view of the real components of the real vector `V`.
"""
realview(::Type{R}, V::AbstractVector{R}) where {R} = V

"""
lanczos!(Ks,A,b[;tol,m,opnorm]) -> Ks
Expand All @@ -197,14 +174,15 @@ Hermitian matrices.
"""
function lanczos!(Ks::KrylovSubspace{B, T, U}, A, b::AbstractVector{T};
tol=1e-7, m=min(Ks.maxiter, size(A, 1)),
opnorm=LinearAlgebra.opnorm, cache=nothing) where {B, T <: Number, U <: Number}
opnorm=LinearAlgebra.opnorm(A,Inf)) where {B, T <: Number, U <: Number}
if m > Ks.maxiter
resize!(Ks, m)
else
Ks.m = m # might change if happy-breakdown occurs
end
V, H = getV(Ks), getH(Ks)
vtol = tol * opnorm(A, Inf)
# vtol = tol * opnorm
vtol = tol * (opnorm isa Function ? opnorm(A,Inf) : opnorm) # backward compatibility
# Safe checks
n = size(V, 1)
@assert length(b) == size(A,1) == size(A,2) == n "Dimension mismatch"
Expand Down
52 changes: 27 additions & 25 deletions src/krylov_phiv.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,36 @@ employed by setting the kwarg `mode=:error_estimate`.
Compute the expv product using a pre-constructed Krylov subspace.
"""
function expv(t, A, b; m=min(30, size(A, 1)), tol=1e-7, rtol=(tol),
opnorm=LinearAlgebra.opnorm, cache=nothing, iop=0,
mode=:happy_breakdown)
Ks = if mode == :happy_breakdown
arnoldi(A, b; m=m, tol=tol, opnorm=opnorm, iop=iop)
function expv(t, A, b; mode=:happy_breakdown, kwargs...)
# Master dispatch function
if mode == :happy_breakdown
_expv_hb(t, A, b; kwargs...)
elseif mode == :error_estimate
n = size(A,1)
T = promote_type(typeof(t), eltype(A), eltype(b))
U = ishermitian(A) ? real(T) : T
KrylovSubspace{T,U}(n, m)
_expv_ee(t, A, b; kwargs...)
else
error("Unknown Krylov iteration termination mode, $(mode)")
throw(ArgumentError("Unknown Krylov iteration termination mode, $(mode)"))
end
end
function _expv_hb(t, A, b; cache=nothing, kwargs_arnoldi...)
# Happy-breakdown mode: first construct Krylov subspace then expv!
Ks = arnoldi(A, b; kwargs_arnoldi...)
w = similar(b)
if mode == :happy_breakdown
expv!(w, t, Ks; cache=cache)
elseif mode == :error_estimate
expv!(w, t, A, b,
Ks, get_subspace_cache(Ks);
atol=tol, rtol=rtol)
end
expv!(w, t, Ks; cache=cache)
end
function _expv_ee(t, A, b; m=min(30, size(A, 1)), tol=1e-7, rtol=(tol),
ishermitian::Bool=LinearAlgebra.ishermitian(A))
# Error-estimate mode: construction of Krylov subspace and expv! at the same time
n = size(A,1)
T = promote_type(typeof(t), eltype(A), eltype(b))
U = ishermitian ? real(T) : T
Ks = KrylovSubspace{T,U}(n, m)
w = similar(b)
expv!(w, t, A, b, Ks, get_subspace_cache(Ks); atol=tol, rtol=rtol)
end
function expv(t, Ks::KrylovSubspace{B, T, U}; cache=nothing) where {B, T, U}
function expv(t, Ks::KrylovSubspace{B, T, U}; kwargs...) where {B, T, U}
n = size(getV(Ks), 1)
w = Vector{T}(undef, n)
expv!(w, t, Ks; cache=cache)
expv!(w, t, Ks; kwargs...)
end
"""
expv!(w,t,Ks[;cache]) -> w
Expand Down Expand Up @@ -138,17 +142,15 @@ Compute the matrix-phi-vector products using a pre-constructed Krylov subspace.
the φ-functions in exponential integrators. arXiv preprint arXiv:0907.4631.
Formula (10).
"""
function phiv(t, A, b, k; m=min(30, size(A, 1)), tol=1e-7, opnorm=LinearAlgebra.opnorm, iop=0,
cache=nothing, correct=false, errest=false)
Ks = arnoldi(A, b; m=m, tol=tol, opnorm=opnorm, iop=iop)
function phiv(t, A, b, k; cache=nothing, correct=false, errest=false, kwargs_arnoldi...)
Ks = arnoldi(A, b; kwargs_arnoldi...)
w = Matrix{eltype(b)}(undef, length(b), k+1)
phiv!(w, t, Ks, k; cache=cache, correct=correct, errest=errest)
end
function phiv(t, Ks::KrylovSubspace{B, T, U}, k; cache=nothing, correct=false,
errest=false) where {B, T, U}
function phiv(t, Ks::KrylovSubspace{B, T, U}, k; kwargs...) where {B, T, U}
n = size(getV(Ks), 1)
w = Matrix{T}(undef, n, k+1)
phiv!(w, t, Ks, k; cache=cache, correct=correct, errest=errest)
phiv!(w, t, Ks, k; kwargs...)
end
"""
phiv!(w,t,Ks,k[;cache,correct,errest]) -> w[,errest]
Expand Down
17 changes: 10 additions & 7 deletions src/krylov_phiv_adaptive.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,20 @@ function phiv_timestep!(u::AbstractVector{T}, t::tType, A, B::AbstractMatrix{T};
return u
end
function phiv_timestep!(U::AbstractMatrix{T}, ts::Vector{tType}, A, B::AbstractMatrix{T}; tau::Real=0.0,
m::Int=min(10, size(A, 1)), tol::Real=1e-7, opnorm=LinearAlgebra.opnorm, iop::Int=0,
m::Int=min(10, size(A, 1)), tol::Real=1e-7, opnorm=LinearAlgebra.opnorm(A,Inf), iop::Int=0,
correct::Bool=false, caches=nothing, adaptive=false, delta::Real=1.2,
ishermitian::Bool=LinearAlgebra.ishermitian(A),
gamma::Real=0.8, NA::Int=0, verbose=false) where {T <: Number, tType <: Real}
# Choose initial timestep
abstol = tol * opnorm(A, Inf)
if opnorm isa Function
opnorm = opnorm(A,Inf) # backward compatibility
end
abstol = tol * opnorm
verbose && println("Absolute tolerance: $abstol")
if iszero(tau)
Anorm = opnorm(A, Inf)
b0norm = norm(@view(B[:, 1]), Inf)
tau = 10/Anorm * (abstol * ((m+1)/ℯ)^(m+1) * sqrt(2*pi*(m+1)) /
(4*Anorm*b0norm))^(1/m)
tau = 10/opnorm * (abstol * ((m+1)/ℯ)^(m+1) * sqrt(2*pi*(m+1)) /
(4*opnorm*b0norm))^(1/m)
verbose && println("Initial time step unspecified, chosen to be $tau")
end
# Initialization
Expand All @@ -135,7 +138,7 @@ function phiv_timestep!(U::AbstractMatrix{T}, ts::Vector{tType}, A, B::AbstractM
copyto!(u, @view(B[:, 1])) # u(0) = b0
coeffs = ones(tType, p);
if adaptive # initialization step for the adaptive scheme
if ishermitian(A)
if ishermitian
iop = 2 # does not have an effect on arnoldi!, just for flops estimation
end
if iszero(NA)
Expand Down Expand Up @@ -176,7 +179,7 @@ function phiv_timestep!(U::AbstractMatrix{T}, ts::Vector{tType}, A, B::AbstractM
while omega > delta # inner loop of Algorithm 3
m_new, tau_new, q, kappa = _phiv_timestep_adapt(
m, tau, epsilon, m_old, tau_old, epsilon_old, q, kappa,
gamma, omega, maxtau, n, p, NA, iop, opnorm(getH(Ks), 1), verbose)
gamma, omega, maxtau, n, p, NA, iop, LinearAlgebra.opnorm(getH(Ks), 1), verbose)
m, m_old = m_new, m
tau, tau_old = tau_new, tau
# Compute ϕp(tau*A)wp using the new parameters
Expand Down
36 changes: 33 additions & 3 deletions src/krylov_expv.jl → src/krylov_phiv_error_estimate.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,45 @@
using Printf
# Alternative Krylov phiv methods using error estimates of Saad to automatically
# terminate Arnoldi/Lanczos iterations.
# Currently only expv for Lanczos is implemented.

########################################
# Cache types
abstract type SubspaceCache{T} end
abstract type HermitianSubspaceCache{T} <: SubspaceCache{T} end

include("stegr_cache.jl")
mutable struct StegrCache{T,R<:Real} <: HermitianSubspaceCache{T}
v::Vector{T} # Subspace-propagated vector
w::Vector{T}
sw::Stegr.StegrWork{R}
StegrCache(::Type{T}, n::Integer) where T = new{T,real(T)}(
Vector{T}(undef, n), Vector{T}(undef, n),
Stegr.StegrWork(real(T), BlasInt(n)))
end

"""
expT!(α, β, t, cache)
Calculate the subspace exponential `exp(t*T)` for a tridiagonal
subspace matrix `T` with `α` on the diagonal and `β` on the
super-/subdiagonal, diagonalizing via `stegr!`.
"""
function expT!::AbstractVector{R}, β::AbstractVector{R}, t::Number,
cache::StegrCache{T,R}) where {T,R<:Real}
stegr!(α, β, cache.sw)
sel = 1:length(α)
@inbounds for i = sel
cache.w[i] = exp(t*cache.sw.w[i])*cache.sw.Z[1,i]
end
mul!(@view(cache.v[sel]), @view(cache.sw.Z[sel,sel]), @view(cache.w[sel]))
end

get_subspace_cache(Ks::KrylovSubspace{B,T,U}) where {B,T,U<:Complex} =
error("Subspace exponential caches not yet available for non-Hermitian matrices.")
get_subspace_cache(Ks::KrylovSubspace{B,T,U}) where {B,T,U<:Real} =
StegrCache(Ks)
StegrCache(T, Ks.maxiter)

########################################
# Phiv with error estimate as termination condition
"""
expv!(w, t, A, b, Ks, cache)
Expand Down
Loading

0 comments on commit 9304f45

Please sign in to comment.