quchip.backend.dynamiqs

Dynamiqs backend: JAX-native solver for differentiable, vmappable simulation.

This backend wraps dynamiqs (which builds on JAX) to supply:

  • Full JAX traceability. Every operator, state, and signal program evaluates inside JAX, so gradients flow through frame resolution, carrier phases, envelope parameters, crosstalk mixing, and dissipator strengths.

  • Native batched solves. A typed SolveBatch is stacked along a batch axis and integrated with native Dynamiqs integrators under vmap in solve_batch(). Structurally heterogeneous batches fail loudly, with no silent sequential fallback, so the caller can regroup them with quchip.engine.solve_many().

References

  • Guilmin et al. — dynamiqs: an open-source Python library for GPU-accelerated and differentiable simulation of quantum systems (2024)

  • Bradbury et al. — JAX: composable transformations of Python+NumPy programs (2018)

  • Lindblad — Commun. Math. Phys. 48, 119 (1976)

Classes

DynamiqsBackend()

Concrete backend backed by dynamiqs + JAX.

class quchip.backend.dynamiqs.DynamiqsBackend[source]

Bases: Backend

Concrete backend backed by dynamiqs + JAX. Operators/states are QArray.

Example

>>> from quchip.backend.dynamiqs import DynamiqsBackend
>>> backend = DynamiqsBackend()
>>> a = backend.destroy(3)
>>> n = backend.matmul(backend.dag(a), a)
>>> float(backend.to_array(n)[2, 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 dynamiqs norm with its default PSD assumption.

Parameters:

state_or_op (QArray) – Ket or bra for the Euclidean norm, or a positive-semidefinite matrix for its real trace. General indefinite operators are outside this hook’s matrix convention.

Returns:

Native scalar. Traced inputs keep their gradients.

Return type:

jax.Array

trace(op)[source]

Return the scalar trace Tr(op).

Parameters:

op (Operator) – Backend-native operator.

eager_operators()[source]

Temporarily use dense Dynamiqs operators, then restore the global layout.

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.

diag(values, dims=None)[source]

Build a backend-native sparse-DIA diagonal operator from main-diagonal values.

Overrides the protocol default to keep the operator in sparse-DIA layout, even when values is a JAX tracer.

Parameters:
  • values (array_like) – Main-diagonal entries, flattened in input order.

  • dims (list or None, default None) – Subsystem dimensions as in from_array. None uses one subsystem.

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.

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.

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.

stack_states(states)[source]

Stack the saved states of a trajectory into a single (T, …) array.

The default densifies each state and applies np.stack along a new leading time axis. A backend whose solver already returns a stacked native state (dynamiqs) overrides this method with a no-op pass-through.

Parameters:

states (sequence of State) – Saved states in time order, with matching shapes and tensor dimensions.

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_over_time(stacked_states, keep, dims)[source]

Reduce onto subsystem(s) keep at every save point, and return a (T, k, k) DM stack.

Accepts a ket or DM trajectory and uses no per-point Python loop.

Parameters:
  • stacked_states (array_like) – Backend-stacked saved states from stack_states.

  • keep (int or list of int) – Subsystem positions to keep.

  • dims (list of int) – Full tensor-product dimensions before tracing.

matmul(a, b)[source]

Multiply operators, keeping a product of sparse-DIA factors sparse.

Concrete factors multiply on the host. Traced sparse-DIA factors multiply their diagonals directly.

Parameters:
  • a (Operator) – Left and right matrix factors with compatible dimensions.

  • b (Operator) – Left and right matrix factors with compatible dimensions.

linear_combination(terms)[source]

Accumulate scalar multiples of operators into one qarray, on the host when every term is concrete.

Sparse-DIA terms sum their diagonals into a sparse-DIA result. A sum that includes a dense term is dense, as in qarray arithmetic. Batched operators or coefficients use qarray arithmetic.

Parameters:

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

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]

Validate dynamiqs integration options and fill the default step budget.

Parameters:
  • options (dict) – Supported keys: method (native deterministic dynamiqs method), gradient (native gradient configuration), max_steps (integer ceiling), progress_meter, store_states, and store_final_state. nsteps aliases max_steps. progress_bar aliases progress_meter. Do not supply an alias and its canonical key together. Set tolerances on the native method object. With an explicit method, set its step limit there instead of also passing max_steps here. Monte Carlo methods and unrecognized keys are rejected.

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

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

Returns:

Copied options with 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.

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 by a trace-constrained JAX solve.

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

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

  • guess (QArray or None, default None) – Accepted for the backend protocol. The solve and its uniqueness check always run, so the state stays differentiable.

Returns:

Stationary density matrix and convergence diagnostics.

Return type:

SteadyStateSolverResult

Notes

problem.options accepts only method="direct" and rank_tolerance (singular-value cutoff, default automatic). A nonunique stationary kernel gives a NaN state, so inspect the returned nullity.

stationary_bytes(engine_result)[source]

Return the estimated peak bytes of the dense stationary solve, with its generator.

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

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

prepare_hamiltonian(engine_result, tlist=None)[source]

Convert a EngineResult into a dynamiqs native RHS.

Static terms are summed as qarrays. Dynamic terms with ScalarModulation time-dependence are wrapped with dynamiqs.modulated and a JAX-traceable _SignalCallable. tlist passes through in metadata but is not used for sampling, because Dynamiqs evaluates callables on the integrator’s adaptive grid.

Parameters:
  • engine_result (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]

Lower compatible problems into stable leaves for one vmapped solve.

For each dynamic slot, the per-element ScalarModulation signals are stacked leaf-by-leaf along a leading batch axis (jnp.stack on matching pytree leaves).

Raises:

ValueError – If a slot contains heterogeneous pytree structures (cannot be stacked) or a non-ScalarModulation time dependence.

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 a single native dynamiqs vmap.

Raises RuntimeError, with no silent fallback, when the batch is not structurally homogeneous. Callers with heterogeneous inputs should regroup through quchip.engine.solve_many().

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) – Accepted for the backend protocol. The vmapped solve runs in this process.

Returns:

Results in batch order.

Return type:

list of SolverResult