-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathcheby.jl
280 lines (226 loc) · 6.49 KB
/
cheby.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
module Cheby
export cheby_coeffs, cheby_coeffs!, ChebyWrk, cheby!, cheby
using SpecialFunctions
using LinearAlgebra
using TimerOutputs: @timeit_debug, TimerOutput
"""Calculate Chebychev coefficients.
```julia
a::Vector{Float64} = cheby_coeffs(Δ, dt; limit=1e-12)
```
return an array of coefficiencts larger than `limit`.
# Arguments
* `Δ`: the spectral radius of the underlying operator
* `dt`: the time step
See also [`cheby_coeffs!`](@ref) for an in-place version.
"""
function cheby_coeffs(Δ, dt; limit=1e-12)
α = abs(0.5 * Δ * dt)
coeffs = Float64[]
a = besselj(0, α)
append!(coeffs, a)
ϵ = abs(a)
i = 1
while ϵ > limit
a = 2 * besselj(i, α)
append!(coeffs, a)
ϵ = abs(a)
i += 1
end
return coeffs
end
"""Calculate Chebychev coefficients in-place.
```julia
n::Int = cheby_coeffs!(coeffs, Δ, dt, limit=1e-12)
```
overwrites the first `n` values in `coeffs` with new coefficients larger than
`limit` for the given new spectral radius `Δ` and time step `dt`. The `coeffs`
array will be resized if necessary, and may have a length > `n` on exit.
See also [`cheby_coeffs`](@ref) for an non-in-place version.
"""
function cheby_coeffs!(coeffs, Δ, dt, limit=1e-12)
α = abs(0.5 * Δ * dt)
a = besselj(0, α)
coeffs[1] = a
N = length(coeffs)
ϵ = abs(a)
n = 1
while ϵ > limit
n += 1
a = 2 * besselj(n - 1, α)
coeffs[n] = a
ϵ = abs(a)
if (n >= N)
N *= 2
resize!(coeffs, N)
end
end
return n
end
"""
Workspace for the Chebychev propagation routine.
```julia
ChebyWrk(Ψ, Δ, E_min, dt; limit=1e-12)
```
initializes the workspace for the propagation of a state similar to `Ψ` under a
Hamiltonian with eigenvalues between `E_min` and `E_min + Δ`,
and a time step `dt`. Chebychev coefficients smaller than the given `limit` are
discarded.
"""
mutable struct ChebyWrk{ST,CFS,FT<:AbstractFloat}
v0::ST
v1::ST
v2::ST
coeffs::CFS
n_coeffs::Int64
Δ::FT
E_min::FT
dt::FT
limit::FT
timing_data::TimerOutput
function ChebyWrk(
Ψ::ST,
Δ::FT,
E_min::FT,
dt::FT;
limit::FT=1e-12,
_timing_data=TimerOutput()
) where {ST,FT}
v0::ST = similar(Ψ)
v1::ST = similar(Ψ)
v2::ST = similar(Ψ)
coeffs = cheby_coeffs(Δ, dt; limit=limit)
n_coeffs = length(coeffs)
new{ST,typeof(coeffs),FT}(
v0,
v1,
v2,
coeffs,
n_coeffs,
Δ,
E_min,
dt,
limit,
_timing_data
)
end
end
"""Evaluate `Ψ = exp(-i H dt) Ψ` in-place.
```julia
cheby!(Ψ, H, dt, wrk; E_min=nothing, check_normalization=false)
```
# Arguments
* `Ψ`: on input, initial vector. Will be overwritten with result.
* `H`: Hermitian operator
* `dt`: time step
* `wrk`: internal workspace
* `E_min`: minimum eigenvalue of H, to be used instead of the `E_min` from the
initialization of `wrk`. The same `wrk` may be used for different values
`E_min`, as long as the spectra radius `Δ` and the time step `dt` are the
same as those used for the initialization of `wrk`.
* `check_normalizataion`: perform checks that the H does not exceed the
spectral radius for which the the workspace was initialized.
The routine will not allocate any internal storage. This implementation
requires `copyto!` `lmul!`, and `axpy!` to be implemented for `Ψ`, and the
three-argument `mul!` for `Ψ` and `H`.
"""
function cheby!(Ψ, H, dt, wrk; kwargs...)
E_min = get(kwargs, :E_min, wrk.E_min)
check_normalization = get(kwargs, :check_normalization, false)
Δ = wrk.Δ
β::Float64 = (Δ / 2) + E_min # "normfactor"
@assert abs(dt) ≈ abs(wrk.dt) "wrk was initialized for dt=$(wrk.dt), not dt=$dt"
if dt > 0
c = -2im / Δ
else
c = 2im / Δ
end
a = wrk.coeffs
ϵ = wrk.limit
@assert length(a) > 1 "Need at least 2 Chebychev coefficients"
v0 = wrk.v0
v1 = wrk.v1
v2 = wrk.v2
# v0 ↔ Ψ; Ψ = a[1] * v0
copyto!(v0, Ψ)
lmul!(a[1], Ψ)
# v1 = -i * H_norm * v0 = c * (H * v0 - β * v0)
@timeit_debug wrk.timing_data "matrix-vector product" begin
mul!(v1, H, v0)
end
axpy!(-β, v0, v1)
lmul!(c, v1)
# Ψ += a[2] * v1
axpy!(a[2], v1, Ψ)
c *= 2
for i = 3:wrk.n_coeffs
# v2 = -2i * H_norm * v1 + v0 = c * (H * v1 - β * v1) + v0
@timeit_debug wrk.timing_data "matrix-vector product" begin
mul!(v2, H, v1)
end
axpy!(-β, v1, v2)
lmul!(c, v2)
if check_normalization
map_norm = abs(dot(v1, v2)) / (2 * norm(v1)^2)
@assert(
map_norm <= (1.0 + ϵ),
"Incorrect normalization (E_min=$(E_min), Δ=$(Δ))"
)
end
# v2 += v0
axpy!(true, v0, v2)
# Ψ += a[i] * v2
axpy!(a[i], v2, Ψ)
v0, v1, v2 = v1, v2, v0 # switch w/o copying
end
lmul!(exp(-im * β * dt), Ψ)
end
"""Evaluate `Ψ = exp(i- H dt) Ψ`.
```julia
Ψ_out = cheby(Ψ, H, dt, wrk; E_min=nothing, check_normalization=false)
```
acts like [`cheby!`](@ref) but does not modify `Ψ` in-place.
"""
function cheby(Ψ, H, dt, wrk; kwargs...)
E_min = get(kwargs, :E_min, wrk.E_min)
check_normalization = get(kwargs, :check_normalization, false)
Δ = wrk.Δ
β::Float64 = (Δ / 2) + E_min # "normfactor"
@assert dt ≈ wrk.dt "wrk was initialized for dt=$(wrk.dt), not dt=$dt"
if dt > 0
c = -2im / Δ
else
c = 2im / Δ
end
a = wrk.coeffs
ϵ = wrk.limit
@assert length(a) > 1 "Need at least 2 Chebychev coefficients"
v0 = wrk.v0
v1 = wrk.v1
v2 = wrk.v2
v0 = Ψ
Ψ = a[1] * v0
@timeit_debug wrk.timing_data "matrix-vector product" begin
v1 = c * (H * v0 - β * v0)
end
Ψ += a[2] * v1
c *= 2
for i = 3:wrk.n_coeffs
@timeit_debug wrk.timing_data "matrix-vector product" begin
v2 = H * v1
end
v2 += -v1 * β
v2 = c * v2
if check_normalization
map_norm = abs(dot(v1, v2)) / (2 * norm(v1)^2)
@assert(
map_norm <= (1.0 + ϵ),
"Incorrect normalization (E_min=$(E_min), Δ=$(Δ))"
)
end
v2 += v0
Ψ += a[i] * v2
v0, v1, v2 = v1, v2, v0 # switch w/o copying
end
return exp(-im * β * dt) * Ψ
end
end