Plotting
maxwellbloch.plot provides Plotly-based visualisation primitives for MBSolve and OBSolve results. Figures are interactive in the browser — hover to read values, drag to zoom, click the legend to toggle traces.
Installation
The plotting module requires the optional [plot] dependencies:
pip install maxwellbloch[plot]
or with uv:
uv pip install maxwellbloch[plot]
kaleido is included in [plot] and is required for static PNG export (fig.write_image()). If you only need interactive figures in a notebook you can install plotly alone.
Usage pattern
All plot functions take a solved MBSolve instance as their first argument and return a plotly.graph_objects.Figure. They never call .show() internally — that is always your call:
from maxwellbloch import plot
fig = plot.field_spacetime(mbs) # returns a Figure
fig.show(renderer='notebook_connected') # interactive in Jupyter
fig.write_image('output.png') # static PNG via kaleido
Setup: solve a two-level absorber
We use a weak Gaussian probe propagating through a two-level absorber. This is enough to demonstrate every primitive.
[1]:
from maxwellbloch import mb_solve, plot
mb_solve_json = """
{
"atom": {
"num_states": 2,
"decays": [{"channels": [[0, 1]], "rate": 1.0}],
"fields": [
{
"label": "probe",
"coupled_levels": [[0, 1]],
"rabi_freq": 0.01,
"rabi_freq_t_func": "gaussian",
"rabi_freq_t_args": {"ampl": 1.0, "centre": 0.0, "fwhm": 1.0}
}
]
},
"t_min": -3.0,
"t_max": 6.0,
"t_steps": 120,
"z_min": 0.0,
"z_max": 1.0,
"z_steps": 20,
"z_steps_inner": 2,
"interaction_strengths": [2.0],
"savefile": "plotting-two-level"
}
"""
mbs = mb_solve.MBSolve.from_json_str(mb_solve_json)
Omegas_zt, states_zt = mbs.mbsolve()
100%|██████████| 20/20 [00:00<00:00, 64.33z/s, Ω_max=0.0155]
Saving MBSolve to plotting-two-level.qu
field_spacetime — space-time heatmap of |Ω(z, t)|
The most common view: the full propagation in one figure. Hover over any pixel to read the exact (z, t, |Ω|) values.
[2]:
fig = plot.field_spacetime(mbs)
fig.show(renderer='notebook_connected')
field_envelope — pulse shape at fixed z
Compare the input pulse (z = z_min) to the output pulse (z = z_max). The probe is absorbed as it propagates through the medium.
[3]:
fig = plot.field_envelope(mbs, z_indices=[0, -1])
fig.show(renderer='notebook_connected')
field_z_profile_anim — animated spatial profile
Watch the pulse propagate through the medium. Each frame shows the field envelope \(|\Omega(z)|\) at a single instant. Use the slider to scrub to a specific time, or press ▶ Play to animate.
[4]:
fig = plot.field_z_profile_anim(mbs)
fig.show(renderer='notebook_connected')
pulse_area — area theorem
The pulse area \(\mathcal{A} = \int |\Omega(z,t)|\, dt / \pi\) decreases from the input value as the probe is absorbed.
[5]:
fig = plot.pulse_area(mbs)
fig.show(renderer='notebook_connected')
spectrum — frequency-domain absorption
Computes the absorption spectrum via Fourier transform of the time-domain field at z_idx (default: exit face). This works in the linear (weak-field) regime; for strong fields the result is the nonlinear transmission, not the susceptibility.
Key options:
freq_range— clip the displayed frequency axis to|f| ≤ freq_range(γ), hiding the noisy high-frequency wings.show_dispersion=True— add the dispersive component on a secondary axis.freq_scale="arcsinh"— compress the frequency axis nonlinearly (linear near zero, log-like at large |f|) so narrow and broad features coexist.arcsinh_scalesets the width of the linear region in γ.window— apply a SciPy window (e.g."hann") to the time-domain field before the FFT to reduce spectral leakage from truncated free-induction decay.
[6]:
# Narrow window around the resonance with dispersion on secondary axis
fig = plot.spectrum(mbs, freq_range=3.0, show_dispersion=True)
fig.show(renderer='notebook_connected')
[7]:
# Arcsinh frequency scale (linear ±1 γ, log-like beyond) with Hann windowing
# to reduce leakage from the free-induction decay tail
fig = plot.spectrum(mbs, freq_scale="arcsinh", arcsinh_scale=1.0, window="hann")
fig.show(renderer='notebook_connected')
population — state populations vs time
The diagonal elements \(\rho_{ii}(t)\) of the density matrix at a fixed z position. The probe drives population from |0⟩ to |1⟩ and back.
[8]:
fig = plot.population(mbs, z_idx=0)
fig.show(renderer='notebook_connected')
population_spacetime — population heatmap
Like field_spacetime but for a density-matrix diagonal element. Useful for visualising population inversion, storage, and retrieval.
[9]:
fig = plot.population_spacetime(mbs, state_idx=1)
fig.show(renderer='notebook_connected')
coherence — off-diagonal density matrix elements
Plot \(|\rho_{01}(t)|\), \(\mathrm{Re}(\rho_{01})\), or \(\mathrm{Im}(\rho_{01})\) at a fixed z. The coherence is proportional to the macroscopic polarisation of the medium and drives the field evolution.
[10]:
fig = plot.coherence(mbs, 0, 1, z_idx=0, component='abs')
fig.show(renderer='notebook_connected')
Static PNG export
Any figure can be saved as a static PNG using fig.write_image(). This requires kaleido (included in maxwellbloch[plot]).
fig = plot.field_spacetime(mbs)
fig.write_image('field_spacetime.png', scale=2) # scale=2 for high-DPI
SVG and PDF are also supported:
fig.write_image('field_spacetime.svg')
fig.write_image('field_spacetime.pdf')