k-dense-ai/qutip
Simulate and audit closed and open quantum-system models with QuTiP 5, including deterministic, trajectory, steady-state, spectral, and phase-space workflows. Use for local quantum-dynamics work where physical assumptions, dimensions, and numerical convergence must be explicit.
npx skills add https://github.com/K-Dense-AI/scientific-agent-skills --skill qutip
Use QuTiP for finite-dimensional quantum mechanics, quantum optics, Lindblad
dynamics, trajectories, weak-coupling Bloch-Redfield models, and specialized
Floquet, HEOM, and permutational-invariance methods. It is not a hardware
execution SDK. Circuit and control functionality moved to separate QuTiP family
packages.
This skill targets QuTiP 5.3.0, released 2026-05-22. QuTiP 5.3 requires
Python 3.11 or newer. Its required distributions are NumPy (>=1.23.2), SciPy
(>=1.9.2, excluding 1.16.0 and 1.17.0), and packaging.
Create a dedicated environment and pin every direct distribution:
uv venv --python 3.11
uv pip install "qutip==5.3.0"
For plots:
uv pip install "qutip[graphics]==5.3.0"
Optional QuTiP family packages are independently versioned:
uv pip install "qutip-qip==0.4.2"
uv pip install "qutip-qtrl==0.2.0"
uv pip install "qutip-jax==0.1.1"
qutip-qip 0.4.2 (2026-06-23) is the production/stable circuit, gate, andnoisy-device simulation package. Import from qutip_qip, not qutip.qip.
qutip-qtrl 0.2.0 (2026-06-23) provides GRAPE and CRAB **quantum optimalcontrol**. It is not a trajectory viewer. Import from qutip_qtrl, not
qutip.control; PyPI still classifies it pre-alpha.
qutip-jax 0.1.1 (2025-05-29) is the official JAX data backend for GPU andautomatic-differentiation experiments. It is explicitly pre-alpha.
qutip-cupy is an official QuTiP-organization repository, but it has no PyPIrelease and its own README says it is not officially released. Do not put an
unreleased Git install into a reproducible workflow.
Use a project lockfile or a hash-generating uv pip compile workflow when
transitive dependency identity must also be frozen.
Before solving, record:
Hamiltonian entries are angular frequencies and rates have reciprocal-time
units. Convert cyclic frequency with \(2\pi f\); never mix Hz and rad/s.
tensor(A, B, C) fixes subsystem indices 0, 1, 2.Preserve that order in every state, operator, collapse channel, and partial
trace. obj.ptrace([0, 2]) keeps those subsystems; it does not trace them.
and eigenvalues above a stated negative tolerance. Tiny negative values may
be numerical; material negativity invalidates a claimed state.
gamma is representedby sqrt(gamma) * A, not gamma * A. Define what each rate measures. For
example, sqrt(gamma_phi / 2) * sigmaz() gives coherence decay
exp(-gamma_phi * t).
bath-equilibrium, truncation, symmetry, and initial-factorization assumptions
wherever used.
tolerances, trajectory count, and random seeds. Report result.stats.
window and spacing, ODE tolerances, trajectories, Floquet harmonics, HEOM
depth and bath exponents, or PIQS representation as applicable.
Prefer explicit imports and inspect both shape and structured dimensions:
from qutip import basis, qeye, sigmaz, tensor
psi = tensor(basis(2, 0), basis(3, 1))
z_on_first = tensor(sigmaz(), qeye(3))
assert psi.shape == (6, 1)
assert psi.dims == [[2, 3], [1]]
assert z_on_first.dims == [[2, 3], [2, 3]]
rho_first = psi.proj().ptrace(0) # keep subsystem 0
Matrix shape alone is insufficient: two objects can both be 6-by-6 but encode
different tensor factorizations. Read references/core_concepts.md before
building composite, superoperator, or channel models.
| Model | Current API | Required justification |
|---|---|---|
| Closed, pure, unitary | sesolve | Hermitian Hamiltonian; no dissipation |
| Lindblad/open or mixed | mesolve | Markovian completely positive model and channel rates |
| Quantum jumps | mcsolve | Unravelling, trajectory convergence, seeds |
| Microscopic weak bath | brmesolve | Born-Markov/weak coupling, spectra, secular choice |
| Diffusive measurement | ssesolve, smesolve | monitored versus unmonitored channels |
| Periodic drive | FloquetBasis, fsesolve, fmmesolve | verified period and Floquet convergence |
| Structured non-Markovian bath | qutip.solver.heom | bath expansion and hierarchy convergence |
| Symmetric spin ensemble | qutip.piqs | permutation symmetry and basis choice |
Do not select a more specialized solver merely because it exists.
QuTiP 5.3 uses ordinary option dictionaries. Solver controls, e_ops, and
args are keyword-only; the old mutable options object is gone.
import numpy as np
from qutip import basis, mesolve, sigmam, sigmaz
omega = 2.0
gamma = 0.15
tlist = np.linspace(0.0, 20.0, 401)
excited = basis(2, 0)
result = mesolve(
0.5 * omega * sigmaz(),
excited,
tlist,
c_ops=[np.sqrt(gamma) * sigmam()],
e_ops={"sigma_z": sigmaz(), "excited": excited.proj()},
options={
"method": "adams",
"atol": 1e-10,
"rtol": 1e-8,
"store_final_state": True,
"progress_bar": "",
},
)
population = np.asarray(result.e_data["excited"])
assert np.max(np.abs(population - np.exp(-gamma * tlist))) < 2e-6
assert isinstance(result.stats, dict)
If the problem is stiff, compare bdf or lsoda; do not change an integrator
without rerunning tolerance and invariant checks. QuTiP 5.3 also supports
options={"matrix_form": True} in mesolve; benchmark and validate it before
using it as a default.
Prefer trusted Pythonic callables or numeric coefficient arrays. Do not create
coefficient source strings from user input.
import numpy as np
from qutip import QobjEvo, sigmax, sigmaz
def envelope(t, amplitude, center, width):
return amplitude * np.exp(-0.5 * ((t - center) / width) ** 2)
H = QobjEvo(
[0.5 * sigmaz(), [sigmax(), envelope]],
args={"amplitude": 0.2, "center": 5.0, "width": 1.0},
)
instantaneous_H = H(5.0)
H.arguments(amplitude=0.1)
The older f(t, args) coefficient signature is deprecated in 5.3 and is
scheduled for removal in 5.5. See references/time_evolution.md.
import numpy as np
from qutip import basis, mcsolve, sigmam, sigmaz
tlist = np.linspace(0.0, 10.0, 201)
result = mcsolve(
0.5 * sigmaz(),
basis(2, 0),
tlist,
[np.sqrt(0.2) * sigmam()],
e_ops=[basis(2, 0).proj()],
ntraj=400,
seeds=20260723,
options={"keep_runs_results": False, "progress_bar": ""},
)
Report ntraj, result.seeds, uncertainty or repeated-seed sensitivity, and
whether individual runs were retained. Reuse seeds=previous_result.seeds only
when paired trajectories are intentional. ssesolve and smesolve use the
boolean heterodyne argument, not legacy integer noise codes.
import numpy as np
from qutip import QFunc, liouvillian, operator_to_vector, qfunc, steadystate
rho_ss = steadystate(H, c_ops, method="direct")
residual = (liouvillian(H, c_ops) * operator_to_vector(rho_ss)).norm()
assert residual < 1e-9
xvec = np.linspace(-5.0, 5.0, 151)
Q_once = qfunc(rho_ss, xvec, xvec)
q_many = QFunc(xvec, xvec)
Q_again = q_many(rho_ss)
assert Q_once.shape == (len(xvec), len(xvec))
For wigner, qfunc, and QFunc, array element [j, k] corresponds to
yvec[j], xvec[k]. In QuTiP 5.3, QFunc is initialized with fixed
coordinates and called with a state; it has no .eval method. This skill never
uses Python dynamic-code execution. Prefer plot_wigner, Result.plot_expect,
or explicit Matplotlib axes as documented in references/visualization.md.
Direct spectrum is a stationary steady-state spectrum. An FFT of a finite
correlation requires explicit checks for tail decay, timestep aliasing,
frequency resolution, window sensitivity, and transform convention. See
references/analysis.md.
qutip.solver.heom; the legacy QuTiP 4 nonmarkov HEOMnamespace is stale.
FloquetBasis for modes and quasi-energies. VerifyH(t + T) == H(t) numerically and sweep basis/truncation choices.
from qutip import piqs. Dicke.pisolve is only theoptimized diagonal-state/diagonal-Hamiltonian route; general Dicke-basis
dynamics use the Liouvillian with mesolve.
brmesolve can violate positivity, especially without secularization. Checkdensity-matrix eigenvalues over time.
simulation as quantum-hardware execution.
See references/advanced.md for HEOM, Floquet, PIQS, stochastic, and extension
boundaries.
All bundled tools are local-only, emit strict JSON, reject non-finite JSON and
unknown keys, and never load pickle files or executable model code. Simulation
imports are lazy, so every --help works without QuTiP installed.
| Script | Purpose |
|---|---|
| scripts/qobj_model_validator.py | Validate bounded Qobj model JSON, dimensions, states, rates, and role compatibility |
| scripts/two_level_simulation.py | Run a bounded two-level Lindblad or jump simulation |
| scripts/solver_config_planner.py | Select a current solver and option/checklist plan |
| scripts/convergence_sweep.py | Sweep tolerances/grid size or trajectory count on a synthetic model |
| scripts/result_audit.py | Audit JSON output without deserializing Python objects |
| scripts/steady_state_spectrum_planner.py | Plan bounded steady-state and direct/FFT spectral checks |
Example:
python skills/qutip/scripts/two_level_simulation.py --help
python skills/qutip/scripts/two_level_simulation.py \
--decay-rate 0.2 --t-final 10 --time-points 201 \
--output two-level.json
python skills/qutip/scripts/result_audit.py two-level.json
assumptions.
untrusted QuTiP object/result files because object serialization can execute
code.
references/core_concepts.md — Qobj, dimensions, tensor products, states,channels, and unit conventions
references/time_evolution.md — current solver signatures, options, results,QobjEvo, trajectories, and numerical controls
references/analysis.md — physical-state audits, steady states,correlations, spectra, and convergence
references/visualization.md — Wigner, Q functions, QFunc, Bloch, result,and matrix plots
references/advanced.md — Bloch-Redfield, stochastic, Floquet, HEOM, PIQS,and QuTiP family package boundaries
Verified 2026-07-23:
Take k-dense-ai/qutip from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip, uv.
Without those the skill loads but fails at the first command.