quchip.backend¶
Backend selection and concrete backend implementations.
quchip ships two backends:
QuTiPBackend— CPU,qutip.Qobjoperators, process-parallel sweeps via loky. Default backend.DynamiqsBackend— JAX/dynamiqs, fully differentiable, nativevmapbatched solves. Optional extra (pip install quchip[dynamiqs]).
The public entry points are get_default_backend() /
set_default_backend() / reset_default_backend().
Engine internals use the thread-safe _backend_context() to scope a
backend for the duration of a single assembly pass (every backend call the
engine makes is reentrant because the override is a ContextVar).
Example
>>> from quchip.backend import get_default_backend, set_default_backend
>>> backend = get_default_backend() # QuTiPBackend by default
>>> set_default_backend("dynamiqs")
Functions
Return the active default backend. |
|
Clear the cached default backend; the next |
|
|
Set the active default backend by name ( |
- class quchip.backend.Backend[source]¶
Bases:
ABCAbstract contract implemented by every quchip operator backend.
A backend wraps a quantum-dynamics library (QuTiP, dynamiqs, …) and exposes three orthogonal surfaces:
Operator algebra —
destroy/create/number/identity,tensor,embed,dag,matmul, eigensystem helpers. Concrete operators support native Python arithmetic (+ - * @); quchip never introducesscale/addwrappers.State algebra —
basis/coherent,overlap,ptrace,expect, ket ↔ density-matrix conversion.IR lowering & solve dispatch —
prepare_hamiltonian/prepare_batchconvert engine IR into native RHS form;sesolve/mesolverun the solver;solve_problem/solve_batchdrive the full engine-to-result pipeline.
Defaults provided here are NumPy-based and correct for any backend, so concrete subclasses only override where they can do better (e.g. QuTiP reuses
Qobj.eigenstates, dynamiqs reusesdq.expect).- abstract property array_module: Any¶
Return the array module used by backend-aware numeric code.
Returns
numpyfor CPU-only backends andjax.numpyfor JAX-traceable backends. Engine code that must stay differentiable (signal evaluation, frame assembly) routes array ops through this module so the dynamiqs backend preserves JAX traceability end-to-end.
- abstractmethod to_array(op)[source]¶
Return a dense array (native to
array_module) for op.
- norm(state_or_op)[source]¶
Return the Frobenius / ℓ²-norm of a state or operator.
A concrete
floatfor CPU-only backends; a JAX-traceable backend may return a native 0-d array instead so a traced amplitude stays differentiable — callers that need concreteness route throughmaybe_concrete_scalar().
- from_array(data, dims=None)[source]¶
Construct a native operator from a dense matrix.
dims accepts quchip’s row/col layout (
[[row_dims], [col_dims]]) or a flat list;Nonemeans a single subsystem of sizedata.shape[0].
- diag(values, dims=None)[source]¶
Construct a backend-native diagonal operator from main-diagonal values.
Backends that distinguish layouts (e.g. dynamiqs sparse-DIA) should return their sparse representation so element-wise composition with other diagonal operators stays sparse. Default falls through to
from_array()after building a densenp.diag.
- abstractmethod to_canonical_operator(op)[source]¶
Serialize a native operator into the backend-agnostic canonical IR.
- Parameters:
op (Operator)
- Return type:
- abstractmethod from_canonical_operator(canonical)[source]¶
Reconstruct a native operator from the canonical IR payload.
- Parameters:
canonical (CanonicalOperator)
- Return type:
- coerce_operator(op)[source]¶
Coerce an array-like local operator into backend-native form.
The operator-side mirror of
coerce_state(). Device-side physical-coupling accessors (e.g.charge_coupling_operator()) return dense, trace-safe arrays rather than backend-native operators; composition entry points (tensor(),dag()) route their operands through this hook so both forms compose freely. Backends whose native operators are already arrays override with a trace-safe passthrough.
- eigensystem_data(op)[source]¶
Return ascending eigenvalues, stacked eigenvector matrix, and lazy eigenstates.
- Parameters:
op (Any)
- Return type:
- expect(op, state)[source]¶
Return the expectation value ⟨state|op|state⟩ — accepts ket or density matrix.
- ptrace(state, keep, dims)[source]¶
Reduce a composite state onto subsystem(s) keep via partial trace.
- permute_state(state, dims, order)[source]¶
Reorder a composite state’s subsystems.
dimsare the subsystem levels in state’s current tensor order.orderfollowsnumpy.transposeconvention:order[i]is the current-order subsystem index that becomes positioniafter permuting (soorder == list(range(len(dims)))is a no-op).Default: densify, reshape to one axis per subsystem,
transpose, reshape back — correct for any backend. A ket reshapes/transposes once; a density matrix applies the same subsystem permutation to both the row and column index groups. Concrete backends may override this with a native reorder (e.g. QuTiP’sQobj.permute) that stays sparse-friendly and keeps dims metadata.Every array op routes through
array_module(never barenumpy), so a traced state stays traced end-to-end under the dynamiqs backend.dims/orderare always concrete Python ints (subsystem structure, never a physics parameter), so their product is taken withmath.prod()rather thanarray_module.prod— routing a shape value through the array module would turn it into a tracer underjitand make the subsequentreshapefail.
- stack_states(states)[source]¶
Stack a trajectory’s saved states into a single
(T, …)array.Default densifies each state and ``np.stack``s along a new leading time axis. Backends whose solver already returns a stacked native state (dynamiqs) override this to a no-op pass-through.
- expect_over_time(op, stacked_states)[source]¶
Return ⟨op⟩(t) for every save point, as one
(T,)array inarray_module.Accepts a ket stack
(T, n, 1)(returns ⟨ψ|op|ψ⟩) or a density-matrix stack(T, n, n)(returnsTr(op·ρ)). One einsum replaces the per-pointexpectloop. Every intermediate stays inarray_module, so the JAX backend keeps the trace differentiable.
- overlap_over_time(target, stacked_states)[source]¶
Return ⟨target|ψ(t)⟩ for every save point, as one
(T,)array.For a ket stack this is the phase-sensitive complex amplitude ⟨target|ψ(t)⟩ (never
|·|²— phase-dependent gradients flow through it). For a density-matrix stack there is no single phase, so it returns the target-state population ⟨target|ρ(t)|target⟩. Stays inarray_modulefor JAX traceability.
- populations_over_time(stacked_states)[source]¶
Return full-chip diagonal populations
(T, ∏dims)(real) for every save point.For a ket stack this is
|ψ(t)|²along the level axis (the density matrix is never built); for a DM stack it is the real diagonal. Stays inarray_modulefor JAX traceability.
- ptrace_over_time(stacked_states, keep, dims)[source]¶
Reduce onto subsystem(s) keep at every save point, returning a
(T, k, k)DM stack.Reduces a ket or DM trajectory onto subsystem(s) keep without a per-point Python loop.
- embed(op, device_index, dims)[source]¶
Embed a single-device operator at device_index into the full tensor space.
Validation is shared; the identity factors and tensor product dispatch through the backend’s own
identity()/tensor(), so each backend keeps its native-optimal layout.
- abstractmethod 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.
Handles subsystem SWAP when
index_a > index_band identity-padding for non-adjacent devices, without materializing the dense full matrix when the backend supports sparse layouts.
- coherent(n, alpha)[source]¶
Build the coherent state \(|\alpha\rangle\) truncated to n Fock levels.
Built from the analytic series \(|\alpha\rangle = e^{-|\alpha|^2/2} \sum_k \alpha^k/\sqrt{\Gamma(k+1)}\,|k\rangle\).
- as_density_matrix(state)[source]¶
Promote a ket to its density matrix; pass a density matrix through unchanged.
- abstractmethod tensor(*operators)[source]¶
Return the tensor product of operators, preserving subsystem dims metadata.
- abstractmethod sesolve(H, psi0, tlist, e_ops=None, options=None)[source]¶
Solve the Schrödinger equation \(i\hbar \partial_t |\psi\rangle = H |\psi\rangle\).
- Parameters:
H (Any) – Native Hamiltonian (
Qobj/QobjEvoor dynamiqsTimeQArray).psi0 (Any) – Initial ket.
tlist (Any) – 1D array of save times in ns.
e_ops (list[Any] | None) – Optional observables to accumulate ⟨ψ|Oᵢ|ψ⟩ along the trajectory.
options (dict[str, Any] | None) – Backend-specific options dict. See each backend’s
resolve_solver_optionsfor supported keys.
- Return type:
- abstractmethod mesolve(H, rho0, tlist, c_ops=None, e_ops=None, options=None)[source]¶
Solve the Lindblad master equation (Lindblad 1976; Breuer & Petruccione 2002).
Integrates \(\dot\rho = -i[H,\rho] + \sum_k \mathcal D[L_k]\rho\) with \(\mathcal D[L]\rho = L\rho L^\dagger - \tfrac12\{L^\dagger L, \rho\}\).
- batched_sesolve(problems, *, n_jobs=-1, progress=True)[source]¶
Solve multiple sesolve problems. Default: sequential dispatch.
Backends that can vectorize (dynamiqs
vmap) or parallelize (QuTiP via loky) override this method. Each problem dict carries the same kwargs assesolve().
- batched_mesolve(problems, *, n_jobs=-1, progress=True)[source]¶
Solve multiple mesolve problems. Default: sequential dispatch.
- prepare_hamiltonian(description, tlist)[source]¶
Convert a
HamiltonianDescriptioninto a native solver RHS.The engine passes frequencies already converted by
2π(angular rad/ns); backends must not rescale. Concrete backends override this method to choose their native time-dependence representation.- Parameters:
description (HamiltonianDescription)
tlist (Any)
- Return type:
- resolve_solver_options(options, *, metadata, tlist)[source]¶
Merge user options with backend-side defaults and metadata heuristics.
Default is a shallow copy; concrete backends use metadata (e.g.
spectral_bound_ghz) to fill in integrator step budgets when the user did not specify one.
- coerce_state(state, dims=None)[source]¶
Convert a foreign-native state into this backend’s native form.
Called at the solve boundary so a per-call
backend=override (seeQuantumSequence.simulate()) accepts initial states that were built under another backend — e.g. a QuTiPQobjfromchip.state(...)handed to a dynamiqs gradient solve. Default: pass through unchanged.dimsare the chip’s subsystem levels, for backends whose state type carries tensor structure.
- solve_problem(problem)[source]¶
Lower and solve a
SolveProblem— the single-element entry point.Picks
mesolvewhen collapse operators are present (open system) orsesolveotherwise, unlessproblem.solverforces a choice.- Parameters:
problem (SolveProblem)
- Return type:
- prepare_batch(description, tlist)[source]¶
Lower a
BatchedHamiltonianDescriptioninto a prepared batch.The return type declares the batching strategy:
EagerBatch(one RHS per element),VmappedBatch(one natively batched RHS — dynamiqs), orDeferredBatch(backend-private payload consumed by an overriddensolve_batch()— QuTiP). Default: lowers each element independently viaprepare_hamiltonian()into anEagerBatch.- Parameters:
description (BatchedHamiltonianDescription)
tlist (Any)
- Return type:
- solve_batch(batch, *, progress=True)[source]¶
Lower and solve a
SolveBatch.Default: prepares the batch then dispatches each element’s RHS through
batched_sesolve()/batched_mesolve(). Native backends override to avoid the per-element unpack.- Parameters:
batch (SolveBatch)
progress (bool)
- Return type:
- class quchip.backend.SolverResult(times, states=None, expect=None, final_state=None, stats=<factory>, solver='')[source]¶
Bases:
objectBackend-agnostic container for a single time-evolution solve.
- Parameters:
- times¶
1D array of save times (ns) matching the solver’s
tlist.- Type:
Any
- states¶
Saved states, one per save time, in the backend’s native state type (
Qobj/QArray).Noneifstore_stateswas disabled.- Type:
list[Any] | None
- expect¶
Expectation-value traces.
list[list[float]]indexed bye_ops-then-time, or adictwhen the engine supplied labelled observables (keys are either strings or(drive_label, op)pairs).
- final_state¶
Final state (
states[-1]when states are stored). Useful for multi-segment protocols without retaining full trajectories.- Type:
Any | None
- stats¶
Solver-specific diagnostics (integrator step count, batch index, etc.). Purely informational; no physics depends on it.
- class quchip.backend.PreparedHamiltonian(rhs, metadata=<factory>)[source]¶
Bases:
objectBackend-native Hamiltonian produced by
Backend.prepare_hamiltonian().rhsis whatever the backend’s solver accepts directly — aQobj/QobjEvofor QuTiP, a dynamiqsTimeQArray/ sum of them for dynamiqs.metadatapasses engine-level hints (e.g.spectral_bound_ghzfor integrator step heuristics) through opaquely.
- class quchip.backend.EagerBatch(rhs_list, batch_size, metadata=<factory>, tlist=None)[source]¶
Bases:
objectBatched-solve payload with one native RHS per element.
The default
Backend.solve_batch()dispatches each element’s RHS throughBackend.batched_sesolve()/Backend.batched_mesolve().
- class quchip.backend.VmappedBatch(rhs, batch_size, metadata=<factory>, tlist=None)[source]¶
Bases:
objectBatched-solve payload with a single natively batched RHS.
rhscovers every element at once — the dynamiqs path, where the per-element signals are stacked along a leading batch axis and the solver runs one vmapped call.
- class quchip.backend.DeferredBatch(shared, batch_size, metadata=<factory>, tlist=None)[source]¶
Bases:
objectBatched-solve payload whose RHS construction is deferred.
sharedcarries backend-private state; the producing backend must overrideBackend.solve_batch()to consume it (the QuTiP path — finalQobjEvoassembly happens inside loky workers so main-process overhead stays O(1) in batch size).
- class quchip.backend.EigensystemData(eigenvalues, eigenvector_matrix, _states_builder=None, _states_cache=None)[source]¶
Bases:
objectHermitian eigensystem returned in a single diagonalization call.
eigenvaluesis ascending.eigenvector_matrixstacks the eigenvectors as columns in the bare-product basis used for the diagonalization. The per-column backend-native kets are exposed lazily via theeigenstatesproperty so the dressing / sweep hot path (which reads onlyeigenvalues+eigenvector_matrix+ labeling) never pays forDbackend-ket allocations and a secondO(D**2)densification it does not use.Backends populate either
_states_builder(a callable that materializes the ket list on demand) or prime_states_cachedirectly (when the diagonalizer already produced the kets, e.g. QuTiP’sQobj.eigenstates).- Parameters:
- property eigenstates: list[Any]¶
Return per-column backend-native eigenstate kets (built on first access).
Memoizes only when the materialized kets are tracer-free: under
jit/grad/vmapthe states carry tracers bound to the current trace, so caching them would let a stale tracer escape into a later trace. Concrete states are cached.
- quchip.backend.compute_two_body_permutation(idx_a, idx_b, dims)[source]¶
Compute permutations moving two subsystems to the front of a tensor product.
Returns
(order, inverse)where order lists the original subsystem positions in their reordered layout (the two target subsystems first, in ascending index order, followed by spectators) and inverse maps each original position to its new slot soorder[inverse[i]] == i.Used by backends to embed a two-body operator acting on arbitrary device indices into the full tensor product without densifying spectators.
- Parameters:
- Return type:
Examples
>>> from quchip.backend._dims import compute_two_body_permutation >>> compute_two_body_permutation(2, 0, [2, 2, 2, 2]) ([0, 2, 1, 3], [0, 2, 1, 3])
- quchip.backend.default_solver_steps(metadata, tlist)[source]¶
Return a heuristic step budget: ~100 steps per period of the fastest scale, min 200,000.
nstepsis an abort ceiling for adaptive integrators, not a step-size choice — the integrator picks its own step and the ceiling only decides when to give up, so a generous value costs nothing on a converging solve. The previous tight budget (10/period, min 500) sat at the same order as the true step count of high-order methods at tight tolerance over long spans, which forced user code to carry{"nsteps": 200_000}overrides; the floor now bakes that in.The fastest oscillation is the larger of the static spectral span (
spectral_bound_ghz) and the largest time-dependent carrier (max_carrier_freq_ghz). Both are ordinary GHz. Considering the carrier is essential in a rotating frame, where the static span is near-zero but inter-mode / drive carriers still drive fast dynamics.- Parameters:
metadata (dict[str, Any]) – Hamiltonian metadata; reads
spectral_bound_ghzandmax_carrier_freq_ghz(both ordinary GHz).tlist (Any) – Save-time grid; only its span
tlist[-1] - tlist[0](ns) is used.available (Returns None when neither scale is)
is (the tlist span)
non-positive
be (or any input is a JAX tracer (which must not)
default). (concretized — the caller falls back to the solver library's)
- Return type:
int | None
- quchip.backend.normalize_dims_from_list(dims, fallback=None)[source]¶
Normalize quchip-style dims metadata to a flat subsystem-dim tuple.
Accepts
[[rows], [cols]],[[rows]], or a flat list.Nonemeans a single subsystem of size fallback.- Parameters:
- Return type:
Examples
>>> from quchip.backend._dims import normalize_dims_from_list >>> normalize_dims_from_list([[2, 3], [2, 3]]) (2, 3) >>> normalize_dims_from_list(None, fallback=4) (4,)
- quchip.backend.validate_two_body_indices(index_a, index_b, dims)[source]¶
Raise
ValueErrorwhen two-body device indices are equal or out of range.
- quchip.backend.get_default_backend()[source]¶
Return the active default backend.
Returns (in order) the nearest
_backend_context()override, the user-set default fromset_default_backend(), or a freshly instantiatedQuTiPBackend(cached for subsequent calls).- Return type:
- quchip.backend.set_default_backend(backend)[source]¶
Set the active default backend by name (
"qutip"/"dynamiqs") or instance.
- quchip.backend.reset_default_backend()[source]¶
Clear the cached default backend; the next
get_default_backend()rebuilds it.- Return type:
None
Modules
Backend-agnostic solver-result and IR-lowering containers. |
|
Dynamiqs backend — JAX-native solver for differentiable, vmappable simulation. |
|
Backend protocol and backend-agnostic solver-result containers. |
|
QuTiP backend implementation of the |