Skill Tracer
Skills › Scientific Agent Skills › qutip › visualization.md

visualization.md

338 lines · skills/qutip/references/visualization.md at 5f2bc03

Open on GitHub
1# QuTiP 5.3 Visualization
2
3Research and API verification date: **2026-10-01**. Examples target
4`qutip==5.3.1` with its pinned graphics extra.
5
6```bash
7uv pip install "qutip[graphics]==5.3.1"
8```
9
10Plots are diagnostics and communication artifacts, not substitutes for
11normalization, positivity, convergence, or uncertainty checks.

QuTiP 5.3 Visualization

Research and API verification date: 2026-10-01. Examples target qutip==5.3.1 with its pinned graphics extra.

uv pip install "qutip[graphics]==5.3.1"

Plots are diagnostics and communication artifacts, not substitutes for normalization, positivity, convergence, or uncertainty checks.

Raw lines 1 to 12
13## Phase-space coordinates and axis order
15For QuTiP's oscillator phase-space functions, the default scaling is
17\[
18a = \tfrac12 g(x + i y), \qquad g=\sqrt{2},
19\]
21which corresponds to \(\hbar=2/g^2=1\).
23In QuTiP 5.3, returned arrays use:
25```text
26array[j, k] <-> yvec[j], xvec[k]
27```
29This applies to `wigner`, `qfunc`, and class-based `QFunc`. Therefore, pass
30`xvec` horizontally and `yvec` vertically to Matplotlib:
32```python
33image = ax.pcolormesh(xvec, yvec, values, shading="auto")
34```
36The 5.3 release notes explicitly clarified this order. Do not transpose by
37habit; test with unequal x/y lengths.

Phase-space coordinates and axis order

For QuTiP's oscillator phase-space functions, the default scaling is

\[ a = \tfrac12 g(x + i y), \qquad g=\sqrt{2}, \]

which corresponds to \(\hbar=2/g^2=1\).

In QuTiP 5.3, returned arrays use:

array[j, k] <-> yvec[j], xvec[k]

This applies to wigner, qfunc, and class-based QFunc. Therefore, pass xvec horizontally and yvec vertically to Matplotlib:

image = ax.pcolormesh(xvec, yvec, values, shading="auto")

The 5.3 release notes explicitly clarified this order. Do not transpose by habit; test with unequal x/y lengths.

Raw lines 13 to 38
39## Wigner function
41Current signature:
43```text
44wigner(psi, xvec, yvec=None, method="clenshaw", g=sqrt(2),
45 sparse=False, parfor=False, offset=0)
46```
48```python
49import numpy as np
50import matplotlib.pyplot as plt
51from qutip import coherent, wigner
53N = 30
54state = coherent(N, 1.5)
55xvec = np.linspace(-5.0, 5.0, 201)
56yvec = np.linspace(-4.0, 4.0, 161)
57W = wigner(state, xvec, yvec, method="clenshaw")
59fig, ax = plt.subplots()
60limit = float(np.max(np.abs(W)))
61mesh = ax.pcolormesh(
62 xvec,
63 yvec,
64 W,
65 shading="auto",
66 cmap="RdBu_r",
67 vmin=-limit,
68 vmax=limit,
70ax.set(xlabel="x", ylabel="y", title="Wigner function")
71fig.colorbar(mesh, ax=ax)
72fig.tight_layout()
73```
75Methods:
77- `clenshaw`: robust default, especially at higher excitation;
78- `iterative`: recurrence method;
79- `laguerre`: can help for sparse high-dimensional states;
80- `fft`: computes y coordinates internally and has a different return form.
82The `offset` argument added in 5.3 supports Fock representations whose first
83represented number state is not zero.
85Numerical checks:
87- sweep Hilbert cutoff and phase-space extent;
88- increase grid density;
89- compare normalization using the documented coordinate scaling;
90- treat tiny negative values near numerical tolerance separately from robust
91 Wigner negativity;
92- preserve an equal data aspect ratio when x and y share physical units.
94Current convenience plotting:
96```python
97from qutip import plot_wigner
99fig, ax = plot_wigner(
100 state,
101 xvec=xvec,
102 yvec=yvec,
103 projection="2d",
104 colorbar=True,
106```
108Use the returned figure and axis rather than relying on global plotting state.

Wigner function

Current signature:

wigner(psi, xvec, yvec=None, method="clenshaw", g=sqrt(2),
       sparse=False, parfor=False, offset=0)
import numpy as np
import matplotlib.pyplot as plt
from qutip import coherent, wigner

N = 30
state = coherent(N, 1.5)
xvec = np.linspace(-5.0, 5.0, 201)
yvec = np.linspace(-4.0, 4.0, 161)
W = wigner(state, xvec, yvec, method="clenshaw")

fig, ax = plt.subplots()
limit = float(np.max(np.abs(W)))
mesh = ax.pcolormesh(
    xvec,
    yvec,
    W,
    shading="auto",
    cmap="RdBu_r",
    vmin=-limit,
    vmax=limit,
)
ax.set(xlabel="x", ylabel="y", title="Wigner function")
fig.colorbar(mesh, ax=ax)
fig.tight_layout()

Methods:

  • clenshaw: robust default, especially at higher excitation;
  • iterative: recurrence method;
  • laguerre: can help for sparse high-dimensional states;
  • fft: computes y coordinates internally and has a different return form.

The offset argument added in 5.3 supports Fock representations whose first represented number state is not zero.

Numerical checks:

  • sweep Hilbert cutoff and phase-space extent;
  • increase grid density;
  • compare normalization using the documented coordinate scaling;
  • treat tiny negative values near numerical tolerance separately from robust Wigner negativity;
  • preserve an equal data aspect ratio when x and y share physical units.

Current convenience plotting:

from qutip import plot_wigner

fig, ax = plot_wigner(
    state,
    xvec=xvec,
    yvec=yvec,
    projection="2d",
    colorbar=True,
)

Use the returned figure and axis rather than relying on global plotting state.

Raw lines 39 to 109
110## Husimi Q function

Husimi Q function

Raw lines 110 to 111
112### One state
114Current signature:
116```text
117qfunc(state, xvec, yvec, g=sqrt(2), precompute_memory=1024, cutoff=170)
118```
120```python
121from qutip import qfunc
123Q = qfunc(state, xvec, yvec)
124assert Q.shape == (len(yvec), len(xvec))
126fig, ax = plt.subplots()
127mesh = ax.pcolormesh(xvec, yvec, Q, shading="auto", cmap="viridis")
128fig.colorbar(mesh, ax=ax)
129```
131`qfunc` and `QFunc` gained `cutoff=170` in 5.3.1 to stabilize
132large coherent amplitudes. This is an algorithmic threshold, not the Hilbert
133cutoff `N`; independently converge the physical state truncation.
135The Q function is nonnegative in exact arithmetic, but plotting still needs
136truncation, extent, and grid checks.

One state

Current signature:

qfunc(state, xvec, yvec, g=sqrt(2), precompute_memory=1024, cutoff=170)
from qutip import qfunc

Q = qfunc(state, xvec, yvec)
assert Q.shape == (len(yvec), len(xvec))

fig, ax = plt.subplots()
mesh = ax.pcolormesh(xvec, yvec, Q, shading="auto", cmap="viridis")
fig.colorbar(mesh, ax=ax)

qfunc and QFunc gained cutoff=170 in 5.3.1 to stabilize large coherent amplitudes. This is an algorithmic threshold, not the Hilbert cutoff N; independently converge the physical state truncation.

The Q function is nonnegative in exact arithmetic, but plotting still needs truncation, extent, and grid checks.

Raw lines 112 to 137
138### Many states on the same grid
140Current class usage is:
142```python
143from qutip import QFunc
145q_on_grid = QFunc(xvec, yvec, memory=256)
146Q_first = q_on_grid(state_a)
147Q_second = q_on_grid(state_b)
148```
150`QFunc` is constructed with fixed coordinates and then **called with each
151state**. QuTiP 5.3 exposes no `.eval` method on this class. This skill does not
152use Python dynamic-code execution.
154The `memory` parameter bounds internal workspace in MB and can raise
155`MemoryError` for a large state. For a one-off large state, use `qfunc` with a
156carefully selected `precompute_memory`.

Many states on the same grid

Current class usage is:

from qutip import QFunc

q_on_grid = QFunc(xvec, yvec, memory=256)
Q_first = q_on_grid(state_a)
Q_second = q_on_grid(state_b)

QFunc is constructed with fixed coordinates and then called with each state. QuTiP 5.3 exposes no .eval method on this class. This skill does not use Python dynamic-code execution.

The memory parameter bounds internal workspace in MB and can raise MemoryError for a large state. For a one-off large state, use qfunc with a carefully selected precompute_memory.

Raw lines 138 to 157
158## Bloch sphere
160```python
161import matplotlib.pyplot as plt
162from qutip import Bloch, basis
164psi = (basis(2, 0) + 1j * basis(2, 1)).unit()
165bloch = Bloch()
166bloch.add_states(psi)
167bloch.add_vectors([0.0, 0.0, 1.0], color="black")
168bloch.make_sphere()
169plt.show()
170```
172For dynamics, solve with saved states or the three Pauli expectations:
174```python
175from qutip import sigmax, sigmay, sigmaz
177result = mesolve(
178 H,
179 rho0,
180 tlist,
181 c_ops=c_ops,
182 e_ops=[sigmax(), sigmay(), sigmaz()],
184bloch = Bloch()
185bloch.add_points([result.expect[0], result.expect[1], result.expect[2]])
186bloch.make_sphere()
187```
189Audit each Bloch vector norm. A density matrix maps inside the unit sphere; a
190vector materially outside it indicates numerical or modeling error.
192Use explicit colors and line styles and a colorblind-safe palette. QuTiP
193settings include:
195```python
196import qutip
198qutip.settings.colorblind_safe = True
199```
201Avoid mutating global settings in reusable library code unless the caller
202expects it.

Bloch sphere

import matplotlib.pyplot as plt
from qutip import Bloch, basis

psi = (basis(2, 0) + 1j * basis(2, 1)).unit()
bloch = Bloch()
bloch.add_states(psi)
bloch.add_vectors([0.0, 0.0, 1.0], color="black")
bloch.make_sphere()
plt.show()

For dynamics, solve with saved states or the three Pauli expectations:

from qutip import sigmax, sigmay, sigmaz

result = mesolve(
    H,
    rho0,
    tlist,
    c_ops=c_ops,
    e_ops=[sigmax(), sigmay(), sigmaz()],
)
bloch = Bloch()
bloch.add_points([result.expect[0], result.expect[1], result.expect[2]])
bloch.make_sphere()

Audit each Bloch vector norm. A density matrix maps inside the unit sphere; a vector materially outside it indicates numerical or modeling error.

Use explicit colors and line styles and a colorblind-safe palette. QuTiP settings include:

import qutip

qutip.settings.colorblind_safe = True

Avoid mutating global settings in reusable library code unless the caller expects it.

Raw lines 158 to 203
204## Fock distributions
206```python
207from qutip import plot_fock_distribution
209fig, ax = plot_fock_distribution(state)
210ax.set(title="Fock probabilities", xlabel="n", ylabel="Probability")
211fig.tight_layout()
212```
214For comparisons, share axes and use the returned `fig, ax`:
216```python
217fig, axes = plt.subplots(1, 2, figsize=(9, 3), sharey=True)
218plot_fock_distribution(state_a, fig=fig, ax=axes[0])
219plot_fock_distribution(state_b, fig=fig, ax=axes[1])
220```
222Report the probability in the highest represented levels. A visually small
223last bar may still be insufficient if a target observable weights high
224occupations strongly.

Fock distributions

from qutip import plot_fock_distribution

fig, ax = plot_fock_distribution(state)
ax.set(title="Fock probabilities", xlabel="n", ylabel="Probability")
fig.tight_layout()

For comparisons, share axes and use the returned fig, ax:

fig, axes = plt.subplots(1, 2, figsize=(9, 3), sharey=True)
plot_fock_distribution(state_a, fig=fig, ax=axes[0])
plot_fock_distribution(state_b, fig=fig, ax=axes[1])

Report the probability in the highest represented levels. A visually small last bar may still be insufficient if a target observable weights high occupations strongly.

Raw lines 204 to 225
226## Matrix diagnostics
228Hinton diagrams:
230```python
231from qutip import hinton
233fig, ax = hinton(rho, color_style="phase")
234```
236Three-dimensional matrix histograms:
238```python
239from qutip import matrix_histogram
241fig, ax = matrix_histogram(rho, bar_style="abs", color_style="phase")
242```
244QuTiP 5 uses `x_basis`, `y_basis`, `bar_style`, and `color_style` rather than
245old ad hoc label and bar-type recipes. Pass a `Qobj` where supported so
246dimension-aware labels can be retained.
248For dense matrices beyond a modest size, a heatmap is usually more legible and
249less expensive than 3D bars. Never hide the imaginary part when it is relevant.

Matrix diagnostics

Hinton diagrams:

from qutip import hinton

fig, ax = hinton(rho, color_style="phase")

Three-dimensional matrix histograms:

from qutip import matrix_histogram

fig, ax = matrix_histogram(rho, bar_style="abs", color_style="phase")

QuTiP 5 uses x_basis, y_basis, bar_style, and color_style rather than old ad hoc label and bar-type recipes. Pass a Qobj where supported so dimension-aware labels can be retained.

For dense matrices beyond a modest size, a heatmap is usually more legible and less expensive than 3D bars. Never hide the imaginary part when it is relevant.

Raw lines 226 to 250
251## Solver result plots
253QuTiP 5.3 adds result methods:
255```python
256fig, axes = result.plot_expect(labels=["population", "coherence"])
257```
259For publication or reusable analysis, explicit plotting remains clearer:
261```python
262fig, ax = plt.subplots()
263ax.plot(result.times, result.e_data["population"], label="population")
264ax.set(xlabel="time", ylabel="expectation value")
265ax.legend()
266fig.tight_layout()
267```
269Multi-trajectory means need uncertainty bands:
271```python
272mean = np.asarray(result.average_expect[0])
273standard_error = np.asarray(result.std_expect[0]) / np.sqrt(result.num_trajectories)
274ax.plot(result.times, mean)
275ax.fill_between(
276 result.times,
277 mean - 1.96 * standard_error,
278 mean + 1.96 * standard_error,
279 alpha=0.25,
281```
283Confirm that the trajectory estimator and sample count justify the chosen
284interval; the formula above is only a simple independent-sample approximation.

Solver result plots

QuTiP 5.3 adds result methods:

fig, axes = result.plot_expect(labels=["population", "coherence"])

For publication or reusable analysis, explicit plotting remains clearer:

fig, ax = plt.subplots()
ax.plot(result.times, result.e_data["population"], label="population")
ax.set(xlabel="time", ylabel="expectation value")
ax.legend()
fig.tight_layout()

Multi-trajectory means need uncertainty bands:

mean = np.asarray(result.average_expect[0])
standard_error = np.asarray(result.std_expect[0]) / np.sqrt(result.num_trajectories)
ax.plot(result.times, mean)
ax.fill_between(
    result.times,
    mean - 1.96 * standard_error,
    mean + 1.96 * standard_error,
    alpha=0.25,
)

Confirm that the trajectory estimator and sample count justify the chosen interval; the formula above is only a simple independent-sample approximation.

Raw lines 251 to 285
286## Correlation and spectrum plots
288Plot complex correlations deliberately:
290```python
291fig, axes = plt.subplots(2, 1, sharex=True)
292axes[0].plot(taulist, np.real(correlation), label="real")
293axes[1].plot(taulist, np.imag(correlation), label="imaginary")
294axes[1].set_xlabel("delay")
295for ax in axes:
296 ax.legend()
297```
299For spectra:
301- label angular frequency and units;
302- show negative frequencies when physically meaningful;
303- disclose windowing, smoothing, and zero-padding;
304- avoid a logarithmic y-axis when values can be negative;
305- include frequency resolution and convergence information in the caption.

Correlation and spectrum plots

Plot complex correlations deliberately:

fig, axes = plt.subplots(2, 1, sharex=True)
axes[0].plot(taulist, np.real(correlation), label="real")
axes[1].plot(taulist, np.imag(correlation), label="imaginary")
axes[1].set_xlabel("delay")
for ax in axes:
    ax.legend()

For spectra:

  • label angular frequency and units;
  • show negative frequencies when physically meaningful;
  • disclose windowing, smoothing, and zero-padding;
  • avoid a logarithmic y-axis when values can be negative;
  • include frequency resolution and convergence information in the caption.
Raw lines 286 to 306
307## Animations
309Animations can conceal nonconvergence and are expensive to render. First
310produce static frames at physically meaningful times. If animation is needed:
312- cap frame count and resolution;
313- keep phase-space color limits fixed across frames;
314- avoid recomputing solver dynamics inside the frame callback;
315- save to a user-selected local path;
316- record the time-to-frame mapping.
318QuTiP 5 includes animation helpers in its visualization API, but their inputs
319still require stored states and memory planning.

Animations

Animations can conceal nonconvergence and are expensive to render. First produce static frames at physically meaningful times. If animation is needed:

  • cap frame count and resolution;
  • keep phase-space color limits fixed across frames;
  • avoid recomputing solver dynamics inside the frame callback;
  • save to a user-selected local path;
  • record the time-to-frame mapping.

QuTiP 5 includes animation helpers in its visualization API, but their inputs still require stored states and memory planning.

Raw lines 307 to 320
321## Figure export
323```python
324fig.savefig("phase_space.svg", bbox_inches="tight")
325fig.savefig("phase_space.png", dpi=300, bbox_inches="tight")
326```
328Use an explicit local output path, avoid overwriting without user intent, and
329save the numeric data/configuration next to the figure. A raster image alone is
330not a reproducible result.

Figure export

fig.savefig("phase_space.svg", bbox_inches="tight")
fig.savefig("phase_space.png", dpi=300, bbox_inches="tight")

Use an explicit local output path, avoid overwriting without user intent, and save the numeric data/configuration next to the figure. A raster image alone is not a reproducible result.

Raw lines 321 to 331
332## Sources (verified 2026-10-01)
334- [Visualization and animation API](https://qutip.readthedocs.io/en/stable/apidoc/visualization.html)
335- [Wigner and Q-function API](https://qutip.readthedocs.io/en/stable/apidoc/visualization.html#pseudoprobability-functions)
336- [Bloch sphere guide](https://qutip.readthedocs.io/en/stable/guide/guide-bloch.html)
337- [QuTiP 5.3.1 release notes](https://github.com/qutip/qutip/releases/tag/v5.3.1)
338- [Official QuTiP version-5 tutorials](https://github.com/qutip/qutip-tutorials/tree/main/tutorials-v5)