Skip to content

Commit 30910cb

Browse files
committed
Add ExponentialUtilities GRAPE support + tests
1 parent 4ccdd82 commit 30910cb

14 files changed

Lines changed: 550 additions & 76 deletions

Project.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,25 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1515
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
1616

1717
[weakdeps]
18+
ExponentialUtilities = "d4d017d3-3776-5f7e-afef-a10c40355c18"
1819
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
20+
QuantumGradientGenerators = "a563f35e-61db-434d-8c01-8b9e3ccdfd85"
1921
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
2022
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2123

2224
[extensions]
25+
QuantumPropagatorsExponentialUtilitiesExt = "ExponentialUtilities"
2326
QuantumPropagatorsODEExt = "OrdinaryDiffEq"
27+
QuantumPropagatorsQuantumGradientGeneratorsExt = "QuantumGradientGenerators"
2428
QuantumPropagatorsRecursiveArrayToolsExt = "RecursiveArrayTools"
2529
QuantumPropagatorsStaticArraysExt = "StaticArrays"
2630

2731
[compat]
32+
ExponentialUtilities = "1.11"
2833
OffsetArrays = "1"
2934
OrdinaryDiffEq = "6.59"
3035
ProgressMeter = "1"
36+
QuantumGradientGenerators = "0.1"
3137
Random = "1"
3238
RecursiveArrayTools = "3.12"
3339
SpecialFunctions = "1, 2"

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ DocInventories = "43dc2714-ed3b-44b5-b226-857eda1aa7de"
55
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
66
DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"
77
DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656"
8+
ExponentialUtilities = "d4d017d3-3776-5f7e-afef-a10c40355c18"
89
LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589"
910
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
1011
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ using Pkg
33
using Documenter
44
using QuantumPropagators
55
using QuantumPropagators: AbstractPropagator, set_t!, set_state!
6+
import ExponentialUtilities # ensure ExponentialUtilities extension is loaded
67
import OrdinaryDiffEq # ensure ODE extension is loaded
78
using DocumenterCitations
89
using DocumenterInterLinks

docs/src/methods.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ As discussed in the [Overview](@ref overview_approaches), time propagation can b
1515
We consider this especially in the piecewise-constant case (`pwc=true` in [`propagate`](@ref)/[`init_prop`](@ref)), which is required for the traditional optimization methods [GRAPE](https://juliaquantumcontrol.github.io/GRAPE.jl/stable/) and [Krotov](https://juliaquantumcontrol.github.io/Krotov.jl/stable/). In these propagations, the time-dependent generator ``\op{H}(t)`` is [evaluated](@ref QuantumPropagators.Controls.evaluate) to a constant operator ``\op{H}`` on each interval of the time grid. The analytical solution to the Schrödinger or Liouville equation is well known, and propagation step simply has to evaluate the application of the time evolution operator ``\op{U} = \exp[-i \op{H} dt]`` to the state ``|Ψ⟩``. The following methods are built in to `QuantumPropagators`:
1616

1717
* [`ExpProp`](@ref method_expprop) – constructs ``\op{U}`` explicitly and then applies it to ``|Ψ⟩``
18+
* [`ExponentialUtilities`](@ref method_exponentialutilities) – applies ``\exp(\op{H} dt) |Ψ⟩`` using Krylov methods without explicitly forming ``\op{U}``
1819
* [`Cheby`](@ref method_cheby) — expansion of ``\op{U} |Ψ⟩`` into Chebychev polynomials, valid if ``\op{H}`` has real eigenvalues
1920
* [`Newton`](@ref method_newton) – expansion of ``\op{U} |Ψ⟩`` into Newton polynomials, valid if ``\op{H}`` has complex eigenvalues (non-Hermitian Hamiltonian, Liouvillian)
2021

@@ -65,6 +66,52 @@ init_prop(state, generator, tlist, method::Val{:ExpProp}; kwargs...)
6566
* Comparing against another propagator
6667

6768

69+
## [`ExponentialUtilities`](@id method_exponentialutilities)
70+
71+
The method requires that the [ExponentialUtilities.jl](https://docs.sciml.ai/ExponentialUtilities/stable/)
72+
package is loaded
73+
74+
```
75+
using ExponentialUtilities
76+
```
77+
78+
and then passed as `method=ExponentialUtilities` (or `method=:expv`) to
79+
[`propagate`](@ref) or [`init_prop`](@ref):
80+
81+
```@docs
82+
init_prop(state, generator, tlist, method::Val{:ExponentialUtilities}; kwargs...)
83+
```
84+
85+
This method evaluates ``\exp(-i \op{H} dt) |Ψ⟩`` via a Krylov expv algorithm
86+
without explicitly forming the matrix exponential. It is therefore often a
87+
good fit for larger systems or matrix-free operators where direct matrix
88+
exponentiation is too costly.
89+
90+
**Advantages**
91+
92+
* Avoids explicit construction of ``\op{U}``
93+
* Works with matrix-free operators that support `mul!`
94+
* Good scaling for large sparse systems
95+
96+
**Disadvantages**
97+
98+
* Requires ExponentialUtilities.jl
99+
* Performance depends on Krylov parameters and operator structure
100+
101+
**When to use**
102+
103+
* Large, sparse, or matrix-free generators
104+
* Piecewise-constant GRAPE-style workflows that need expv
105+
106+
**GRAPE note**
107+
108+
* When using `method=:expv` with GRAPE, set `gradient_method=:taylor`.
109+
The default `:gradgen` path uses `GradVector`/`GradgenOperator`, which is
110+
not currently supported by the ExponentialUtilities Krylov backend.
111+
* For non-Hermitian generators (e.g., Liouvillians), pass
112+
`prop_expv_kwargs=(; ishermitian=false)` to avoid Hermitian-only paths.
113+
114+
68115
## [`Cheby`](@id method_cheby)
69116

70117
The method should be loaded with
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
module QuantumPropagatorsExponentialUtilitiesExt
2+
3+
using LinearAlgebra
4+
using TimerOutputs: @timeit_debug, TimerOutput
5+
using ExponentialUtilities
6+
7+
using QuantumPropagators:
8+
QuantumPropagators,
9+
PWCPropagator,
10+
_pwc_get_max_genop,
11+
_pwc_get_genop,
12+
_pwc_set_genop!,
13+
_pwc_set_t!,
14+
_pwc_advance_time!,
15+
_pwc_process_parameters
16+
using QuantumPropagators.Controls: get_controls
17+
using QuantumPropagators.Interfaces: supports_inplace
18+
using QuantumPropagators.Generators: ScaledOperator
19+
import QuantumPropagators: init_prop, prop_step!, set_t!
20+
21+
struct _SizedOp{A}
22+
A::A
23+
end
24+
25+
Base.size(op::_SizedOp) = size(op.A)
26+
Base.size(op::_SizedOp, dim::Integer) = size(op.A)[dim]
27+
Base.eltype(op::_SizedOp) = eltype(op.A)
28+
LinearAlgebra.ishermitian(op::_SizedOp) = LinearAlgebra.ishermitian(op.A)
29+
LinearAlgebra.mul!(y, op::_SizedOp, x) = mul!(y, op.A, x)
30+
31+
_ensure_size_dim(A) =
32+
hasmethod(size, Tuple{typeof(A), Int}) ? A :
33+
(hasmethod(size, Tuple{typeof(A)}) ? _SizedOp(A) : A)
34+
35+
36+
"""Propagator for Krylov expv propagation via ExponentialUtilities (`method=ExponentialUtilities`).
37+
38+
This is a [`PWCPropagator`](@ref).
39+
"""
40+
mutable struct ExpvPropagator{GT,OT,ST} <: PWCPropagator
41+
const generator::GT
42+
state::ST
43+
t::Float64 # time at which current `state` is defined
44+
n::Int64 # index of next interval to propagate
45+
const tlist::Vector{Float64}
46+
parameters::AbstractDict
47+
controls
48+
genop::OT
49+
backward::Bool
50+
inplace::Bool
51+
convert_state::Type
52+
convert_operator::Type
53+
expv_kwargs::NamedTuple
54+
const timing_data::TimerOutput
55+
end
56+
57+
58+
set_t!(propagator::ExpvPropagator, t) = _pwc_set_t!(propagator, t)
59+
60+
61+
_expv_convert_state(state) = typeof(state)
62+
_expv_convert_operator(::Any) = Any
63+
_expv_convert_operator(::QuantumPropagators.Generator) = Matrix{ComplexF64}
64+
_expv_convert_operator(::QuantumPropagators.Operator) = Matrix{ComplexF64}
65+
_expv_convert_operator(::QuantumPropagators.ScaledOperator) = Matrix{ComplexF64}
66+
67+
68+
"""
69+
```julia
70+
using ExponentialUtilities
71+
72+
expv_propagator = init_prop(
73+
state,
74+
generator,
75+
tlist;
76+
method=ExponentialUtilities,
77+
inplace=QuantumPropagators.Interfaces.supports_inplace(state),
78+
backward=false,
79+
verbose=false,
80+
parameters=nothing,
81+
expv_kwargs=(;),
82+
convert_state=_expv_convert_state(state),
83+
convert_operator=_expv_convert_operator(generator),
84+
_...
85+
)
86+
```
87+
88+
initializes an [`ExpvPropagator`](@ref).
89+
90+
# Method-specific keyword arguments
91+
92+
* `expv_kwargs`: NamedTuple of keyword arguments forwarded to
93+
`ExponentialUtilities.expv`.
94+
* `convert_state`: Type to which to temporarily convert the state before
95+
calling `expv`.
96+
* `convert_operator`: Type to which to convert the operator before calling
97+
`expv`.
98+
"""
99+
function init_prop(
100+
state,
101+
generator,
102+
tlist,
103+
method::Val{:ExponentialUtilities};
104+
inplace = supports_inplace(state),
105+
backward = false,
106+
verbose = false,
107+
parameters = nothing,
108+
expv_kwargs = (;),
109+
convert_state = _expv_convert_state(state),
110+
convert_operator = _expv_convert_operator(generator),
111+
_...
112+
)
113+
tlist = convert(Vector{Float64}, tlist)
114+
controls = get_controls(generator)
115+
G = _pwc_get_max_genop(generator, controls, tlist)
116+
117+
parameters = _pwc_process_parameters(parameters, controls, tlist)
118+
timing_data = TimerOutput()
119+
120+
n = 1
121+
t = tlist[1]
122+
if backward
123+
n = length(tlist) - 1
124+
t = float(tlist[n + 1])
125+
end
126+
GT = typeof(generator)
127+
OT = typeof(G)
128+
ST = typeof(state)
129+
return ExpvPropagator{GT,OT,ST}(
130+
generator,
131+
inplace ? copy(state) : state,
132+
t,
133+
n,
134+
tlist,
135+
parameters,
136+
controls,
137+
G,
138+
backward,
139+
inplace,
140+
convert_state,
141+
convert_operator,
142+
expv_kwargs,
143+
timing_data,
144+
)
145+
end
146+
147+
148+
# Aliases
149+
init_prop(state, generator, tlist, method::Val{:expv}; kwargs...) =
150+
init_prop(state, generator, tlist, Val(:ExponentialUtilities); kwargs...)
151+
152+
153+
function prop_step!(propagator::ExpvPropagator)
154+
@timeit_debug propagator.timing_data "prop_step!" begin
155+
if nameof(typeof(propagator.state)) == :GradVector &&
156+
nameof(parentmodule(typeof(propagator.state))) == :QuantumGradientGenerators
157+
throw(ArgumentError(
158+
"ExponentialUtilities propagation does not support GRAPE `gradient_method=:gradgen`. " *
159+
"Use `gradient_method=:taylor` instead."
160+
))
161+
end
162+
H = propagator.genop
163+
n = propagator.n
164+
tlist = getfield(propagator, :tlist)
165+
(0 < n < length(tlist)) || return nothing
166+
dt = tlist[n + 1] - tlist[n]
167+
if propagator.backward
168+
dt = -dt
169+
end
170+
dt_expv = complex(dt)
171+
172+
Ψ = convert(propagator.convert_state, propagator.state)
173+
if propagator.inplace
174+
if supports_inplace(propagator.genop)
175+
_pwc_set_genop!(propagator, n)
176+
H = convert(propagator.convert_operator, propagator.genop)
177+
else
178+
H = convert(propagator.convert_operator, _pwc_get_genop(propagator, n))
179+
end
180+
H = _ensure_size_dim(H)
181+
H = ScaledOperator(-1im, H)
182+
@timeit_debug propagator.timing_data "expv" begin
183+
Ψ = ExponentialUtilities.expv(dt_expv, H, Ψ; propagator.expv_kwargs...)
184+
end
185+
copyto!(propagator.state, convert(typeof(propagator.state), Ψ))
186+
else
187+
H = convert(propagator.convert_operator, _pwc_get_genop(propagator, n))
188+
H = _ensure_size_dim(H)
189+
H = ScaledOperator(-1im, H)
190+
@timeit_debug propagator.timing_data "expv" begin
191+
Ψ = ExponentialUtilities.expv(dt_expv, H, Ψ; propagator.expv_kwargs...)
192+
end
193+
setfield!(propagator, :state, convert(typeof(propagator.state), Ψ))
194+
end
195+
196+
_pwc_advance_time!(propagator)
197+
return propagator.state
198+
end
199+
end
200+
201+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module QuantumPropagatorsQuantumGradientGeneratorsExt
2+
3+
using QuantumGradientGenerators: GradgenOperator, GradVector
4+
5+
Base.size(G::GradgenOperator, dim::Integer) = size(G.G, dim)
6+
Base.similar(::GradVector, ::Type{T}, dims::Tuple{Int,Int}) where {T} =
7+
Matrix{T}(undef, dims...)
8+
Base.similar(::GradVector, ::Type{T}, dims::Tuple{Int}) where {T} =
9+
Vector{T}(undef, dims[1])
10+
11+
end

src/generators.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ function Base.copyto!(tgt::Operator, src::Operator)
177177
end
178178

179179
Base.size(O::Operator) = size(O.ops[1])
180+
Base.size(O::Operator, dim::Integer) = size(O.ops[1], dim)
181+
Base.eltype(O::Operator) = eltype(O.ops[1])
180182

181183

182184
function LinearAlgebra.ishermitian(O::Operator{OT,CT}) where {OT,CT}
@@ -251,6 +253,8 @@ Base.Array(O::ScaledOperator) = Array{ComplexF64}(O)
251253

252254

253255
Base.size(O::ScaledOperator) = size(O.operator)
256+
Base.size(O::ScaledOperator, dim::Integer) = size(O.operator, dim)
257+
Base.eltype(O::ScaledOperator) = promote_type(eltype(O.operator), typeof(O.coeff))
254258

255259
LinearAlgebra.ishermitian(O::ScaledOperator) = (isreal(O.coeff) && ishermitian(O.operator))
256260

src/newton.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ mutable struct NewtonWrk{T}
3434
restarts::Int64
3535
m_max::Int64
3636
timing_data::TimerOutput
37-
function NewtonWrk(v0::T; m_max::Int64 = 10, _timing_data = TimerOutput()) where {T}
37+
function NewtonWrk(v0::T; m_max::Int64=10, _timing_data=TimerOutput()) where {T}
3838
if m_max <= 2
3939
error("Newton propagation requires m_max > 2")
4040
end
@@ -60,8 +60,8 @@ mutable struct NewtonWrk{T}
6060
end
6161

6262

63-
lbound(array::OffsetArray, dim = 1) = first(axes(array)[dim])
64-
ubound(array::OffsetArray, dim = 1) = last(axes(array)[dim])
63+
lbound(array::OffsetArray, dim=1) = first(axes(array)[dim])
64+
ubound(array::OffsetArray, dim=1) = last(axes(array)[dim])
6565

6666

6767
function leja_radius(z)
@@ -281,9 +281,9 @@ function newton!(Ψ, H, dt, wrk; kwargs...)
281281
wrk.v,
282282
H,
283283
_dt;
284-
extended = true,
285-
norm_min = norm_min,
286-
_timing_data = wrk.timing_data
284+
extended=true,
285+
norm_min=norm_min,
286+
_timing_data=wrk.timing_data
287287
)
288288
end
289289
if m == 1 && s == 0
@@ -294,7 +294,7 @@ function newton!(Ψ, H, dt, wrk; kwargs...)
294294
break
295295
end
296296
@timeit_debug wrk.timing_data "diagonalize_hessenberg_matrix" begin
297-
ritz = diagonalize_hessenberg_matrix(Hess, m, accumulate = true)
297+
ritz = diagonalize_hessenberg_matrix(Hess, m, accumulate=true)
298298
end
299299

300300
# In the first iteration, the radius will be determined

test/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ DocInventories = "43dc2714-ed3b-44b5-b226-857eda1aa7de"
88
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
99
DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"
1010
DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656"
11+
ExponentialUtilities = "d4d017d3-3776-5f7e-afef-a10c40355c18"
1112
GRAPE = "6b52fcaf-80fe-489a-93e9-9f92080510be"
1213
GRAPELinesearchAnalysis = "290eba36-e2d8-4488-81b6-f66cc44f2186"
1314
IOCapture = "b5f81e59-6552-4d32-b1f0-c071b021bf89"

0 commit comments

Comments
 (0)