Skip to content

Commit 10168c7

Browse files
committed
Merge branch 'main' into julia-1.12
2 parents 53131db + d44d251 commit 10168c7

File tree

21 files changed

+612
-108
lines changed

21 files changed

+612
-108
lines changed

.github/workflows/Benchmarks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
- uses: actions/checkout@v5
3838
- uses: julia-actions/setup-julia@v2
3939
with:
40-
version: '1'
40+
version: '1.11'
4141
arch: x64
4242
- uses: julia-actions/cache@v2
4343
- name: Run benchmark

.github/workflows/CleanPreviewDoc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: Checkout gh-pages branch
18-
uses: actions/checkout@v4
18+
uses: actions/checkout@v5
1919
with:
2020
ref: gh-pages
2121
- name: Delete preview(s) and reset history

.github/workflows/SpellCheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ jobs:
1010
- name: Checkout Actions Repository
1111
uses: actions/checkout@v5
1212
- name: Check spelling
13-
uses: crate-ci/typos@v1.37.2
13+
uses: crate-ci/typos@v1.38.1

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main)
99

10+
- Introduce new methods of `sesolve_map` and `mesolve_map` for advanced usage. Users can now customize their own `iter`ator structure, `prob_func` and `output_func`. ([#565])
11+
12+
## [v0.37.0]
13+
Release date: 2025-10-12
14+
1015
- Fix `cite()` bibtex output. ([#552])
16+
- Implement `sesolve_map` and `mesolve_map` for solving multiple initial states and parameter sets in parallel. ([#554])
1117
- Add `qeye_like` and `qzero_like`, which are synonyms of `one` and `zero`. ([#555])
1218
- Add steadystate and DSF benchmarks. The `SteadyStateODESOlver` tolerances are lowered to `terminate_reltol=1e-4` and `terminate_abstol=1e-6` to improve speed at the cost of accuracy. ([#557])
1319

@@ -234,6 +240,7 @@ Release date: 2024-11-13
234240
[v0.34.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.34.1
235241
[v0.35.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.35.0
236242
[v0.36.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.36.0
243+
[v0.37.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.37.0
237244
[#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86
238245
[#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139
239246
[#271]: https://github.com/qutip/QuantumToolbox.jl/issues/271
@@ -328,5 +335,7 @@ Release date: 2024-11-13
328335
[#544]: https://github.com/qutip/QuantumToolbox.jl/issues/544
329336
[#546]: https://github.com/qutip/QuantumToolbox.jl/issues/546
330337
[#552]: https://github.com/qutip/QuantumToolbox.jl/issues/552
338+
[#554]: https://github.com/qutip/QuantumToolbox.jl/issues/554
331339
[#555]: https://github.com/qutip/QuantumToolbox.jl/issues/555
332340
[#557]: https://github.com/qutip/QuantumToolbox.jl/issues/557
341+
[#565]: https://github.com/qutip/QuantumToolbox.jl/issues/565

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "QuantumToolbox"
22
uuid = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab"
33
authors = ["Alberto Mercurio", "Yi-Te Huang"]
4-
version = "0.36.0"
4+
version = "0.37.0"
55

66
[deps]
77
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"

benchmarks/Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
[deps]
22
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
3+
Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
4+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
35
QuantumToolbox = "6c2fb7c5-b903-41d2-bc5e-5a7c320b9fab"
46
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
7+
SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1"
8+
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"

benchmarks/autodiff.jl

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
function benchmark_autodiff!(SUITE)
2+
# Use harmonic oscillator system for both sesolve and mesolve
3+
N = 20
4+
a = destroy(N)
5+
ψ0 = fock(N, 0)
6+
tlist = range(0, 40, 100)
7+
8+
# ---- SESOLVE ----
9+
# For direct Forward differentiation
10+
function my_f_sesolve_direct(p)
11+
H = p[1] * a' * a + p[2] * (a + a')
12+
sol = sesolve(H, ψ0, tlist, progress_bar = Val(false))
13+
return real(expect(a' * a, sol.states[end]))
14+
end
15+
16+
# For SciMLSensitivity.jl (reverse mode with Zygote and Enzyme)
17+
coef_Δ(p, t) = p[1]
18+
coef_F(p, t) = p[2]
19+
H_evo = QobjEvo(a' * a, coef_Δ) + QobjEvo(a + a', coef_F)
20+
21+
function my_f_sesolve(p)
22+
sol = sesolve(
23+
H_evo,
24+
ψ0,
25+
tlist,
26+
progress_bar = Val(false),
27+
params = p,
28+
sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()),
29+
)
30+
return real(expect(a' * a, sol.states[end]))
31+
end
32+
33+
# ---- MESOLVE ----
34+
# For direct Forward differentiation
35+
function my_f_mesolve_direct(p)
36+
H = p[1] * a' * a + p[2] * (a + a')
37+
c_ops = [sqrt(p[3]) * a]
38+
sol = mesolve(H, ψ0, tlist, c_ops, progress_bar = Val(false))
39+
return real(expect(a' * a, sol.states[end]))
40+
end
41+
42+
# For SciMLSensitivity.jl (reverse mode with Zygote and Enzyme)
43+
coef_γ(p, t) = sqrt(p[3])
44+
c_ops = [QobjEvo(a, coef_γ)]
45+
L = liouvillian(H_evo, c_ops)
46+
47+
function my_f_mesolve(p)
48+
sol = mesolve(
49+
L,
50+
ψ0,
51+
tlist,
52+
progress_bar = Val(false),
53+
params = p,
54+
sensealg = BacksolveAdjoint(autojacvec = EnzymeVJP()),
55+
)
56+
return real(expect(a' * a, sol.states[end]))
57+
end
58+
59+
# Parameters for benchmarks
60+
params_sesolve = [1.0, 1.0]
61+
params_mesolve = [1.0, 1.0, 1.0]
62+
63+
# Benchmark sesolve - Forward
64+
SUITE["Autodiff"]["sesolve"]["Forward"] = @benchmarkable ForwardDiff.gradient($my_f_sesolve_direct, $params_sesolve)
65+
66+
# Benchmark sesolve - Reverse (Zygote)
67+
SUITE["Autodiff"]["sesolve"]["Reverse (Zygote)"] = @benchmarkable Zygote.gradient($my_f_sesolve, $params_sesolve)
68+
69+
# Benchmark sesolve - Reverse (Enzyme)
70+
SUITE["Autodiff"]["sesolve"]["Reverse (Enzyme)"] = @benchmarkable Enzyme.autodiff(
71+
Enzyme.set_runtime_activity(Enzyme.Reverse),
72+
Const($my_f_sesolve),
73+
Active,
74+
Duplicated($params_sesolve, dparams_sesolve),
75+
) setup=(dparams_sesolve = Enzyme.make_zero($params_sesolve))
76+
77+
# Benchmark mesolve - Forward
78+
SUITE["Autodiff"]["mesolve"]["Forward"] = @benchmarkable ForwardDiff.gradient($my_f_mesolve_direct, $params_mesolve)
79+
80+
# Benchmark mesolve - Reverse (Zygote)
81+
SUITE["Autodiff"]["mesolve"]["Reverse (Zygote)"] = @benchmarkable Zygote.gradient($my_f_mesolve, $params_mesolve)
82+
83+
# Benchmark mesolve - Reverse (Enzyme)
84+
SUITE["Autodiff"]["mesolve"]["Reverse (Enzyme)"] = @benchmarkable Enzyme.autodiff(
85+
Enzyme.set_runtime_activity(Enzyme.Reverse),
86+
Const($my_f_mesolve),
87+
Active,
88+
Duplicated($params_mesolve, dparams_mesolve),
89+
) setup=(dparams_mesolve = Enzyme.make_zero($params_mesolve))
90+
91+
return nothing
92+
end

benchmarks/runbenchmarks.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ using LinearAlgebra
33
using SparseArrays
44
using QuantumToolbox
55
using SciMLBase: EnsembleSerial, EnsembleThreads
6+
using ForwardDiff
7+
using Zygote
8+
using Enzyme: Enzyme, Const, Active, Duplicated
9+
using SciMLSensitivity: BacksolveAdjoint, EnzymeVJP
610

711
BLAS.set_num_threads(1)
812

@@ -14,13 +18,15 @@ include("dynamical_shifted_fock.jl")
1418
include("eigenvalues.jl")
1519
include("steadystate.jl")
1620
include("timeevolution.jl")
21+
include("autodiff.jl")
1722

1823
benchmark_correlations_and_spectrum!(SUITE)
1924
benchmark_dfd!(SUITE)
2025
benchmark_dsf!(SUITE)
2126
benchmark_eigenvalues!(SUITE)
2227
benchmark_steadystate!(SUITE)
2328
benchmark_timeevolution!(SUITE)
29+
benchmark_autodiff!(SUITE)
2430

2531
BenchmarkTools.tune!(SUITE)
2632
results = BenchmarkTools.run(SUITE, verbose = true)

docs/src/resources/api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ mesolve
215215
mcsolve
216216
ssesolve
217217
smesolve
218+
sesolve_map
219+
mesolve_map
218220
dfd_mesolve
219221
liouvillian
220222
liouvillian_generalized

docs/src/users_guide/cluster.md

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -193,72 +193,44 @@ flush(stdout)
193193
end
194194

195195
@everywhere begin
196-
const Nc = 20
197-
const ωc = 1.0
198-
const g = 0.05
199-
const γ = 0.01
200-
const F = 0.01
196+
Nc = 20
197+
ωc = 1.0
198+
g = 0.05
199+
γ = 0.01
200+
F = 0.01
201201

202-
const a = tensor(destroy(Nc), qeye(2))
202+
a = tensor(destroy(Nc), qeye(2))
203203

204-
const σm = tensor(qeye(Nc), sigmam())
205-
const σp = tensor(qeye(Nc), sigmap())
204+
σm = tensor(qeye(Nc), sigmam())
205+
σp = tensor(qeye(Nc), sigmap())
206+
σz = tensor(qeye(Nc), sigmaz())
206207

207-
H(ωq) = ωc * a' * a + ωq * tensor(num(Nc), qeye(2)) + g * (a' * σm + a * σp)
208+
ωq_fun(p, t) = p[1] # ωq
209+
drive_fun(p, t) = p[3] * cos(p[2] * t) # F cos(ωd t)
208210

209-
coef(p, t) = p.F * cos(p.ωd * t) # coefficient for the time-dependent term
211+
H = ωc * a' * a + QobjEvo(σz / 2, ωq_fun) + g * (a' * σm + a * σp) + QobjEvo(a + a', drive_fun)
210212

211-
const c_ops = [sqrt(γ) * a, sqrt(γ) * σm]
212-
const e_ops = [a' * a]
213+
c_ops = [sqrt(γ) * a, sqrt(γ) * σm]
214+
e_ops = [a' * a]
213215
end
214216

215-
# Define the ODE problem and the EnsembleProblem generation function
217+
ωq_list = range(ωc - 3*g, ωc + 3*g, 100)
218+
ωd_list = range(ωc - 3*g, ωc + 3*g, 100)
219+
F_list = [F]
216220

217-
@everywhere begin
218-
ωq_list = range(ωc - 3*g, ωc + 3*g, 100)
219-
ωd_list = range(ωc - 3*g, ωc + 3*g, 100)
220-
221-
const iter = collect(Iterators.product(ωq_list, ωd_list))
222-
223-
function my_prob_func(prob, i, repeat, channel)
224-
ωq, ωd = iter[i]
225-
H_i = H(ωq)
226-
H_d_i = H_i + QobjEvo(a + a', coef) # Hamiltonian with a driving term
227-
228-
L = liouvillian(H_d_i, c_ops).data # Make the Liouvillian
229-
230-
put!(channel, true) # Update the progress bar channel
231-
232-
remake(prob, f=L, p=(F = F, ωd = ωd))
233-
end
234-
end
235-
236-
ωq, ωd = iter[1]
237-
H0 = H(ωq) + QobjEvo(a + a', coef)
238-
ψ0 = tensor(fock(Nc, 0), basis(2, 1)) # Ground State
221+
ψ_list = [
222+
tensor(fock(Nc, 0), basis(2, 0)),
223+
tensor(fock(Nc, 0), basis(2, 1))
224+
]
239225
tlist = range(0, 20 / γ, 1000)
240226

241-
prob = mesolveProblem(H0, ψ0, tlist, c_ops, e_ops=e_ops, progress_bar=Val(false), params=(F = F, ωd = ωd))
242-
243-
### Just to print the progress bar
244-
progr = ProgressBar(length(iter))
245-
progr_channel::RemoteChannel{Channel{Bool}} = RemoteChannel(() -> Channel{Bool}(1))
246-
###
247-
ens_prob = EnsembleProblem(prob.prob, prob_func=(prob, i, repeat) -> my_prob_func(prob, i, repeat, progr_channel))
248-
249-
250-
@sync begin
251-
@async while take!(progr_channel)
252-
next!(progr)
253-
end
254-
255-
@async begin
256-
sol = solve(ens_prob, Tsit5(), EnsembleSplitThreads(), trajectories = length(iter))
257-
put!(progr_channel, false)
258-
end
259-
end
227+
sol = mesolve_map(H, ψ_list, tlist, c_ops; e_ops = e_ops, params = (ωq_list, ωd_list, F_list), ensemblealg = EnsembleSplitThreads())
260228
```
261229

262-
We are using the [`mesolveProblem`](@ref) function to define the master equation problem. We added some code to manage the progress bar, which is updated through a `RemoteChannel`. The `prob_func` argument of the `EnsembleProblem` function is used to define the function that generates the problem for each iteration. The `iter` variable contains the product of the `ωq_list` and `ωd_list` lists, which are used to sweep over the parameters. The `sol = solve(ens_prob, Tsit5(), EnsembleDistributed(), trajectories=length(iter))` command is used to solve the problem with the distributed ensemble method. The output of the script will be printed in the `output.out` file, which contains an output similar to the previous example.
230+
Notice that we are using the [`mesolve_map`](@ref) function, which internally uses the `SciMLBase.EnsembleProblem` function to parallelize the computation of [`mesolveProblem`](@ref). The result is an `Array` of [`TimeEvolutionSol`](@ref), where each element corresponds to a specific combination of initial states and parameters. One can access the solution for a specific combination of initial states and parameters using indexing. For example,
263231

232+
```julia
233+
sol[i,j,k,l].expect
234+
```
264235

236+
this will give the expectation values for the case of `ψ_list[i]`, `ωq_list[j]`, `ωd_list[k]`, and `F_list[l]`.

0 commit comments

Comments
 (0)