Stochastic bundling of Lindblad operators for QuTiP
Project description
qutip-bundling
Stochastic bundling of Lindblad operators for QuTiP.
Implements the stochastically bundled dissipator of
S. Adhikari and R. Baer, Stochastically Bundled Dissipators for the Quantum Master Equation, J. Chem. Theory Comput. 2025, 21, 4142–4150. https://doi.org/10.1021/acs.jctc.5c00145
It is an independent package that builds on QuTiP — install alongside QuTiP and use with the solvers you already know.
What it does
A Lindblad master equation with a large number N_L of collapse operators is
expensive: the dissipator costs one matrix product per operator, and N_L
typically grows as the square of the Hilbert-space dimension.
The dissipator is quadratic in the operators, so it can be reproduced in
expectation by a much smaller set of M randomly bundled operators:
R_m = (1 / sqrt(M)) * sum_alpha r_{m,alpha} * c_alpha
where the r_{m,alpha} are independent random numbers with zero mean and unit
second moment. The bundled dissipator is an unbiased estimator of the full
one for any M, and the bundled operators still have Lindblad form, so the
dynamics stay completely positive and trace preserving.
Pair that with the deterministic Davies Lamb-shift Hamiltonian (built from
the bare L_alpha, never bundled), and a single bundled mesolve call
reproduces full master-equation dynamics with M independent of the
Hilbert-space dimension — per-step cost drops from O(N^5) to O(N^3).
Install
pip install qutip-bundling # once published to PyPI
# or, from a checkout:
pip install -e ".[examples,test]"
API: three operator transforms plus optional solver helpers
The easiest entry point is :func:davies_operators, which builds the
collapse operators directly from a Hamiltonian H and a system-bath
coupling operator X, with the correct sign convention baked in (see
CONVENTIONS.md -- getting the sign wrong silently runs the dynamics
backwards):
from qutip_bundling import davies_operators, bundle, rk4_mesolve
c_ops = davies_operators(H, X, gamma) # gamma(omega) callable
R = bundle(c_ops, M=8, rng=0) # M bundled operators
res = rk4_mesolve(H, rho0, tlist, c_ops=R, e_ops=e_ops)
Under the hood the method is three pure operator transforms that return operators, not solver results:
| Function | Purpose |
|---|---|
davies_operators(H, X, gamma) |
recommended for Davies/Bohr setups -- build collapse operators from H and coupling op X, correct sign baked in |
build_collapse_ops(L_ops, omegas, gamma) |
bare L_alpha and the spectral function → standard collapse operators sqrt(gamma) * L_alpha |
bundle(c_ops, M, ...) |
the method -- any collapse-operator list → M randomly bundled operators |
lamb_shift_hamiltonian(L_ops, omegas, imag_gamma) |
bare L_alpha and Im Gamma → Lamb-shift Hamiltonian H_LS = sum_alpha imag_gamma * L^dag L (built once) |
Spectral inputs (gamma, imag_gamma) may be either a callable
f(omega) -> float or an array aligned with omegas.
Bringing your own operators (bundling is not tied to Davies)
bundle is the actual method, and it takes any list of collapse
operators — it does not care how they were built. The bundling is a purely
algebraic operation: it replaces N_L operators {c_alpha} with M random
linear combinations
R_m = (1 / sqrt(M)) * sum_alpha r_{m,alpha} * c_alpha,
so that the bundled dissipator sum_m D[R_m] equals the full dissipator
sum_alpha D[c_alpha] in expectation. Nothing in that statement assumes the
Davies construction, an ohmic bath, or even a master equation derived from a
microscopic model.
So if your collapse operators come from somewhere else — a Redfield equation
mapped to Lindblad form, a phenomenological model, quantum-optics jump
operators, local Lindbladians on a lattice, or operators that already include
their rates — you skip davies_operators and build_collapse_ops entirely
and bundle them directly:
from qutip_bundling import bundle, mesolve_ensemble
my_c_ops = [...] # any collapse operators you like
R = bundle(my_c_ops, M=8, rng=0)
res = qutip.mesolve(H, rho0, tlist, c_ops=R, e_ops=e_ops)
# or with averaging + error bars:
ens = mesolve_ensemble(H, rho0, tlist, my_c_ops, M=8,
e_ops=e_ops, n_realizations=32)
davies_operators is provided because the Davies/Bohr construction is the
common case and the sign convention is easy to get wrong — but it is one
optional way to produce an operator list, not a requirement of the method.
Any bath spectral function
When you do use davies_operators or build_collapse_ops, the spectral
function gamma(omega) is arbitrary — the library never assumes a particular
form (the ohmic example in the demos and tests is just an example). Pass any
callable or array. For physically sensible relaxation to the Gibbs state it
should be non-negative and satisfy detailed balance
gamma(-omega)/gamma(omega) = exp(-omega/kT) (see CONVENTIONS.md). For
instance a Drude–Lorentz bath:
def gamma_drude(omega, lam=0.5, gamma_c=2.0, kT=1.0):
# spectral density J(omega) = 2*lam*gamma_c*omega / (omega**2 + gamma_c**2)
if abs(omega) < 1e-12:
return 2.0 * lam * kT / gamma_c # classical/zero-frequency limit
J = 2.0 * lam * gamma_c * omega / (omega**2 + gamma_c**2)
return J / (1.0 - math.exp(-omega / kT)) # detailed-balance weighting
c_ops = davies_operators(H, X, gamma_drude)
A microscopically-derived construction may not even factor into
operators x gamma(omega) — some methods hand you collapse operators with the
rates already included and no separate spectral function. Those just go
straight into bundle.
A convenience composes the three for the common case:
from qutip_bundling import prepare_bundled_dynamics
import qutip
bundled = prepare_bundled_dynamics(
L_ops, omegas, gamma=gamma, imag_gamma=imag_gamma, M=8, rng=0,
)
result = qutip.mesolve(
H_sys + bundled.H_lamb_shift, rho0, tlist,
c_ops=bundled.c_ops, e_ops=e_ops,
)
For ensemble averaging with error bars:
from qutip_bundling import mesolve_ensemble
ens = mesolve_ensemble(H_total, rho0, tlist, c_ops_full,
M=8, e_ops=e_ops, n_realizations=32)
mean = ens.expect[0] # mean of observable 0 over realizations
error_bar = ens.sem[0] # standard error of the mean (shrinks as 1/sqrt(n))
spread = ens.std[0] # standard deviation across trajectories (single-run noise)
raw = ens.samples # shape (n_realizations, n_e_ops, n_times)
# Everything else can be recomputed from raw -- your own fluctuation
# definition, percentiles, histograms -- since no information is discarded.
Advanced: bias reduction with the jackknife
A finite bundle size M introduces an O(1/M) bias (eq. 14 of the paper).
You can usually control it just by increasing M or inspecting ens.samples
directly. When you specifically want to cancel the leading bias term at fixed
M, the package provides the jackknife-2 estimator (eqs. 15–16):
from qutip_bundling import mesolve_jackknife
jack = mesolve_jackknife(H_total, rho0, tlist, c_ops_full,
M=8, e_ops=e_ops, n_realizations=32)
# jack.expect[0] is bias-corrected; jack.extra["direct"] is the uncorrected mean
This is an optimization, not a required step — the core method is bundle → solve → average.
How the pieces fit together
Bundling is a Monte Carlo trick for the dissipator only. The Davies
Lamb-shift Hamiltonian H_LS is a deterministic part of the renormalized
Hamiltonian (eq. 4 of the paper) — it's built once from the bare L_alpha,
added to the system Hamiltonian, and never enters the bundling step. If
imag_gamma is identically zero (the paper's test cases), H_LS = 0 and
the package reduces to the dissipator-only case.
Because the API returns operators rather than wrapping a solver, the same
building blocks work with qutip.mesolve, mcsolve, smesolve, or any
custom propagator.
Tutorial
A step-by-step Jupyter notebook is in examples/tutorial.ipynb. It builds a
system, bundles its operators, shows convergence to the deterministic answer
as M grows, shows the standard deviation vs standard error, and uses the
native backend. Every cell is executed and its output checked.
Validation
examples/oscillator_demo.py builds an open system with many Davies/Bohr
Lindblad operators and a non-zero Lamb shift, and verifies all three
guarantees:
- The bundled dissipator is an unbiased estimator of the full one (error
decays as
1/sqrt(samples)). - The Lamb shift is deterministic, Hermitian, and unchanged by any bundling call.
- A bundled
mesolvewithM = 8plusH_LSreproduces the dynamics of the full operator set within the dynamic range of the observables.
Acknowledgments
Developed in the group of Prof. Roi Baer at the Fritz Haber Center for Molecular Dynamics, Institute of Chemistry, The Hebrew University of Jerusalem.
License
BSD-3-Clause.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qutip_bundling-0.5.0.tar.gz.
File metadata
- Download URL: qutip_bundling-0.5.0.tar.gz
- Upload date:
- Size: 25.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3fd9cd6e0a9dee9818b8b12d723c001e4c7cdf1abcfdf48054cc5186843c328b
|
|
| MD5 |
703e05d868af7282d3e2cb76100b8327
|
|
| BLAKE2b-256 |
a8f28d3bf59e969dfe628a178cb6c6654448b26ad2a879dc08799e3fde2be2f1
|
File details
Details for the file qutip_bundling-0.5.0-py3-none-any.whl.
File metadata
- Download URL: qutip_bundling-0.5.0-py3-none-any.whl
- Upload date:
- Size: 19.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a55e1759efc239e2bcc73aa725a48eabe88cd27e039fd441994377bd88bd4683
|
|
| MD5 |
37b1849e83bbf6fd763472251c5054eb
|
|
| BLAKE2b-256 |
72673c86bc45a4a1ed0089dda05d0e4a2f661551db825dda5e90ddd9f6a369b5
|