Skip to content

Commit 84c22ce

Browse files
authored
Add Wigner plotting via CairoMakie (#292)
2 parents 519a191 + c10ac2b commit 84c22ce

File tree

10 files changed

+349
-20
lines changed

10 files changed

+349
-20
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ 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 `plot_wigner` function for easy plotting of Wigner functions. ([#86], [#292])
1011

1112

1213
## [v0.23.1]
@@ -44,7 +45,9 @@ Release date: 2024-11-13
4445
[v0.22.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.22.0
4546
[v0.23.0]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.0
4647
[v0.23.1]: https://github.com/qutip/QuantumToolbox.jl/releases/tag/v0.23.1
48+
[#86]: https://github.com/qutip/QuantumToolbox.jl/issues/86
4749
[#139]: https://github.com/qutip/QuantumToolbox.jl/issues/139
50+
[#292]: https://github.com/qutip/QuantumToolbox.jl/issues/292
4851
[#306]: https://github.com/qutip/QuantumToolbox.jl/issues/306
4952
[#309]: https://github.com/qutip/QuantumToolbox.jl/issues/309
5053
[#311]: https://github.com/qutip/QuantumToolbox.jl/issues/311

Project.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,17 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0"
2828

2929
[weakdeps]
3030
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
31+
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
3132

3233
[extensions]
3334
QuantumToolboxCUDAExt = "CUDA"
35+
QuantumToolboxCairoMakieExt = "CairoMakie"
3436

3537
[compat]
3638
Aqua = "0.8"
3739
ArrayInterface = "6, 7"
3840
CUDA = "5"
41+
CairoMakie = "0.12"
3942
DiffEqBase = "6"
4043
DiffEqCallbacks = "4.2.1 - 4"
4144
DiffEqNoiseProcess = "5"
@@ -62,8 +65,9 @@ julia = "1.10"
6265

6366
[extras]
6467
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
68+
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
6569
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
6670
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
6771

6872
[targets]
69-
test = ["Aqua", "JET", "Test"]
73+
test = ["Aqua", "CairoMakie", "JET", "Test"]

docs/make.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ using DocumenterVitepress
77
using DocumenterCitations
88
using Changelog
99

10+
# Load of packages required to compile the extension documentation
11+
using CairoMakie
12+
1013
DocMeta.setdocmeta!(QuantumToolbox, :DocTestSetup, :(using QuantumToolbox); recursive = true)
1114

1215
# some options for `makedocs`
@@ -76,7 +79,10 @@ const PAGES = [
7679
]
7780

7881
makedocs(;
79-
modules = [QuantumToolbox],
82+
modules = [
83+
QuantumToolbox,
84+
Base.get_extension(QuantumToolbox, :QuantumToolboxCairoMakieExt),
85+
],
8086
authors = "Alberto Mercurio and Yi-Te Huang",
8187
repo = Remotes.GitHub("qutip", "QuantumToolbox.jl"),
8288
sitename = "QuantumToolbox.jl",

docs/src/resources/api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,9 @@ convert_unit
281281
row_major_reshape
282282
meshgrid
283283
```
284+
285+
## [Visualization](@id doc-API:Visualization)
286+
287+
```@docs
288+
plot_wigner
289+
```

docs/src/tutorials/logo.md

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,25 +67,16 @@ Next, we construct the triangular cat state as a normalized superposition of thr
6767
normalize!(ψ)
6868
```
6969

70-
### Defining the Grid and calculating the Wigner function
70+
### Defining the Grid and plotting the Wigner function
7171

72-
We define the grid for the Wigner function and calculate it using the [`wigner`](@ref) function. We shift the grid in the imaginary direction to ensure that the Wigner function is centered around the origin of the figure. The [`wigner`](@ref) function also supports the `g` scaling factor, which we put here equal to ``2``.
72+
We define the grid for the Wigner function and plot it using the [`plot_wigner`](@ref) function. This, internally calls the [`wigner`](@ref) function for the computation. We shift the grid in the imaginary direction to ensure that the Wigner function is centered around the origin of the figure. The [`wigner`](@ref) function also supports the `g` scaling factor, which we put here equal to ``2``.
7373

7474
```@example logo
7575
xvec = range(-ρ, ρ, 500) .* 1.5
7676
yvec = xvec .+ (abs(imag(α1)) - abs(imag(α2))) / 2
7777
78-
wig = wigner(ψ, xvec, yvec, g = 2)
79-
```
80-
81-
### Plotting the Wigner function
82-
83-
Finally, we plot the Wigner function using the `heatmap` function from the `CairoMakie` package.
84-
85-
```@example logo
8678
fig = Figure(size = (250, 250), figure_padding = 0)
87-
ax = Axis(fig[1, 1])
88-
heatmap!(ax, xvec, yvec, wig', colormap = :RdBu, interpolate = true, rasterize = 1)
79+
fig, ax, hm = plot_wigner(ψ, xvec = xvec, yvec = yvec, g = 2, library = Val(:CairoMakie), location = fig[1,1])
8980
hidespines!(ax)
9081
hidexdecorations!(ax)
9182
hideydecorations!(ax)
@@ -118,12 +109,8 @@ nothing # hide
118109
And the Wigner function becomes more uniform:
119110

120111
```@example logo
121-
wig = wigner(sol.states[end], xvec, yvec, g = 2)
122-
123112
fig = Figure(size = (250, 250), figure_padding = 0)
124-
ax = Axis(fig[1, 1])
125-
126-
img_wig = heatmap!(ax, xvec, yvec, wig', colormap = :RdBu, interpolate = true, rasterize = 1)
113+
fig, ax, hm = plot_wigner(sol.states[end], xvec = xvec, yvec = yvec, g = 2, library = Val(:CairoMakie), location = fig[1,1])
127114
hidespines!(ax)
128115
hidexdecorations!(ax)
129116
hideydecorations!(ax)
@@ -135,7 +122,7 @@ At this stage, we have finished to use the `QuantumToolbox` package. From now on
135122

136123
### Custom Colormap
137124

138-
We define a custom colormap that changes depending on the Wigner function and spatial coordinates. Indeed, we want the three different colormaps, in the regions corresponding to the three coherent states, to match the colors of the Julia logo. We also want the colormap change to be smooth, so we use a Gaussian function to blend the colors. We introduce also a Wigner function dependent transparency to make the logo more appealing.
125+
We define a custom colormap that changes depending on the Wigner function and spatial coordinates. Indeed, we want the three different colormaps, in the regions corresponding to the three coherent states, to match the colors of the Julia logo. We also want the colormap change to be smooth, so we use a Gaussian function to blend the colors. We introduce also a Wigner function dependent transparency to make the logo more appealing. In order to do so, we are going to need the value of the wigner function at each point of the grid, rather than its plot. We will thus call the [`wigner`](@ref) function directly.
139126

140127
```@example logo
141128
function set_color_julia(x, y, wig::T, α1, α2, α3, cmap1, cmap2, cmap3, δ) where {T}
@@ -156,6 +143,7 @@ function set_color_julia(x, y, wig::T, α1, α2, α3, cmap1, cmap2, cmap3, δ) w
156143
return RGBAf(c_tot.r, c_tot.g, c_tot.b, alpha)
157144
end
158145
146+
wig = wigner(sol.states[end], xvec, yvec, g = 2)
159147
X, Y = meshgrid(xvec, yvec)
160148
δ = 1.25 # Smoothing parameter for the Gaussian functions
161149
```

ext/QuantumToolboxCairoMakieExt.jl

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
module QuantumToolboxCairoMakieExt
2+
3+
using QuantumToolbox
4+
using CairoMakie: Axis, Axis3, Colorbar, Figure, GridLayout, heatmap!, surface!, GridPosition, @L_str, Reverse
5+
6+
@doc raw"""
7+
plot_wigner(
8+
library::Val{:CairoMakie},
9+
state::QuantumObject{DT,OpType};
10+
xvec::Union{Nothing,AbstractVector} = nothing,
11+
yvec::Union{Nothing,AbstractVector} = nothing,
12+
g::Real = √2,
13+
method::WignerSolver = WignerClenshaw(),
14+
projection::Union{Val,Symbol} = Val(:two_dim),
15+
location::Union{GridPosition,Nothing} = nothing,
16+
colorbar::Bool = false,
17+
kwargs...
18+
) where {DT,OpType}
19+
20+
Plot the [Wigner quasipropability distribution](https://en.wikipedia.org/wiki/Wigner_quasiprobability_distribution) of `state` using the [`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) plotting library.
21+
22+
# Arguments
23+
- `library::Val{:CairoMakie}`: The plotting library to use.
24+
- `state::QuantumObject`: The quantum state for which the Wigner function is calculated. It can be either a [`Ket`](@ref), [`Bra`](@ref), or [`Operator`](@ref).
25+
- `xvec::AbstractVector`: The x-coordinates of the phase space grid. Defaults to a linear range from -7.5 to 7.5 with 200 points.
26+
- `yvec::AbstractVector`: The y-coordinates of the phase space grid. Defaults to a linear range from -7.5 to 7.5 with 200 points.
27+
- `g::Real`: The scaling factor related to the value of ``\hbar`` in the commutation relation ``[x, y] = i \hbar`` via ``\hbar=2/g^2``.
28+
- `method::WignerSolver`: The method used to calculate the Wigner function. It can be either `WignerLaguerre()` or `WignerClenshaw()`, with `WignerClenshaw()` as default. The `WignerLaguerre` method has the optional `parallel` and `tol` parameters, with default values `true` and `1e-14`, respectively.
29+
- `projection::Union{Val,Symbol}`: Whether to plot the Wigner function in 2D or 3D. It can be either `Val(:two_dim)` or `Val(:three_dim)`, with `Val(:two_dim)` as default.
30+
- `location::Union{GridPosition,Nothing}`: The location of the plot in the layout. If `nothing`, the plot is created in a new figure. Default is `nothing`.
31+
- `colorbar::Bool`: Whether to include a colorbar in the plot. Default is `false`.
32+
- `kwargs...`: Additional keyword arguments to pass to the plotting function.
33+
34+
# Returns
35+
- `fig`: The figure object.
36+
- `ax`: The axis object.
37+
- `hm`: Either the heatmap or surface object, depending on the projection.
38+
39+
!!! note "Import library first"
40+
[`CairoMakie`](https://github.com/MakieOrg/Makie.jl/tree/master/CairoMakie) must first be imported before using this function.
41+
42+
!!! warning "Beware of type-stability!"
43+
If you want to keep type stability, it is recommended to use `Val(:two_dim)` and `Val(:three_dim)` instead of `:two_dim` and `:three_dim`, respectively. Also, specify the library as `Val(:CairoMakie)` See [this link](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-value-type) and the [related Section](@ref doc:Type-Stability) about type stability for more details.
44+
"""
45+
function QuantumToolbox.plot_wigner(
46+
library::Val{:CairoMakie},
47+
state::QuantumObject{DT,OpType};
48+
xvec::Union{Nothing,AbstractVector} = LinRange(-7.5, 7.5, 200),
49+
yvec::Union{Nothing,AbstractVector} = LinRange(-7.5, 7.5, 200),
50+
g::Real = 2,
51+
method::WignerSolver = WignerClenshaw(),
52+
projection::Union{Val,Symbol} = Val(:two_dim),
53+
location::Union{GridPosition,Nothing} = nothing,
54+
colorbar::Bool = false,
55+
kwargs...,
56+
) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}}
57+
QuantumToolbox.getVal(projection) == :two_dim ||
58+
QuantumToolbox.getVal(projection) == :three_dim ||
59+
throw(ArgumentError("Unsupported projection: $projection"))
60+
61+
return _plot_wigner(
62+
library,
63+
state,
64+
xvec,
65+
yvec,
66+
QuantumToolbox.makeVal(projection),
67+
g,
68+
method,
69+
location,
70+
colorbar;
71+
kwargs...,
72+
)
73+
end
74+
75+
function _plot_wigner(
76+
::Val{:CairoMakie},
77+
state::QuantumObject{DT,OpType},
78+
xvec::AbstractVector,
79+
yvec::AbstractVector,
80+
projection::Val{:two_dim},
81+
g::Real,
82+
method::WignerSolver,
83+
location::Union{GridPosition,Nothing},
84+
colorbar::Bool;
85+
kwargs...,
86+
) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}}
87+
fig, location = _getFigAndLocation(location)
88+
89+
lyt = GridLayout(location)
90+
91+
ax = Axis(lyt[1, 1])
92+
93+
wig = wigner(state, xvec, yvec; g = g, method = method)
94+
wlim = maximum(abs, wig)
95+
96+
kwargs = merge(Dict(:colormap => Reverse(:RdBu), :colorrange => (-wlim, wlim)), kwargs)
97+
hm = heatmap!(ax, xvec, yvec, wig'; kwargs...)
98+
99+
if colorbar
100+
Colorbar(lyt[1, 2], hm)
101+
end
102+
103+
ax.xlabel = L"\textrm{Re}(\alpha)"
104+
ax.ylabel = L"\textrm{Im}(\alpha)"
105+
return fig, ax, hm
106+
end
107+
108+
function _plot_wigner(
109+
::Val{:CairoMakie},
110+
state::QuantumObject{DT,OpType},
111+
xvec::AbstractVector,
112+
yvec::AbstractVector,
113+
projection::Val{:three_dim},
114+
g::Real,
115+
method::WignerSolver,
116+
location::Union{GridPosition,Nothing},
117+
colorbar::Bool;
118+
kwargs...,
119+
) where {DT,OpType<:Union{BraQuantumObject,KetQuantumObject,OperatorQuantumObject}}
120+
fig, location = _getFigAndLocation(location)
121+
122+
lyt = GridLayout(location)
123+
124+
ax = Axis3(lyt[1, 1], azimuth = 1.775pi, elevation = pi / 16, protrusions = (30, 90, 30, 30), viewmode = :stretch)
125+
126+
wig = wigner(state, xvec, yvec; g = g, method = method)
127+
wlim = maximum(abs, wig)
128+
129+
kwargs = merge(Dict(:colormap => :RdBu, :colorrange => (-wlim, wlim)), kwargs)
130+
surf = surface!(ax, xvec, yvec, wig'; kwargs...)
131+
132+
if colorbar
133+
Colorbar(lyt[1, 2], surf)
134+
end
135+
136+
ax.xlabel = L"\textrm{Re}(\alpha)"
137+
ax.ylabel = L"\textrm{Im}(\alpha)"
138+
ax.zlabel = "Wigner function"
139+
return fig, ax, surf
140+
end
141+
142+
raw"""
143+
_getFigAndLocation(location::Nothing)
144+
145+
Create a new figure and return it, together with the GridPosition object pointing to the first cell.
146+
147+
# Arguments
148+
- `location::Nothing`
149+
150+
# Returns
151+
- `fig`: The figure object.
152+
- `location`: The GridPosition object pointing to the first cell.
153+
"""
154+
function _getFigAndLocation(location::Nothing)
155+
fig = Figure()
156+
return fig, fig[1, 1]
157+
end
158+
159+
raw"""
160+
_getFigAndLocation(location::GridPosition)
161+
162+
Compute which figure does the location belong to and return it, together with the location itself.
163+
164+
# Arguments
165+
- `location::GridPosition`
166+
167+
# Returns
168+
- `fig`: The figure object.
169+
- `location`: The GridPosition object.
170+
"""
171+
function _getFigAndLocation(location::GridPosition)
172+
fig = _figFromChildren(location.layout)
173+
return fig, location
174+
end
175+
176+
raw"""
177+
_figFromChildren(children::GridLayout)
178+
179+
Recursively find the figure object from the children layout.
180+
181+
# Arguments
182+
- `children::GridLayout`
183+
184+
# Returns
185+
- Union{Nothing, Figure, GridLayout}: The children's parent object.
186+
"""
187+
_figFromChildren(children) = _figFromChildren(children.parent)
188+
189+
raw"""
190+
_figFromChildren(fig::Figure)
191+
192+
Return the figure object
193+
194+
# Arguments
195+
- `fig::Figure`
196+
197+
# Returns
198+
- `fig`: The figure object.
199+
"""
200+
_figFromChildren(fig::Figure) = fig
201+
202+
raw"""
203+
_figFromChildren(::Nothing)
204+
205+
Throw an error if no figure has been found.
206+
207+
# Arguments
208+
- `::Nothing`
209+
210+
# Throws
211+
- `ArgumentError`: If no figure has been found.
212+
"""
213+
_figFromChildren(::Nothing) = throw(ArgumentError("No Figure has been found at the top of the layout hierarchy."))
214+
215+
end

src/QuantumToolbox.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ include("arnoldi.jl")
114114
include("metrics.jl")
115115
include("negativity.jl")
116116
include("steadystate.jl")
117+
include("visualization.jl")
117118

118119
# deprecated functions
119120
include("deprecated.jl")

0 commit comments

Comments
 (0)