Skip to content

Commit cfe7943

Browse files
authored
Make spectrum and correlation align with QuTiP (#330)
* remove `MySolver` and don't return frequency list from `spectrum` * remove `FFTCorrelation` and introduce `spectrum_correlation_fft` * format files * introduce `PseudoInverse<:SpectrumSolver` * update changelog * fix benchmark * introduce `correlation_3op_1t` * format files * fix missing links in docstring of `correlation` functions * update documentation page for two-time correlation function * update doc page of two-time correlation functions * make the output correlation `Matrix` align with `QuTiP` * minor changes * add deprecated methods to `deprecated.jl` * update changelog * improve runtests for `spectrum_correlation_fft`
1 parent 532a17e commit cfe7943

11 files changed

+718
-180
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## Unreleased
99

1010
- Change `SingleSiteOperator` with the more general `MultiSiteOperator`. ([#324])
11+
- Make `spectrum` and `correlation` functions align with `Python QuTiP`, introduce spectrum solver `PseudoInverse`, remove spectrum solver `FFTCorrelation`, and introduce `spectrum_correlation_fft`. ([#330])
1112

1213
## [v0.22.0] (2024-11-20)
1314

@@ -36,3 +37,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3637
[#315]: https://github.com/qutip/QuantumToolbox.jl/issues/315
3738
[#318]: https://github.com/qutip/QuantumToolbox.jl/issues/318
3839
[#324]: https://github.com/qutip/QuantumToolbox.jl/issues/324
40+
[#330]: https://github.com/qutip/QuantumToolbox.jl/issues/330

benchmarks/correlations_and_spectrum.jl

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
function _calculate_fft_spectrum(H, tlist, c_ops, A, B)
2+
corr = correlation_2op_1t(H, nothing, tlist, c_ops, A, B; progress_bar = Val(false))
3+
ωlist, spec = spectrum_correlation_fft(tlist, corr)
4+
return nothing
5+
end
6+
17
function benchmark_correlations_and_spectrum!(SUITE)
28
N = 15
39
ω = 1
@@ -9,11 +15,18 @@ function benchmark_correlations_and_spectrum!(SUITE)
915
c_ops = [sqrt* (nth + 1)) * a, sqrt* nth) * a']
1016

1117
ω_l = range(0, 3, length = 1000)
18+
t_l = range(0, 333 * π, length = 1000)
19+
20+
PI_solver = PseudoInverse()
1221

1322
SUITE["Correlations and Spectrum"]["FFT Correlation"] =
14-
@benchmarkable spectrum($H, $ω_l, $(a'), $a, $c_ops, solver = FFTCorrelation(), progress_bar = false)
23+
@benchmarkable _calculate_fft_spectrum($H, $t_l, $c_ops, $(a'), $a)
24+
25+
SUITE["Correlations and Spectrum"]["Spectrum"]["Exponential Series"] =
26+
@benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a)
1527

16-
SUITE["Correlations and Spectrum"]["Exponential Series"] = @benchmarkable spectrum($H, $ω_l, $(a'), $a, $c_ops)
28+
SUITE["Correlations and Spectrum"]["Spectrum"]["Pseudo Inverse"] =
29+
@benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a, solver = $PI_solver)
1730

1831
return nothing
1932
end

docs/make.jl

+1-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ const PAGES = [
5050
"Solving Problems with Time-dependent Hamiltonians" => "users_guide/time_evolution/time_dependent.md",
5151
],
5252
"Solving for Steady-State Solutions" => "users_guide/steadystate.md",
53-
"Symmetries" => [],
54-
"Two-time correlation functions" => [],
53+
"Two-time correlation functions" => "users_guide/two_time_corr_func.md",
5554
"Extensions" => [
5655
"users_guide/extensions/cuda.md",
5756
],

docs/src/resources/api.md

+4
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,13 @@ lr_mesolve
230230

231231
```@docs
232232
correlation_3op_2t
233+
correlation_3op_1t
233234
correlation_2op_2t
234235
correlation_2op_1t
236+
spectrum_correlation_fft
235237
spectrum
238+
ExponentialSeries
239+
PseudoInverse
236240
```
237241

238242
## [Metrics](@id doc-API:Metrics)

docs/src/resources/bibliography.bib

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
@book{Gardiner-Zoller2004,
2+
title = {Quantum Noise},
3+
ISBN = {9783540223016},
4+
url = {https://link.springer.com/book/9783540223016},
5+
publisher = {Springer Berlin, Heidelberg},
6+
author = {Gardiner, Crispin and Zoller, Peter},
7+
year = {2004},
8+
month = aug
9+
}
10+
111
@book{Nielsen-Chuang2011,
212
title = {Quantum Computation and Quantum Information: 10th Anniversary Edition},
313
ISBN = {9780511976667},
+266
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# [Two-time Correlation Functions](@id doc:Two-time-Correlation-Functions)
2+
3+
## Introduction
4+
5+
With the `QuantumToolbox.jl` time-evolution function [`mesolve`](@ref), a state vector ([`Ket`](@ref)) or density matrix ([`Operator`](@ref)) can be evolved from an initial state at ``t_0`` to an arbitrary time ``t``, namely
6+
7+
```math
8+
\hat{\rho}(t) = \mathcal{G}(t, t_0)\{\hat{\rho}(t_0)\},
9+
```
10+
where ``\mathcal{G}(t, t_0)\{\cdot\}`` is the propagator defined by the equation of motion. The resulting density matrix can then be used to evaluate the expectation values of arbitrary combinations of same-time operators.
11+
12+
To calculate two-time correlation functions on the form ``\left\langle \hat{A}(t+\tau) \hat{B}(t) \right\rangle``, we can use the quantum regression theorem [see, e.g., [Gardiner-Zoller2004](@cite)] to write
13+
14+
```math
15+
\left\langle \hat{A}(t+\tau) \hat{B}(t) \right\rangle = \textrm{Tr} \left[\hat{A} \mathcal{G}(t+\tau, t)\{\hat{B}\hat{\rho}(t)\} \right] = \textrm{Tr} \left[\hat{A} \mathcal{G}(t+\tau, t)\{\hat{B} \mathcal{G}(t, 0)\{\hat{\rho}(0)\}\} \right],
16+
```
17+
18+
We therefore first calculate ``\hat{\rho}(t) = \mathcal{G}(t, 0)\{\hat{\rho}(0)\}`` using [`mesolve`](@ref) with ``\hat{\rho}(0)`` as initial state, and then again use [`mesolve`](@ref) to calculate ``\mathcal{G}(t+\tau, t)\{\hat{B}\hat{\rho}(t)\}`` using ``\hat{B}\hat{\rho}(t)`` as initial state.
19+
20+
Note that if the initial state is the steady state, then ``\hat{\rho}(t) = \mathcal{G}(t, 0)\{\hat{\rho}_{\textrm{ss}}\} = \hat{\rho}_{\textrm{ss}}`` and
21+
22+
```math
23+
\left\langle \hat{A}(t+\tau) \hat{B}(t) \right\rangle = \textrm{Tr} \left[\hat{A} \mathcal{G}(t+\tau, t)\{\hat{B}\hat{\rho}_{\textrm{ss}}\} \right] = \textrm{Tr} \left[\hat{A} \mathcal{G}(\tau, 0)\{\hat{B} \hat{\rho}_{\textrm{ss}}\} \right] = \left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle,
24+
```
25+
which is independent of ``t``, so that we only have one time coordinate ``\tau``.
26+
27+
`QuantumToolbox.jl` provides a family of functions that assists in the process of calculating two-time correlation functions. The available functions and their usage is shown in the table below.
28+
29+
| **Function call** | **Correlation function** |
30+
|:------------------|:-------------------------|
31+
| [`correlation_2op_2t`](@ref) | ``\left\langle \hat{A}(t + \tau) \hat{B}(t) \right\rangle`` or ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \right\rangle`` |
32+
| [`correlation_2op_1t`](@ref) | ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle`` or ``\left\langle \hat{A}(0) \hat{B}(\tau) \right\rangle`` |
33+
| [`correlation_3op_1t`](@ref) | ``\left\langle \hat{A}(0) \hat{B}(\tau) \hat{C}(0) \right\rangle`` |
34+
| [`correlation_3op_2t`](@ref) | ``\left\langle \hat{A}(t) \hat{B}(t + \tau) \hat{C}(t) \right\rangle`` |
35+
36+
The most common used case is to calculate the two time correlation function ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle``, which can be done by [`correlation_2op_1t`](@ref).
37+
38+
```@setup correlation_and_spectrum
39+
using QuantumToolbox
40+
41+
using CairoMakie
42+
CairoMakie.enable_only_mime!(MIME"image/svg+xml"())
43+
```
44+
45+
## Steadystate correlation function
46+
47+
The following code demonstrates how to calculate the ``\langle \hat{x}(t) \hat{x}(0)\rangle`` correlation for a leaky cavity with three different relaxation rates ``\gamma``.
48+
49+
```@example correlation_and_spectrum
50+
tlist = LinRange(0, 10, 200)
51+
a = destroy(10)
52+
x = a' + a
53+
H = a' * a
54+
55+
# if the initial state is specified as `nothing`, the steady state will be calculated and used as the initial state.
56+
corr1 = correlation_2op_1t(H, nothing, tlist, [sqrt(0.5) * a], x, x)
57+
corr2 = correlation_2op_1t(H, nothing, tlist, [sqrt(1.0) * a], x, x)
58+
corr3 = correlation_2op_1t(H, nothing, tlist, [sqrt(2.0) * a], x, x)
59+
60+
# plot by CairoMakie.jl
61+
fig = Figure(size = (500, 350))
62+
ax = Axis(fig[1, 1], xlabel = L"Time $t$", ylabel = L"\langle \hat{x}(t) \hat{x}(0) \rangle")
63+
lines!(ax, tlist, real(corr1), label = L"\gamma = 0.5", linestyle = :solid)
64+
lines!(ax, tlist, real(corr2), label = L"\gamma = 1.0", linestyle = :dash)
65+
lines!(ax, tlist, real(corr3), label = L"\gamma = 2.0", linestyle = :dashdot)
66+
67+
axislegend(ax, position = :rt)
68+
69+
fig
70+
```
71+
72+
## Emission spectrum
73+
74+
Given a correlation function ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle``, we can define the corresponding power spectrum as
75+
76+
```math
77+
S(\omega) = \int_{-\infty}^\infty \left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle e^{-i \omega \tau} d \tau
78+
```
79+
80+
In `QuantumToolbox.jl`, we can calculate ``S(\omega)`` using either [`spectrum`](@ref), which provides several solvers to perform the Fourier transform semi-analytically, or we can use the function [`spectrum_correlation_fft`](@ref) to numerically calculate the fast Fourier transform (FFT) of a given correlation data.
81+
82+
The following example demonstrates how these methods can be used to obtain the emission (``\hat{A} = \hat{a}^\dagger`` and ``\hat{B} = \hat{a}``) power spectrum.
83+
84+
```@example correlation_and_spectrum
85+
N = 4 # number of cavity fock states
86+
ωc = 1.0 * 2 * π # cavity frequency
87+
ωa = 1.0 * 2 * π # atom frequency
88+
g = 0.1 * 2 * π # coupling strength
89+
κ = 0.75 # cavity dissipation rate
90+
γ = 0.25 # atom dissipation rate
91+
92+
# Jaynes-Cummings Hamiltonian
93+
a = tensor(destroy(N), qeye(2))
94+
sm = tensor(qeye(N), destroy(2))
95+
H = ωc * a' * a + ωa * sm' * sm + g * (a' * sm + a * sm')
96+
97+
# collapse operators
98+
n_th = 0.25
99+
c_ops = [
100+
sqrt(κ * (1 + n_th)) * a,
101+
sqrt(κ * n_th) * a',
102+
sqrt(γ) * sm,
103+
];
104+
105+
# calculate the correlation function using mesolve, and then FFT to obtain the spectrum.
106+
# Here we need to make sure to evaluate the correlation function for a sufficient long time and
107+
# sufficiently high sampling rate so that FFT captures all the features in the resulting spectrum.
108+
tlist = LinRange(0, 100, 5000)
109+
corr = correlation_2op_1t(H, nothing, tlist, c_ops, a', a; progress_bar = Val(false))
110+
ωlist1, spec1 = spectrum_correlation_fft(tlist, corr)
111+
112+
# calculate the power spectrum using spectrum
113+
# using Exponential Series (default) method
114+
ωlist2 = LinRange(0.25, 1.75, 200) * 2 * π
115+
spec2 = spectrum(H, ωlist2, c_ops, a', a; solver = ExponentialSeries())
116+
117+
# calculate the power spectrum using spectrum
118+
# using Pseudo-Inverse method
119+
spec3 = spectrum(H, ωlist2, c_ops, a', a; solver = PseudoInverse())
120+
121+
# plot by CairoMakie.jl
122+
fig = Figure(size = (500, 350))
123+
ax = Axis(fig[1, 1], title = "Vacuum Rabi splitting", xlabel = "Frequency", ylabel = "Emission power spectrum")
124+
lines!(ax, ωlist1 / (2 * π), spec1, label = "mesolve + FFT", linestyle = :solid)
125+
lines!(ax, ωlist2 / (2 * π), spec2, label = "Exponential Series", linestyle = :dash)
126+
lines!(ax, ωlist2 / (2 * π), spec3, label = "Pseudo-Inverse", linestyle = :dashdot)
127+
128+
xlims!(ax, ωlist2[1] / (2 * π), ωlist2[end] / (2 * π))
129+
axislegend(ax, position = :rt)
130+
131+
fig
132+
```
133+
134+
## Non-steadystate correlation function
135+
136+
More generally, we can also calculate correlation functions of the kind ``\left\langle \hat{A}(t_1 + t_2) \hat{B}(t_1) \right\rangle``, i.e., the correlation function of a system that is not in its steady state. In `QuantumToolbox.jl`, we can evaluate such correlation functions using the function [`correlation_2op_2t`](@ref). The default behavior of this function is to return a matrix with the correlations as a function of the two time coordinates (``t_1`` and ``t_2``).
137+
138+
```@example correlation_and_spectrum
139+
t1_list = LinRange(0, 10.0, 200)
140+
t2_list = LinRange(0, 10.0, 200)
141+
142+
N = 10
143+
a = destroy(N)
144+
x = a' + a
145+
H = a' * a
146+
147+
c_ops = [sqrt(0.25) * a]
148+
149+
α = 2.5
150+
ρ0 = coherent_dm(N, α)
151+
152+
corr = correlation_2op_2t(H, ρ0, t1_list, t2_list, c_ops, x, x; progress_bar = Val(false))
153+
154+
# plot by CairoMakie.jl
155+
fig = Figure(size = (500, 400))
156+
157+
ax = Axis(fig[1, 1], title = L"\langle \hat{x}(t_1 + t_2) \hat{x}(t_1) \rangle", xlabel = L"Time $t_1$", ylabel = L"Time $t_2$")
158+
159+
heatmap!(ax, t1_list, t2_list, real(corr))
160+
161+
fig
162+
```
163+
164+
### Example: first-order optical coherence function
165+
166+
This example demonstrates how to calculate a correlation function on the form ``\left\langle \hat{A}(\tau) \hat{B}(0) \right\rangle`` for a non-steady initial state. Consider an oscillator that is interacting with a thermal environment. If the oscillator initially is in a coherent state, it will gradually decay to a thermal (incoherent) state. The amount of coherence can be quantified using the first-order optical coherence function
167+
168+
```math
169+
g^{(1)}(\tau) = \frac{\left\langle \hat{a}^\dagger(\tau) \hat{a}(0) \right\rangle}{\sqrt{\left\langle \hat{a}^\dagger(\tau) \hat{a}(\tau) \right\rangle \left\langle \hat{a}^\dagger(0) \hat{a}(0)\right\rangle}}.
170+
```
171+
For a coherent state ``\vert g^{(1)}(\tau) \vert = 1``, and for a completely incoherent (thermal) state ``g^{(1)}(\tau) = 0``. The following code calculates and plots ``g^{(1)}(\tau)`` as a function of ``\tau``:
172+
173+
```@example correlation_and_spectrum
174+
τlist = LinRange(0, 10, 200)
175+
176+
# Hamiltonian
177+
N = 15
178+
a = destroy(N)
179+
H = 2 * π * a' * a
180+
181+
# collapse operator
182+
G1 = 0.75
183+
n_th = 2.00 # bath temperature in terms of excitation number
184+
c_ops = [
185+
sqrt(G1 * (1 + n_th)) * a,
186+
sqrt(G1 * n_th) * a'
187+
]
188+
189+
# start with a coherent state of α = 2.0
190+
ρ0 = coherent_dm(N, 2.0)
191+
192+
# first calculate the occupation number as a function of time
193+
n = mesolve(H, ρ0, τlist, c_ops, e_ops = [a' * a], progress_bar = Val(false)).expect[1,:]
194+
n0 = n[1] # occupation number at τ = 0
195+
196+
# calculate the correlation function G1 and normalize with n to obtain g1
197+
g1 = correlation_2op_1t(H, ρ0, τlist, c_ops, a', a, progress_bar = Val(false))
198+
g1 = g1 ./ sqrt.(n .* n0)
199+
200+
# plot by CairoMakie.jl
201+
fig = Figure(size = (500, 350))
202+
ax = Axis(fig[1, 1], title = "Decay of a coherent state to an incoherent (thermal) state", xlabel = L"Time $\tau$")
203+
lines!(ax, τlist, real(g1), label = L"g^{(1)}(\tau)", linestyle = :solid)
204+
lines!(ax, τlist, real(n), label = L"n(\tau)", linestyle = :dash)
205+
206+
axislegend(ax, position = :rt)
207+
208+
fig
209+
```
210+
211+
### Example: second-order optical coherence function
212+
213+
The second-order optical coherence function, with time-delay ``\tau``, is defined as
214+
215+
```math
216+
g^{(2)}(\tau) = \frac{\left\langle \hat{a}^\dagger(0) \hat{a}^\dagger(\tau) \hat{a}(\tau) \hat{a}(0) \right\rangle}{\left\langle \hat{a}^\dagger(0) \hat{a}(0) \right\rangle^2}.
217+
```
218+
219+
For a coherent state ``g^{(2)}(\tau) = 1``, for a thermal state ``g^{(2)}(\tau = 0) = 2`` and it decreases as a function of time (bunched photons, they tend to appear together), and for a Fock state with ``n``-photons ``g^{(2)}(\tau = 0) = n(n-1)/n^2 < 1`` and it increases with time (anti-bunched photons, more likely to arrive separated in time).
220+
221+
To calculate this type of correlation function with `QuantumToolbox.jl`, we can use [`correlation_3op_1t`](@ref), which computes a correlation function on the form ``\left\langle \hat{A}(0) \hat{B}(\tau) \hat{C}(0) \right\rangle`` (three operators and one delay-time vector). We first have to combine the central two operators into one single one as they are evaluated at the same time, e.g. here we do ``\hat{B}(\tau) = \hat{a}^\dagger(\tau) \hat{a}(\tau) = (\hat{a}^\dagger\hat{a})(\tau)``.
222+
223+
The following code calculates and plots ``g^{(2)}(\tau)`` as a function of ``\tau`` for a coherent, thermal and Fock state:
224+
225+
```@example correlation_and_spectrum
226+
τlist = LinRange(0, 25, 200)
227+
228+
# Hamiltonian
229+
N = 25
230+
a = destroy(N)
231+
H = 2 * π * a' * a
232+
233+
κ = 0.25
234+
n_th = 2.0 # bath temperature in terms of excitation number
235+
c_ops = [
236+
sqrt(κ * (1 + n_th)) * a,
237+
sqrt(κ * n_th) * a'
238+
]
239+
240+
cases = [
241+
Dict("state" => coherent_dm(N, sqrt(2)), "label" => "coherent state", "lstyle" => :solid),
242+
Dict("state" => thermal_dm(N, 2), "label" => "thermal state", "lstyle" => :dash),
243+
Dict("state" => fock_dm(N, 2), "label" => "Fock state", "lstyle" => :dashdot),
244+
]
245+
246+
# plot by CairoMakie.jl
247+
fig = Figure(size = (500, 350))
248+
ax = Axis(fig[1, 1], xlabel = L"Time $\tau$", ylabel = L"g^{(2)}(\tau)")
249+
250+
for case in cases
251+
ρ0 = case["state"]
252+
253+
# calculate the occupation number at τ = 0
254+
n0 = expect(a' * a, ρ0)
255+
256+
# calculate the correlation function g2
257+
g2 = correlation_3op_1t(H, ρ0, τlist, c_ops, a', a' * a, a, progress_bar = Val(false))
258+
g2 = g2 ./ n0^2
259+
260+
lines!(ax, τlist, real(g2), label = case["label"], linestyle = case["lstyle"])
261+
end
262+
263+
axislegend(ax, position = :rt)
264+
265+
fig
266+
```

src/QuantumToolbox.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import DiffEqNoiseProcess: RealWienerProcess!
6060
# other dependencies (in alphabetical order)
6161
import ArrayInterface: allowed_getindex, allowed_setindex!
6262
import Distributed: RemoteChannel
63-
import FFTW: fft, fftshift
63+
import FFTW: fft, ifft, fftfreq, fftshift
6464
import Graphs: connected_components, DiGraph
6565
import IncompleteLU: ilu
6666
import Pkg
@@ -107,6 +107,7 @@ include("time_evolution/time_evolution_dynamical.jl")
107107
# Others
108108
include("permutation.jl")
109109
include("correlations.jl")
110+
include("spectrum.jl")
110111
include("wigner.jl")
111112
include("spin_lattice.jl")
112113
include("arnoldi.jl")

0 commit comments

Comments
 (0)