{ "cells": [ { "cell_type": "markdown", "id": "intro", "metadata": {}, "source": [ "# Plotting\n", "\n", "`maxwellbloch.plot` provides Plotly-based visualisation primitives for\n", "MBSolve and OBSolve results. Figures are interactive in the browser —\n", "hover to read values, drag to zoom, click the legend to toggle traces.\n", "\n", "## Installation\n", "\n", "The plotting module requires the optional `[plot]` dependencies:\n", "\n", "```bash\n", "pip install maxwellbloch[plot]\n", "```\n", "\n", "or with uv:\n", "\n", "```bash\n", "uv pip install maxwellbloch[plot]\n", "```\n", "\n", "`kaleido` is included in `[plot]` and is required for static PNG export\n", "(`fig.write_image()`). If you only need interactive figures in a notebook\n", "you can install `plotly` alone.\n", "\n", "## Usage pattern\n", "\n", "All plot functions take a solved `MBSolve` instance as their first argument\n", "and return a `plotly.graph_objects.Figure`. They never call `.show()`\n", "internally — that is always your call:\n", "\n", "```python\n", "from maxwellbloch import plot\n", "\n", "fig = plot.field_spacetime(mbs) # returns a Figure\n", "fig.show(renderer='notebook_connected') # interactive in Jupyter\n", "fig.write_image('output.png') # static PNG via kaleido\n", "```" ] }, { "cell_type": "markdown", "id": "setup-header", "metadata": {}, "source": [ "## Setup: solve a two-level absorber\n", "\n", "We use a weak Gaussian probe propagating through a two-level absorber.\n", "This is enough to demonstrate every primitive." ] }, { "cell_type": "code", "execution_count": null, "id": "setup", "metadata": {}, "outputs": [], "source": [ "from maxwellbloch import mb_solve, plot\n", "\n", "mb_solve_json = \"\"\"\n", "{\n", " \"atom\": {\n", " \"num_states\": 2,\n", " \"decays\": [{\"channels\": [[0, 1]], \"rate\": 1.0}],\n", " \"fields\": [\n", " {\n", " \"label\": \"probe\",\n", " \"coupled_levels\": [[0, 1]],\n", " \"rabi_freq\": 0.01,\n", " \"rabi_freq_t_func\": \"gaussian\",\n", " \"rabi_freq_t_args\": {\"ampl\": 1.0, \"centre\": 0.0, \"fwhm\": 1.0}\n", " }\n", " ]\n", " },\n", " \"t_min\": -3.0,\n", " \"t_max\": 6.0,\n", " \"t_steps\": 120,\n", " \"z_min\": 0.0,\n", " \"z_max\": 1.0,\n", " \"z_steps\": 20,\n", " \"z_steps_inner\": 2,\n", " \"interaction_strengths\": [2.0],\n", " \"savefile\": \"plotting-two-level\"\n", "}\n", "\"\"\"\n", "\n", "mbs = mb_solve.MBSolve.from_json_str(mb_solve_json)\n", "Omegas_zt, states_zt = mbs.mbsolve()" ] }, { "cell_type": "markdown", "id": "spacetime-header", "metadata": {}, "source": [ "## `field_spacetime` — space-time heatmap of |Ω(z, t)|\n", "\n", "The most common view: the full propagation in one figure.\n", "Hover over any pixel to read the exact (z, t, |Ω|) values." ] }, { "cell_type": "code", "execution_count": null, "id": "spacetime", "metadata": {}, "outputs": [], "source": [ "fig = plot.field_spacetime(mbs)\n", "fig.show(renderer='notebook_connected')" ] }, { "cell_type": "markdown", "id": "envelope-header", "metadata": {}, "source": [ "## `field_envelope` — pulse shape at fixed z\n", "\n", "Compare the input pulse (z = z_min) to the output pulse (z = z_max).\n", "The probe is absorbed as it propagates through the medium." ] }, { "cell_type": "code", "execution_count": null, "id": "envelope", "metadata": {}, "outputs": [], "source": [ "fig = plot.field_envelope(mbs, z_indices=[0, -1])\n", "fig.show(renderer='notebook_connected')" ] }, { "cell_type": "markdown", "id": "pulse-area-header", "metadata": {}, "source": [ "## `pulse_area` — area theorem\n", "\n", "The pulse area $\\mathcal{A} = \\int |\\Omega(z,t)|\\, dt / \\pi$ decreases\n", "from the input value as the probe is absorbed." ] }, { "cell_type": "code", "execution_count": null, "id": "pulse-area", "metadata": {}, "outputs": [], "source": [ "fig = plot.pulse_area(mbs)\n", "fig.show(renderer='notebook_connected')" ] }, { "cell_type": "markdown", "id": "spectrum-header", "metadata": {}, "source": [ "## `spectrum` — frequency-domain absorption\n", "\n", "Computes the absorption spectrum via Fourier transform of the time-domain\n", "field. This works in the linear (weak-field) regime: for strong fields the\n", "result is the nonlinear transmission, not the susceptibility.\n", "\n", "Pass `show_dispersion=True` to add the dispersive component on a secondary\n", "axis." ] }, { "cell_type": "code", "execution_count": null, "id": "spectrum", "metadata": {}, "outputs": [], "source": [ "fig = plot.spectrum(mbs, show_dispersion=True)\n", "fig.show(renderer='notebook_connected')" ] }, { "cell_type": "markdown", "id": "population-header", "metadata": {}, "source": [ "## `population` — state populations vs time\n", "\n", "The diagonal elements $\\rho_{ii}(t)$ of the density matrix at a fixed\n", "z position. The probe drives population from |0⟩ to |1⟩ and back." ] }, { "cell_type": "code", "execution_count": null, "id": "population", "metadata": {}, "outputs": [], "source": [ "fig = plot.population(mbs, z_idx=0)\n", "fig.show(renderer='notebook_connected')" ] }, { "cell_type": "markdown", "id": "pop-spacetime-header", "metadata": {}, "source": [ "## `population_spacetime` — population heatmap\n", "\n", "Like `field_spacetime` but for a density-matrix diagonal element.\n", "Useful for visualising population inversion, storage, and retrieval." ] }, { "cell_type": "code", "execution_count": null, "id": "pop-spacetime", "metadata": {}, "outputs": [], "source": [ "fig = plot.population_spacetime(mbs, state_idx=1)\n", "fig.show(renderer='notebook_connected')" ] }, { "cell_type": "markdown", "id": "coherence-header", "metadata": {}, "source": [ "## `coherence` — off-diagonal density matrix elements\n", "\n", "Plot $|\\rho_{01}(t)|$, $\\mathrm{Re}(\\rho_{01})$, or $\\mathrm{Im}(\\rho_{01})$\n", "at a fixed z. The coherence is proportional to the macroscopic polarisation\n", "of the medium and drives the field evolution." ] }, { "cell_type": "code", "execution_count": null, "id": "coherence", "metadata": {}, "outputs": [], "source": [ "fig = plot.coherence(mbs, 0, 1, z_idx=0, component='abs')\n", "fig.show(renderer='notebook_connected')" ] }, { "cell_type": "markdown", "id": "static-export-header", "metadata": {}, "source": [ "## Static PNG export\n", "\n", "Any figure can be saved as a static PNG using `fig.write_image()`. This\n", "requires `kaleido` (included in `maxwellbloch[plot]`).\n", "\n", "```python\n", "fig = plot.field_spacetime(mbs)\n", "fig.write_image('field_spacetime.png', scale=2) # scale=2 for high-DPI\n", "```\n", "\n", "SVG and PDF are also supported:\n", "\n", "```python\n", "fig.write_image('field_spacetime.svg')\n", "fig.write_image('field_spacetime.pdf')\n", "```" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.14.0" } }, "nbformat": 4, "nbformat_minor": 5 }