skills/qutip/references/time_evolution.md
Research and API verification date: 2026-07-23. All signatures and examples
target qutip==5.3.0.
| Solver | State/model | Main uncertainty |
|---|---|---|
sesolve | Schrödinger equation, pure state, no collapse channels | ODE and model approximation |
mesolve | Lindblad master equation, mixed state, or Liouvillian | channel validity and ODE error |
mcsolve | quantum-jump trajectories | ODE error plus sampling error |
brmesolve | microscopic weak-coupling bath spectra | Born-Markov/secular approximation and positivity |
ssesolve | stochastic Schrödinger equation | diffusive record sampling |
smesolve | stochastic master equation with monitored/unmonitored channels | diffusive record sampling |
fsesolve/fmmesolve | periodic Floquet dynamics | period, basis, harmonic and bath approximations |
Do not use mcsolve merely to make a deterministic calculation parallel. Do
not use brmesolve as a generic replacement for a Lindblad model.
The important QuTiP 5.3 call boundaries are:
sesolve(H, psi0, tlist, *, e_ops=None, args=None, options=None)
mesolve(H, rho0, tlist, c_ops=None, *, e_ops=None, args=None, options=None)
mcsolve(H, state, tlist, c_ops=(), *, e_ops=None, ntraj=500,
args=None, options=None, seeds=None, target_tol=None, timeout=None)
brmesolve(H, psi0, tlist, a_ops=None, sec_cutoff=0.1, *,
c_ops=None, e_ops=None, args=None, options=None)
ssesolve(H, psi0, tlist, sc_ops=(), heterodyne=False, *,
e_ops=None, args=None, ntraj=500, options=None, seeds=None,
target_tol=None, timeout=None)
smesolve(H, rho0, tlist, c_ops=(), sc_ops=(), heterodyne=False, *,
e_ops=None, args=None, ntraj=500, options=None, seeds=None,
target_tol=None, timeout=None)
In 5.3, e_ops, args, and options are keyword-only. Passing solver options
as arbitrary solver keyword arguments was removed. The old qutip.Options
export is no longer present; pass an ordinary dictionary.
sesolve: closed unitary evolutionimport numpy as np
from qutip import basis, sesolve, sigmax, sigmaz
H = 0.5 * sigmax()
psi0 = basis(2, 0)
tlist = np.linspace(0.0, 10.0, 201)
result = sesolve(
H,
psi0,
tlist,
e_ops={"z": sigmaz()},
options={"atol": 1e-10, "rtol": 1e-8, "progress_bar": ""},
)
z = np.asarray(result.e_data["z"])
Audit Hamiltonian Hermiticity and state norm. If norm drift is material, tighten tolerances or investigate the model; do not normalize away unexplained error.
mesolve: Lindblad or explicit Liouvillian evolutionimport numpy as np
from qutip import basis, mesolve, sigmam, sigmaz
gamma = 0.2
excited = basis(2, 0)
tlist = np.linspace(0.0, 12.0, 241)
result = mesolve(
0.5 * sigmaz(),
excited,
tlist,
c_ops=[np.sqrt(gamma) * sigmam()],
e_ops={"p_excited": excited.proj()},
options={
"method": "adams",
"atol": 1e-10,
"rtol": 1e-8,
"store_final_state": True,
"progress_bar": "",
},
)
When no collapse operators are supplied and H is not a superoperator,
mesolve may defer to sesolve. If H is an explicit Liouvillian, document
whether it is in valid Lindblad form or represents a deliberate approximation.
mcsolve: quantum jumpsresult = mcsolve(
0.5 * sigmaz(),
excited,
tlist,
[np.sqrt(gamma) * sigmam()],
e_ops=[excited.proj()],
ntraj=500,
seeds=20260723,
options={
"keep_runs_results": False,
"progress_bar": "",
},
)
mean_population = result.expect[0]
trajectory_spread = result.std_expect[0]
seed_manifest = result.seeds
seeds may be one integer/SeedSequence used to spawn trajectory seeds, or a
list with one seed per trajectory. QuTiP stores the realized seeds in the
result. Reusing them enables a paired comparison, but paired runs are not
independent replicates.
Trajectory rigor:
ntraj and report stabilization or a target uncertainty;options={"keep_runs_results": True} only when individual trajectories
are actually required, because memory scales with trajectories and time
points;collapse; do not print unbounded records by default;target_tol may stop before ntraj, so report the number actually run;For mixed initial conditions, current QuTiP can sample the initial mixture; the result then includes initial-state accounting. State this extra source of sampling explicitly.
brmesolve: Bloch-Redfield evolutionimport numpy as np
from qutip import basis, brmesolve, sigmax, sigmaz
def one_sided_spectrum(w):
return 0.03 * w if w > 0.0 else 0.0
tlist = np.linspace(0.0, 30.0, 601)
result = brmesolve(
0.5 * sigmaz(),
basis(2, 0),
tlist,
a_ops=[(sigmax(), one_sided_spectrum)],
e_ops={"z": sigmaz()},
sec_cutoff=0.1,
options={"atol": 1e-10, "rtol": 1e-8, "progress_bar": ""},
)
The coupling operator in each a_ops pair is normally Hermitian, and the
spectrum is a function of angular frequency. Prefer a current QuTiP
Environment object when it represents the bath more clearly.
Required checks:
sec_cutoff=-1 disables secularization and can worsen
positivity;See advanced.md for the physical boundaries.
Use these for diffusive continuous-measurement unravellings, not jump trajectories:
result = smesolve(
H,
rho0,
tlist,
c_ops=unmonitored_channels,
sc_ops=monitored_channels,
heterodyne=False, # homodyne
e_ops={"signal": measured_quadrature},
ntraj=200,
seeds=20260723,
options={"dt": 0.001, "store_measurement": True, "progress_bar": ""},
)
c_ops are unmonitored deterministic dissipation channels.sc_ops are monitored stochastic channels.heterodyne=False is homodyne; True is heterodyne.noise selectors are not current API.dt separately from ntraj.import numpy as np
from qutip import QobjEvo, sigmax, sigmaz
def pulse(t, amplitude, center, width):
return amplitude * np.exp(-0.5 * ((t - center) / width) ** 2)
H = QobjEvo(
[0.5 * sigmaz(), [sigmax(), pulse]],
args={"amplitude": 0.2, "center": 5.0, "width": 1.0},
)
H_at_t = H(5.0)
H.arguments(amplitude=0.15)
The Pythonic form f(t, parameter, ...) is current. The legacy
f(t, args_dictionary) form is deprecated in 5.3 and scheduled for removal in
5.5.
coefficient_times = np.linspace(0.0, 10.0, 501)
samples = np.cos(coefficient_times)
H = QobjEvo(
[0.5 * sigmaz(), [sigmax(), samples]],
tlist=coefficient_times,
order=3,
)
Sample times must be sorted and match coefficient length. order=0 gives a
left/previous-value step function; the default cubic spline can overshoot.
Converge the coefficient sampling independently of solver output times.
QuTiP also accepts expression strings and may compile them, but this skill does not construct such expressions from configuration or user text. Use trusted callables or numeric arrays in generated workflows.
Common solver options:
options = {
"method": "adams",
"atol": 1e-10,
"rtol": 1e-8,
"nsteps": 10000,
"store_states": False,
"store_final_state": True,
"progress_bar": "",
}
Options are solver- and integrator-specific. Consult the selected solver's
.options documentation before using a key.
Common integration choices:
adams: non-stiff default for many deterministic solvers;bdf: stiff systems;lsoda: switches between non-stiff and stiff methods;dop853, vern7, or vern9;diag: diagonalization-based constant-system evolution;krylov: suitable cases where Krylov evolution is beneficial.An integrator label is not an accuracy certificate. Compare at least two tolerance levels, inspect warnings/stats, and compare a second method for a representative stiff or difficult case.
QuTiP 5.3 adds matrix_form for mesolve/MESolver:
options = {"matrix_form": True, "atol": 1e-10, "rtol": 1e-8}
It uses matrix-matrix products and may reduce memory in suitable models. Benchmark both time and verified observables before adopting it.
tlist is the requested output grid, not necessarily the integrator's internal
step sequence.
Checks:
For FFT spectra, a uniform taulist is required and its spacing/window set
Nyquist range and frequency resolution.
Current solver results can contain:
| Attribute | Meaning |
|---|---|
times | returned times |
states | stored states; may be empty depending on options and e_ops |
final_state | final state when requested |
expect | list aligned with list-form e_ops |
e_data | dictionary keyed like dict-form e_ops |
options | effective result/solver options |
solver | solver name |
stats | timing and diagnostic statistics |
Multi-trajectory results add trajectory counts, seeds, standard deviations,
average states, measurements, and optionally individual run results. Never
assume .states means individual trajectories; inspect
keep_runs_results and the concrete result type.
QuTiP 5.3 adds Result.plot_expect() and MultiTrajResult.plot_expect().
Prefer explicit axes in reusable code.
Solver classes (SESolver, MESolver, MCSolver, BRSolver, SMESolver,
and others) can reuse a constructed right-hand side across runs:
from qutip import MESolver
solver = MESolver(H, c_ops, options={"atol": 1e-10, "rtol": 1e-8})
first = solver.run(rho_a, tlist, e_ops=e_ops)
second = solver.run(rho_b, tlist, e_ops=e_ops)
Use the class interface when repeated setup is material, and verify that updated arguments and initial states are the intended ones.
Do not load untrusted serialized Python or QuTiP objects. For exchange and audit, save:
The bundled CLIs use bounded strict JSON and never pickle results.
../scripts/two_level_simulation.py: bounded mesolve/mcsolve example.../scripts/solver_config_planner.py: solver and option checklist.../scripts/convergence_sweep.py: deterministic or trajectory convergence.../scripts/result_audit.py: portable JSON audit.