quchip.backend.qutip¶
QuTiP backend implementation of the quchip.backend.protocol contract.
QuTiP stores operators as qutip.Qobj (sparse CSR by default, dense on
request). This backend lowers engine IR into Qobj / QobjEvo form and
dispatches the sesolve/mesolve drivers directly.
Batched sweeps are parallelized via the loky reusable process pool —
QuTiP solvers release the GIL only partially, so processes beat threads.
Final QobjEvo assembly happens inside the workers so the main process
never pays for it.
References
Johansson, Nation, Nori — QuTiP 2, Comput. Phys. Commun. 183, 1760 (2012)
Breuer & Petruccione — The Theory of Open Quantum Systems (OUP, 2002)
Classes
Concrete backend backed by QuTiP. |
- class quchip.backend.qutip.QuTiPBackend[source]¶
Bases:
BackendConcrete backend backed by QuTiP.
Operator=State=qutip.Qobj.Example
>>> from quchip.backend.qutip import QuTiPBackend >>> backend = QuTiPBackend() >>> a = backend.destroy(3) >>> float((a.dag() * a).diag()[2].real) 2.0
- destroy(n)[source]¶
Return the annihilation operator for an n-level Fock space (
qutip.destroy, memoized).
- from_array(data, dims=None)[source]¶
Construct a
Qobjfrom a dense matrix with optional row/col dims.
- to_canonical_operator(op)[source]¶
Serialize a
Qobjinto the backend-agnostic canonical IR (CSR or dense).
- eigenenergies(op)[source]¶
Return the ascending eigenvalues of a Hermitian operator (
Qobj.eigenenergies).
- eigensystem_data(op)[source]¶
Return ascending eigenvalues, eigenvector matrix, and primed eigenstate kets.
A
QobjusesQobj.eigenstatesverbatim to preserve the exact degenerate-subspace basis; dense inputs fall through to the protocol default.- Parameters:
op (Any)
- Return type:
- expect(op, state)[source]¶
Return the expectation value ⟨op⟩ for a ket or density matrix via
qutip.expect.
- ptrace(state, keep, dims)[source]¶
Reduce onto subsystem(s) keep via
Qobj.ptrace(partial trace).Rebuilds the composite dims when the incoming state carries flat dims.
- permute_state(state, dims, order)[source]¶
Reorder a
Qobj’s subsystems viaQobj.permute(sparse-friendly, keeps dims).Rebuilds composite
dimsfirst when the incoming state carries flat dims, mirroringptrace(). Non-Qobjinputs fall through to the protocol default.
- tensor(*operators)[source]¶
Return the tensor product of operators via
qutip.tensor(array-likes coerced first).
- embed_two_body(op_ab, index_a, index_b, dims)[source]¶
Embed a two-body operator on devices index_a ⊗ index_b into the full space.
Reorders subsystems (via sparse SWAP when
index_a > index_b) and identity-pads spectators without densifying the full matrix.
- coherent(n, alpha)[source]¶
Return the coherent state |α⟩ truncated to n Fock levels (
qutip.coherent).
- resolve_solver_options(options, *, metadata, tlist)[source]¶
Fill in
nstepsandmax_stepfrom Hamiltonian-metadata heuristics when unset.nstepsis an abort ceiling on the integrator’s total internal step count, not a step-size bound — seedefault_solver_steps()for the heuristic that derives it from the Hamiltonian’s fastest frequency scale.max_stepbounds the size of an individual adaptive step. Without it, QuTiP’s adaptive integrator can step clean over a finite-support pulse that sits inside a long idle span, sampling it only at points where its envelope happens to be near zero. When the user has not setmax_stepandmetadata["max_step_ns"]carries a concrete, positive, finite value — half the narrowest window across the Hamiltonian’s dynamic terms, computed by_solver_hint_metadata()— it is used as the ceiling. An explicit usermax_stepis always authoritative, including QuTiP’s ownmax_step=0(unbounded): the key’s presence inoptionsdecides, not its truthiness.This is QuTiP’s single option-merge boundary (shared by the single and batched solve paths), so the dynamiqs-only
gradientknob — which QuTiP’sSolverOptionsrejects — is stripped here exactly once for portability; downstream runners trust the merged dict.
- coerce_state(state, dims=None)[source]¶
Wrap a foreign-native array state (ket or density matrix) into a
Qobj.Used when a per-call
backend="qutip"override consumes states built under the dynamiqs backend.dimsrestores the tensor structure QuTiP’s solvers require to match the Hamiltonian’s dims.
- sesolve(H, psi0, tlist, e_ops=None, options=None)[source]¶
Solve the Schrödinger equation — wraps
qutip.solver.sesolve.SESolver.
- mesolve(H, rho0, tlist, c_ops=None, e_ops=None, options=None)[source]¶
Solve the Lindblad master equation — wraps
qutip.solver.mesolve.MESolver.
- batched_sesolve(problems, *, n_jobs=-1, progress=True)[source]¶
Run sesolve in parallel via loky workers; fall back to sequential on failure.
- batched_mesolve(problems, *, n_jobs=-1, progress=True)[source]¶
Run mesolve in parallel via loky workers; fall back to sequential on failure.
- prepare_hamiltonian(description, tlist=None)[source]¶
Convert a
HamiltonianDescriptioninto aQobjorQobjEvo.Each dynamic coefficient is band-normalized: every carrier stays analytic while only its slow, carrier-free envelope is sampled (on tlist, locally densified around any window edge — see
_band_coefficient()/_augmented_sample_grid()). This is exact regardless of how resonant a carrier is — the lab frame no longer accumulates the cubic-spline error that pre-sampling the fullenvelope·carrierproduct caused.- Parameters:
- Return type:
- prepare_batch(description, tlist)[source]¶
Build a deferred-construction batch; per-element
QobjEvois built in workers.Each unique
CanonicalOperatoris converted exactly once (shared across elements) and only the slow, carrier-free envelope is sampled on the user grid, locally densified around any window edge (carriers stay analytic — see_band_coefficient()). FinalQobjEvoassembly lives insolve_batch()so it runs inside loky workers, keeping the main process overhead O(1) in batch size.- Parameters:
- Return type:
- solve_batch(batch, *, progress=True)[source]¶
Solve a
SolveBatchwith per-elementQobjEvobuilt in loky workers.- Parameters:
- Return type:
- warmup(n_jobs=-1)[source]¶
Pre-spawn the reusable loky worker pool out of any timed region.
Entirely optional:
solve_batchspins the pool up on demand, so the only reason to call this is to move that one-time spin-up cost out of a region you are timing (benchmarks). Ordinary scripts and notebooks never need it.Forks the workers and pays the per-worker import cost up front by mapping a no-op over the worker count. A subsequent sweep then reuses the live pool instead of paying cold spin-up inside the timed solve. No-op (with a warning) when loky is unavailable.
- Parameters:
n_jobs (int)
- Return type:
None