Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

- Improve Bloch sphere rendering for animation. ([#520])

## [v0.34.0]
Release date: 2025-07-29

Expand Down Expand Up @@ -292,3 +294,4 @@ Release date: 2024-11-13
[#513]: https://github.com/qutip/QuantumToolbox.jl/issues/513
[#515]: https://github.com/qutip/QuantumToolbox.jl/issues/515
[#517]: https://github.com/qutip/QuantumToolbox.jl/issues/517
[#520]: https://github.com/qutip/QuantumToolbox.jl/issues/520
64 changes: 63 additions & 1 deletion docs/src/users_guide/plotting_the_bloch_sphere.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,66 @@ These properties can also be accessed via the `print` command:
```@example Bloch_sphere_rendering
b = Bloch()
print(b)
```
```

## Animating with the Bloch sphere

The [`Bloch`](@ref) structure was designed from the outset to generate animations. To animate a set of vectors or data points, the basic idea is: plot the data at time ``t_1``, save the sphere, clear the sphere, plot data at ``t_2``, and so on. The easiest way to animate data on the Bloch sphere is to use the `record` function provided by [`Makie.jl`](https://docs.makie.org/stable/). We will demonstrate this functionality with the following example: the decay of a qubit on the Bloch sphere.

```@example Bloch_sphere_rendering
# system parameters
ω = 2π
θ = 0.2π
n_th = 0.5 # temperature
γ1 = 0.5
γ2 = 0.2

# operators and the Hamiltonian
sx = sigmax()
sy = sigmay()
sz = sigmaz()
sm = sigmam()
H = ω * (cos(θ) * sz + sin(θ) * sx)

# collapse operators
c_op_list = (
√(γ1 * (n_th + 1)) * sm,
√(γ1 * n_th) * sm',
√γ2 * sz
)

# solving evolution
ψ0 = basis(2, 0)
tlist = LinRange(0, 4, 250)
sol = mesolve(H, ψ0, tlist, c_op_list, e_ops = (sx, sy, sz), progress_bar = Val(false))
```

To animate a set of vectors or data points, we use the `record` function provided by [`Makie.jl`](https://docs.makie.org/stable/):

```@example Bloch_sphere_rendering
# expectation values
x = real(sol.expect[1,:])
y = real(sol.expect[2,:])
z = real(sol.expect[3,:])

# create Bloch sphere
b = Bloch()
b.view = [50,30]
fig, lscene = render(b)

# save animation
record(fig, "qubit_decay.mp4", eachindex(tlist), framerate = 20) do idx
clear!(b)
add_vectors!(b, [sin(θ), 0, cos(θ)])
add_points!(b, [x[1:idx], y[1:idx], z[1:idx]])
render(b, location = lscene)
end
nothing # hide
```

```@raw html
<video autoplay loop muted playsinline controls src="./qubit_decay.mp4" />
```

!!! note
Here, we set the keyword argument `location = lscene` in the last `render` function to update the existing Bloch sphere without creating new `Figure` and `LScene`. This is efficient when drawing animations.
44 changes: 34 additions & 10 deletions ext/QuantumToolboxMakieExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -337,14 +337,24 @@ Render the Bloch sphere visualization from the given [`Bloch`](@ref) object `b`.
# Arguments

- `b::Bloch`: The Bloch sphere object containing states, vectors, and settings to visualize.
- `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`.
- `location::Union{GridPosition,LScene,Nothing}`: The location of the plot in the layout, or `Makie.LScene`. Default is `nothing`.


# Returns

- A tuple `(fig, lscene)` where `fig` is the figure object and `lscene` is the LScene object used for plotting. These can be further manipulated or saved by the user.

# Notes

The keyword argument `location` can be in the either type:

- `Nothing` (default): Create a new figure and plot the Bloch sphere.
- `GridPosition`: Plot the Bloch sphere in the specified location of the plot in the layout.
- `LScene`: Update the existing Bloch sphere using new data and settings in `b::Bloch` without creating new `Figure` and `LScene` (efficient for drawing animation).
"""
function QuantumToolbox.render(b::Bloch; location = nothing)
fig, lscene = _setup_bloch_plot!(b, location)
fig, lscene = _setup_bloch_plot!(location)
_setup_bloch_camara!(b, lscene)
_draw_bloch_sphere!(b, lscene)
_add_labels!(b, lscene)

Expand All @@ -358,30 +368,44 @@ function QuantumToolbox.render(b::Bloch; location = nothing)
end

raw"""
_setup_bloch_plot!(b::Bloch, location) -> (fig, lscene)
_setup_bloch_plot!(location) -> (fig, lscene)

Initialize the figure and `3D` axis for Bloch sphere visualization.
Initialize the Figure and LScene for Bloch sphere visualization.

# Arguments
- `b::Bloch`: Bloch sphere object containing view parameters
- `location`: Figure layout position specification
- `location`: Figure layout position specification, or directly `Makie.LScene` for updating Bloch sphere.

# Returns
- `fig`: Created Makie figure
- `lscene`: Configured LScene object

Sets up the `3D` coordinate system with appropriate limits and view angles.
"""
function _setup_bloch_plot!(b::Bloch, location)
function _setup_bloch_plot!(location)
fig, location = _getFigAndLocation(location)
lscene = LScene(location, show_axis = false, scenekw = (clear = true,))
return fig, lscene
end

function _setup_bloch_plot!(lscene::LScene)
# this function only removes all existing Plots in lscene
# it is useful for users to just update Bloch sphere without creating new figure and lscene (efficient for drawing animation)
fig = lscene.parent
empty!(lscene.scene.plots)
return fig, lscene
end

raw"""
_setup_bloch_camara!(b::Bloch, lscene)

Setup the distance and view angle of the camara.
"""
function _setup_bloch_camara!(b::Bloch, lscene)
length(b.view) == 2 || throw(ArgumentError("The length of `Bloch.view` must be 2."))
cam3d!(lscene.scene, center = false)
cam = cameracontrols(lscene)
cam.fov[] = 12 # Set field of view to 12 degrees
dist = 12 # Set distance from the camera to the Bloch sphere
update_cam!(lscene.scene, cam, deg2rad(b.view[1]), deg2rad(b.view[2]), dist)
return fig, lscene
return nothing
end

raw"""
Expand Down
8 changes: 8 additions & 0 deletions test/ext-test/cpu/makie/makie_ext.jl
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,12 @@ end
@test false
@info "Render threw unexpected error" exception=e
end

# if render location is given as lscene, should return the same Figure and LScene
b = Bloch()
fig1, lscene1 = render(b)
add_states!(b, ψ₁)
fig2, lscene2 = render(b, location = lscene1)
@test fig2 === fig1
@test lscene2 === lscene1
end
Loading