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.

The reusable loky process pool parallelizes batched sweeps and heterogeneous problem lists.

References

  • Johansson, Nation, Nori — QuTiP 2, Comput. Phys. Commun. 183, 1760 (2012)

  • Breuer & Petruccione — The Theory of Open Quantum Systems (OUP, 2002)

Classes

QuTiPBackend()

Concrete backend that uses QuTiP.

class quchip.backend.qutip.QuTiPBackend[source]

Bases: Backend

Concrete backend that uses 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
property array_module: Any

Return the array module used by backend-aware numeric code.

Returns numpy for CPU-only backends and jax.numpy for JAX-traceable backends. Engine code that must stay differentiable (signal evaluation, frame assembly) routes array operations through this module, so the dynamiqs backend stays JAX-traceable end to end.

to_array(op)[source]

Return a dense array (native to array_module) for op.

Parameters:

op (Operator) – Backend-native operator.

overlap(a, b)[source]

Return the scalar inner product ⟨a|b⟩ for two kets.

Parameters:
  • a (State) – Kets in the same Hilbert space, where the first is conjugated.

  • b (State) – Kets in the same Hilbert space, where the first is conjugated.

norm(state_or_op)[source]

Return the native QuTiP norm.

Parameters:

state_or_op (qutip.Qobj) – Ket or bra for the Euclidean norm, or matrix for the trace norm.

Returns:

Qobj.norm() with its default norm selection.

Return type:

float

trace(op)[source]

Return the scalar trace Tr(op).

Parameters:

op (Operator) – Backend-native operator.

destroy(n)[source]

Build the annihilation operator \(\hat a\) for an n-level Fock space.

Parameters:

n (int) – Positive Hilbert-space dimension. Number states go from 0 to n - 1.

create(n)[source]

Build the creation operator \(\hat a^\dagger\) for an n-level Fock space.

Parameters:

n (int) – Positive Hilbert-space dimension. Number states go from 0 to n - 1.

number(n)[source]

Build the number operator \(\hat n = \hat a^\dagger \hat a\).

Parameters:

n (int) – Positive Hilbert-space dimension. Number states go from 0 to n - 1.

identity(n)[source]

Build the identity operator for an n-level space.

Parameters:

n (int) – Positive Hilbert-space dimension. Number states go from 0 to n - 1.

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. None means a single subsystem of size data.shape[0].

Parameters:
  • data (array_like) – Dense matrix in the requested tensor-product basis.

  • dims (list or None, default None) – Subsystem dimensions, flat or [[row_dims], [column_dims]]. None treats the row space as one subsystem.

compact_operator(rows, columns, values, size)[source]

Construct an operator on size compact excitation states from its entries.

Repeated entries add. Each backend chooses the storage. The default stores a dense matrix, which keeps traced values traced, and raises MemoryError before it allocates one that cannot fit.

Parameters:
  • rows (array_like of int) – Row and column index of each entry.

  • columns (array_like of int) – Row and column index of each entry.

  • values (array_like) – Value of each entry.

  • size (int) – Number of compact states.

compact_operators_fit(size, count)[source]

Return whether count compact operators on size states fit in the available memory.

The default stores each as a dense matrix, as compact_operator() does.

Parameters:
  • size (int) – Number of compact states.

  • count (int) – Number of operators that exist at the same time.

linear_combination(terms)[source]

Return Σᵢ cᵢ·Aᵢ, adding the terms in order.

A backend can accumulate concrete terms in its native layout, whereas this default uses operator arithmetic. A coefficient of exactly 1 adds its operator unscaled.

Parameters:

terms (sequence of (scalar, Operator)) – At least one coefficient and operator pair on a common space.

to_canonical_operator(op)[source]

Serialize a native operator into the backend-agnostic canonical IR.

Parameters:

op (Operator) – Backend-native operator.

from_canonical_operator(canonical)[source]

Reconstruct a native operator from the canonical IR payload.

Parameters:

canonical (CanonicalOperator) – Backend-neutral matrix payload with its tensor dimensions and basis metadata.

coerce_operator(op)[source]

Coerce an array-like local operator into backend-native form.

Mirrors coerce_state() for operators. Device-side accessors for physical coupling (for example, charge_coupling_operator()) return dense, trace-safe arrays, not backend-native operators. Composition entry points (tensor(), dag()) send their operands through this hook, so both forms compose freely. A backend whose native operators are already arrays overrides this method with a trace-safe passthrough.

Parameters:

op (Operator) – Backend-native operator.

dag(op)[source]

Return the Hermitian conjugate op†.

Parameters:

op (Operator) – Backend-native operator.

eigenenergies(op)[source]

Return the ascending eigenvalues of a Hermitian operator.

Parameters:

op (Operator) – Hermitian operator to diagonalize.

eigensystem_data(op)[source]

Return ascending eigenvalues, stacked eigenvector matrix, and lazy eigenstates.

Parameters:

op (Operator) – Hermitian operator to diagonalize.

expect(op, state)[source]

Return the expectation value ⟨state|op|state⟩ for a ket or a density matrix.

Parameters:
  • op (Operator) – Observable with the same tensor dimensions as state.

  • state (State) – Ket or density matrix, not normalized by the method.

expect_over_time(op, stacked_states)[source]

Return ⟨op⟩(t) for every save point, as one (T,) array in array_module.

Accepts a ket stack (T, n, 1) (returns ⟨ψ|op|ψ⟩) or a density-matrix stack (T, n, n) (returns Tr(op·ρ)). One matrix product replaces the per-point expect loop. Every intermediate stays in array_module, so the JAX backend keeps the trace differentiable.

Parameters:
  • op (Operator) – Observable in the tensor-product basis of the states.

  • stacked_states (array_like) – Backend-stacked kets or density matrices from stack_states.

ptrace(state, keep, dims)[source]

Reduce a composite state onto subsystem(s) keep with a partial trace.

Parameters:
  • state (Any) – Composite ket or density matrix over the subsystems in dims.

  • keep (int | list[int]) – Subsystem index (or indices) to keep. All other subsystems are traced out.

  • dims (list[int]) – Subsystem dimensions of the full composite space, in order.

permute_state(state, dims, order)[source]

Reorder the subsystems of a composite state.

dims are the subsystem levels in state’s current tensor order. order follows the numpy.transpose convention, where order[i] is the subsystem index in the current order that becomes position i after the permutation, so order == list(range(len(dims))) is a no-op.

Default, correct for all backends: densify, reshape to one axis per subsystem, transpose, and reshape back. A ket reshapes/transposes once. A density matrix applies the same subsystem permutation to the row index group and to the column index group. Concrete backends can override this method with a native reorder (e.g. QuTiP’s Qobj.permute), which stays sparse-friendly and keeps the dims metadata.

Every array operation goes through array_module (never bare numpy), so a traced state stays traced end to end under the dynamiqs backend.

Parameters:
  • state (State) – Ket or density matrix in the current tensor order.

  • dims (sequence of int) – Current subsystem dimensions.

  • order (sequence of int) – Permutation of subsystem indices, listing their positions in the new order.

tensor(*operators)[source]

Return the tensor product of operators, and keep the subsystem dims metadata.

Parameters:

*operators (Operator) – Factors in tensor-product order.

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 the subsystem SWAP when index_a > index_b and the identity-padding for non-adjacent devices. Backends with sparse layouts do not materialize the dense full matrix.

Parameters:
  • op_ab (Operator) – Two-subsystem operator, in index_a then index_b tensor order.

  • index_a (int) – Distinct positions of the target subsystems in dims.

  • index_b (int) – Distinct positions of the target subsystems in dims.

  • dims (sequence of int) – Full tensor-product dimensions in chip order.

basis(n, k)[source]

Build the Fock basis ket \(|k\rangle\) in an n-level space.

Parameters:
  • n (int) – Hilbert-space dimension.

  • k (int) – Number-state index, satisfying 0 <= k < n.

tensor_states(*states)[source]

Return the tensor product of states (defaults to tensor()).

Parameters:

*states (State) – Factors in tensor-product order.

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\).

Parameters:
  • n (int) – Fock-space truncation.

  • alpha (complex) – Dimensionless coherent-state amplitude. Its squared magnitude sets the untruncated mean occupation.

state_to_dm(state)[source]

Return a density matrix, or pass state through if state is already one.

Parameters:

state (State) – Ket or density matrix to inspect or convert.

is_ket(state)[source]

Return whether state is a ket (flat or column vector) rather than a density matrix.

Parameters:

state (State) – Ket or density matrix to inspect or convert.

is_native_state(state)[source]

Return whether state is already represented by this backend.

Parameters:

state (State) – Ket or density matrix to inspect or convert.

resolve_solver_options(options, *, metadata, tlist)[source]

Resolve QuTiP integration options without changing the input mapping.

Parameters:
  • options (dict) – Native QuTiP settings, e.g. method, atol, rtol, nsteps (step ceiling), and max_step (ns). Unspecified step controls use engine hints, and explicit settings take priority. For method-specific keys, see QuTiP solver options. Select the saved-state storage via the simulation’s states argument.

  • metadata (dict) – Lowering hints, including ordinary-GHz spectral/carrier bounds.

  • tlist (array_like) – Save-time grid in ns, used to estimate integration budgets.

Returns:

Copied options, with the missing integration defaults filled.

Return type:

dict

coerce_state(state, dims=None)[source]

Convert a foreign-native state into this backend’s native form.

The solve boundary calls this method so that a per-call backend= override (see QuantumSequence.simulate()) accepts initial states built under another backend. For example, a QuTiP Qobj from chip.state(...) given to a dynamiqs gradient solve. Default: pass through unchanged. dims are the chip’s subsystem levels, for backends whose state type carries tensor structure.

Parameters:
  • state (State or array_like) – Ket or density matrix to convert to this backend.

  • dims (tuple of int or None, default None) – Target tensor dimensions. None lets the backend infer the dimensions.

solve_problem(problem)[source]

Lower and solve a SolveProblem (the single-element entry point).

Delegates to SolveProblem.solver_name(). It uses sesolve only for a ket with no collapse operators and mesolve otherwise, unless problem.solver forces a choice.

Parameters:

problem (SolveProblem) – Complete captured Hamiltonian, initial state, time grid, observables and solver settings.

Returns:

Native solver payload for the requested calculation.

Return type:

SolverResult

trajectory_view(native, problem, *, index, states, deterministic)[source]

Read one view of a native stochastic result of this backend.

Parameters:
  • native (object) – Native stochastic result that solve_problem() returned.

  • problem (SolveProblem) – Captured request, with its solver, state storage and native options.

  • index (int or None) – Kept trajectory, or None for the native ensemble average.

  • states (bool) – Whether to read the saved states.

  • deterministic (bool) – Whether to read the deterministic no-click run instead.

Returns:

Expectation values, state history and final state. Each entry is None when the native result does not keep it.

Return type:

tuple

trajectory_records(native, problem)[source]

Read the diffusive measurement currents of a native stochastic result.

Parameters:
Returns:

Currents of shape (runs, channels, intervals), or (runs, channels, 2, intervals) for heterodyne detection, and the sampling of the signal in each interval: "start", "middle", "end" or "mean".

Return type:

tuple

trajectory_clicks(native, problem)[source]

Read the jump events of a native stochastic result.

Parameters:
Returns:

For each run, its click times and the native jump-operator index of each click; the sampling weight of each run; the weight of the no-click run that the native sampling solves apart; and whether each run filled its native click buffer.

Return type:

tuple

trajectory_run_expect(native, problem)[source]

Read the expectation values of every kept run of a native stochastic result.

Parameters:
Returns:

One array of shape (runs, times) for each native expectation operator.

Return type:

list

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/QobjEvo or dynamiqs TimeQArray).

  • psi0 (Any) – Initial ket.

  • tlist (Any) – Save times in ns.

  • e_ops (list[Any] | None) – Optional observables. The solver accumulates ⟨ψ|Oᵢ|ψ⟩ along the trajectory.

  • options (dict[str, Any] | None) – Backend-specific options dict. For the supported keys, see each backend’s resolve_solver_options.

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\}\).

Parameters:
  • H (object) – Native Hamiltonian, already scaled to angular frequency in rad/ns. Do not multiply by 2*pi again.

  • rho0 (State) – Initial density matrix, or a ket that the concrete solver accepts.

  • tlist (array_like, shape (nt,)) – Save times in ns, strictly increasing.

  • c_ops (list of Operator or None, default None) – Collapse operators including square-root rates in 1/sqrt(ns). None gives no dissipators.

  • e_ops (list of Operator or None, default None) – Observables in output order. None requests no expectation traces.

  • options (dict or None, default None) – Native integration options. See the concrete backend’s resolve_solver_options method.

Returns:

Saved states, expectation traces and solver diagnostics.

Return type:

SolverResult

steadystate(problem, *, prepared=None, guess=None)[source]

Solve a static Lindblad generator with qutip.steadystate().

Parameters:
  • problem (SteadyStateProblem) – Captured static model, observables, and stationary solver options.

  • prepared (PreparedStationary or None, default None) – Matching prepared generator. None builds it.

  • guess (Qobj or None, default None) – Stationary state of a related generator. If this generator annihilates the state to round-off, the method returns it without solving and the stats record guess_reused.

Returns:

Stationary density matrix and convergence diagnostics.

Return type:

SteadyStateSolverResult

Notes

problem.options accepts native QuTiP stationary-solver keywords, e.g. method (default "direct") and solver (default None). quchip consumes rank_tolerance (singular-value cutoff, default automatic) and diagnostic_max_dimension (default 16). Above that Hilbert dimension, the nullity is not computed, and None means unchecked, not a unique state.

stationary_bytes(engine_result)[source]

Return the estimated bytes of the sparse LU factors of a stationary generator.

The method builds the generator again. The estimate is low, as sparse_lu_bytes() says.

Parameters:

engine_result (EngineResult) – Captured static Hamiltonian and collapse channels.

linear_response(problem)[source]

Solve one passive-linear input-output problem in the backend’s native arrays.

Parameters:

problem (LinearResponseProblem) – Captured passive-linear model, selected ports, probe frequencies, and incident amplitudes.

Returns:

Scattering response, internal amplitudes and numerical diagnostics.

Return type:

LinearResponseSolverResult

coherence_response(problem)[source]

Solve a frequency response on adjacent excitation-sector coherences.

Parameters:

problem (CoherenceResponseProblem) – Sector Hamiltonians, channels and frozen-reference probe sources.

stationary_resolvent(engine_result, sources, observables, frequencies, *, prepared=None)[source]

Evaluate trace-zero stationary resolvents in native backend arrays.

For each ordinary-GHz offset f, form and factor the trace-constrained shifted Liouvillian once, then solve (L + i 2π f) X = source with Tr(X) = 0 for every named source column. Return Tr(observable X) keyed by (source, observable). The engine supplies canonical operators. The backend owns Liouvillian construction, factorization, and solves.

Parameters:
  • engine_result (EngineResult) – Captured static operating point.

  • sources (tuple of (str, CanonicalOperator)) – Named source columns for the constrained linear systems.

  • observables (tuple of (str, CanonicalOperator)) – Named operators evaluated against each solution.

  • frequencies (array_like) – Offset frequencies in ordinary GHz.

  • prepared (PreparedStationary or None, default None) – Matching prepared generator. None builds it.

Returns:

Arrays keyed by (source_name, observable_name), indexed by frequency.

Return type:

dict

stationary_propagate(engine_result, initial, observables, times, *, prepared=None)[source]

Propagate an operator under a static Liouvillian and evaluate observables.

initial does not have to be a normalized density matrix. This supports quantum-regression queries, and the backend keeps ownership of the propagation algorithm and the numerical Liouvillian.

Parameters:
  • engine_result (EngineResult) – Captured static operating point.

  • initial (CanonicalOperator) – Initial operator. Regression queries need neither normalization nor positivity.

  • observables (tuple of (str, CanonicalOperator)) – Named operators evaluated after propagation.

  • times (array_like) – Propagation delays in ns.

  • prepared (PreparedStationary or None, default None) – Matching prepared generator. None builds it.

Returns:

Observable names mapped to arrays indexed by delay.

Return type:

dict

parallel_solve_problems(problems, *, progress=True, max_workers=None)[source]

Solve large, structurally heterogeneous problem lists through loky workers.

Parameters:
  • problems (list[Any]) – Problems that cannot be merged into one structural batch.

  • progress (bool) – Display solver progress if the backend supports it.

  • max_workers (int | None) – Worker processes. None chooses the count, and 1 solves in this process.

Returns:

Backend results in input order, or None to keep the engine’s structural-group dispatch.

Return type:

list[SolverResult] | None

batched_sesolve(problems, *, n_jobs=-1, progress=True)[source]

Solve multiple sesolve problems, sequentially by default.

Backends that can vectorize (dynamiqs vmap) or parallelize (QuTiP with loky) override this method. Each problem dict carries the same kwargs as sesolve().

Parameters:
  • problems (list of dict) – One keyword-argument mapping per sesolve call.

  • n_jobs (int, default -1) – Worker count, where -1 requests all CPUs. The base sequential implementation ignores it.

  • progress (bool, default True) – Request a progress display if the backend supports it.

Returns:

Results in input order.

Return type:

list of SolverResult

batched_mesolve(problems, *, n_jobs=-1, progress=True)[source]

Solve multiple mesolve problems, sequentially by default.

Parameters:
  • problems (list of dict) – One keyword-argument mapping per mesolve call.

  • n_jobs (int, default -1) – Worker count, where -1 requests all CPUs. The base sequential implementation ignores it.

  • progress (bool, default True) – Request a progress display if the backend supports it.

Returns:

Results in input order.

Return type:

list of SolverResult

prepare_hamiltonian(description, tlist=None)[source]

Convert a EngineResult into a Qobj or QobjEvo.

Each dynamic coefficient is band-normalized. Every carrier stays analytic, and only its slow, carrier-free envelope is sampled on tlist, locally densified around each window edge. This avoids the cubic-spline error of a pre-sampled full envelope·carrier product, even for resonant carriers in the lab frame.

Parameters:
  • description (EngineResult) – Captured engine Hamiltonian and channels in solver units.

  • tlist (array_like) – Requested save times in ns, used by native time-dependent lowering.

Returns:

Native right-hand side and numerical integration hints.

Return type:

PreparedHamiltonian

prepare_batch(batch)[source]

Build a deferred-construction batch whose workers build each element’s QobjEvo.

Only the slow, carrier-free envelope is sampled on the user grid, locally densified around each window edge, and the carriers stay analytic.

Parameters:

batch (SolveBatch) – Batch of captured problems with compatible save grids.

Returns:

Eager, native-vectorized, or deferred representation selected by this backend.

Return type:

PreparedBatch

solve_batch(batch, *, progress=True, max_workers=None)[source]

Solve a SolveBatch, with the QobjEvo for each element built in loky workers.

Parameters:
  • batch (SolveBatch) – Captured problems and batch parameter values.

  • progress (bool, default True) – Request a batch progress display.

  • max_workers (int or None, default None) – Worker processes. None takes the CPU count that loky reports, which honors LOKY_MAX_CPU_COUNT, or fewer when the available memory cannot hold them. 1 solves in this process.

Returns:

Results in batch order.

Return type:

list of SolverResult

warmup(n_jobs=-1)[source]

Start the reusable solver pool.

Parameters:

n_jobs (int, default -1) – Worker count. -1 takes the CPU count that loky reports.