quchip.backend.dynamiqs

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

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

  • 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 a single dq.sesolve / dq.mesolve call (vmap under the hood) in solve_batch(). Structurally heterogeneous batches fail loudly — no silent sequential fallback — so the caller can regroup them via 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 for JAX-traceable numeric code (jax.numpy).

to_array(op)[source]

Return a dense jax.numpy array for op.

Parameters:

op (Any)

Return type:

Any

overlap(a, b)[source]

Return the complex inner product ⟨a|b⟩ for two kets, via dynamiqs.braket.

dynamiqs.overlap returns the real-valued \(|\langle a|b \rangle|^2\) (or a density-matrix fidelity), not the protocol’s phase-sensitive complex inner product — dq.braket is the matching primitive.

Parameters:
Return type:

complex

norm(state_or_op)[source]

Return the norm of a state or operator via dynamiqs.norm.

Returns the backend-native (possibly traced) scalar rather than a concrete float — the protocol allows a 0-d array here so a traced amplitude stays traceable under jax.jit/grad.

Parameters:

state_or_op (Any)

Return type:

Any

trace(op)[source]

Return the scalar trace Tr(op) via dynamiqs.trace.

Parameters:

op (Any)

Return type:

complex

destroy(n)[source]

Return the annihilation operator for an n-level Fock space (dynamiqs.destroy).

Parameters:

n (int)

Return type:

Any

create(n)[source]

Return the creation operator for an n-level Fock space (dynamiqs.create).

Parameters:

n (int)

Return type:

Any

number(n)[source]

Return the number operator for an n-level Fock space (dynamiqs.number).

Parameters:

n (int)

Return type:

Any

identity(n)[source]

Return the identity operator for an n-level space (dynamiqs.eye).

Parameters:

n (int)

Return type:

Any

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 — the offsets (0,) are concrete, so the SparseDIAQArray constructor accepts the traced values directly. Without this override, dynamiqs’s from_canonical_operator densifies any traced DIA payload, which forces a dense H₀ for circuit-style devices and triggers the sparse→dense warning during static-Hamiltonian assembly.

Parameters:
Return type:

Any

from_array(data, dims=None)[source]

Construct a native QArray from a dense matrix (row/col or flat dims).

Parameters:
Return type:

Any

to_canonical_operator(op)[source]

Serialize a QArray into the backend-agnostic canonical IR.

Parameters:

op (Any)

Return type:

Any

from_canonical_operator(canonical)[source]

Reconstruct a QArray from the canonical IR payload (dense/DIA/COO).

Parameters:

canonical (Any)

Return type:

Any

coerce_operator(op)[source]

Pass op through unchanged (trace-safe): native operators are (JAX) arrays already.

Parameters:

op (Any)

Return type:

Any

dag(op)[source]

Return the Hermitian conjugate op† via dynamiqs.dag.

Parameters:

op (Any)

Return type:

Any

eigenenergies(op)[source]

Return the ascending eigenvalues of a Hermitian operator (jax.numpy.linalg.eigvalsh).

Parameters:

op (Any)

Return type:

Any

eigensystem_data(op)[source]

Return ascending eigenvalues, eigenvector matrix, and lazily built eigenstate kets.

Parameters:

op (Any)

Return type:

EigensystemData

expect(op, state)[source]

Return the expectation value ⟨op⟩ for a ket or density matrix via dynamiqs.expect.

Parameters:
Return type:

complex

ptrace(state, keep, dims)[source]

Reduce onto subsystem(s) keep via dynamiqs.ptrace (partial trace).

Parameters:
Return type:

Any

stack_states(states)[source]

Pass the native stacked QArray through; restack a list if needed.

Parameters:

states (Any)

Return type:

Any

expect_over_time(op, stacked_states)[source]

Return ⟨op⟩(t) at every save point via a single batched dynamiqs.expect call.

Parameters:
  • op (Any)

  • stacked_states (Any)

Return type:

Any

ptrace_over_time(stacked_states, keep, dims)[source]

Reduce onto subsystem(s) keep at every save point (partial trace, batched dynamiqs.ptrace).

Parameters:
Return type:

Any

tensor(*operators)[source]

Return the tensor product of operators via dynamiqs.tensor (pass-through for one factor).

Parameters:

operators (Any)

Return type:

Any

embed_two_body(op_ab, index_a, index_b, dims)[source]

Embed a two-body operator on devices index_aindex_b into the full space.

Keeps a sparse operator sparse; a dense operator is reordered, padded with identities, and permuted back to the original subsystem order.

Parameters:
Return type:

Any

basis(n, k)[source]

Return the Fock basis ket |k⟩ in an n-level space (dynamiqs.basis).

Parameters:
Return type:

Any

tensor_states(*states)[source]

Return the tensor product of states via dynamiqs.tensor (pass-through for one state).

Parameters:

states (Any)

Return type:

Any

coherent(n, alpha)[source]

Return the coherent state |α⟩ truncated to n Fock levels (dynamiqs.coherent).

Parameters:
Return type:

Any

state_to_dm(state)[source]

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

Parameters:

state (Any)

Return type:

Any

is_ket(state)[source]

Return whether state is a column-vector ket rather than a density matrix.

Parameters:

state (Any)

Return type:

bool

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

Normalize quchip aliases and fill in a max_steps budget.

The quchip option aliases (progress_barprogress_meter, nstepsmax_steps) are mapped onto dynamiqs’ canonical names by _normalize_dq_options(). When max_steps is still unset, it is derived by default_solver_steps() (see that function’s docstring for the heuristic) — the same heuristic the QuTiP backend uses.

max_steps is an abort ceiling on the integrator’s total internal step count, not a bound on individual step size. Unlike the QuTiP backend, this backend does not consume metadata['max_step_ns']: dynamiqs’ deterministic adaptive methods (Tsit5, Dopri5, Dopri8, Kvaerno3/Kvaerno5) expose no step-size-bound parameter analogous to QuTiP’s max_step (verified against dynamiqs 0.3.3 — none of those methods accept a dtmax-style argument). dq.method.Event does accept dtmax, but it governs only the no-click evolution inside dq.jssesolve’s jump-SSE integration, not the deterministic sesolve/mesolve path this backend uses; wrapping a deterministic solve in Event to borrow its dtmax would fail before integration even starts.

Consequence: a finite-support pulse embedded in a long idle span can be silently skipped by dynamiqs’ adaptive step selection, the same failure mode quchip.backend.qutip.QuTiPBackend.resolve_solver_options() fixes via max_step. The engine’s max_step_ns hint is present in metadata but has no landing site on this backend today. Options for closing the gap — segmented integration around each pulse window, or an upstream request for a dtmax knob on dynamiqs’ deterministic methods — are unimplemented future work.

Parameters:
Return type:

dict[str, Any]

coerce_state(state, dims=None)[source]

Convert a QuTiP Qobj state into a dimensioned dynamiqs qarray.

Used when a per-call backend="dynamiqs" override consumes initial states built under the QuTiP backend (chip.state(...) before the call). Composite subsystem dimensions must survive the conversion: dynamiqs checks them when multiplying the Hamiltonian by the state.

Parameters:
Return type:

Any

sesolve(H, psi0, tlist, e_ops=None, options=None)[source]

Solve the Schrödinger equation via dynamiqs.sesolve (JAX-traced).

Parameters:
Return type:

SolverResult

mesolve(H, rho0, tlist, c_ops=None, e_ops=None, options=None)[source]

Solve the Lindblad master equation via dynamiqs.mesolve (JAX-traced).

Parameters:
Return type:

SolverResult

solve_problem(problem)[source]

Lower and solve a single SolveProblem via a cached jitted solve.

Falls back to the protocol default (one-shot prepare_hamiltonian + sesolve/mesolve) whenever the description contains a dynamic term that is not a ScalarModulation (the cached path can only rebuild ScalarModulation signals).

Parameters:

problem (Any)

Return type:

SolverResult

prepare_hamiltonian(description, tlist=None)[source]

Convert a HamiltonianDescription into a dynamiqs native RHS.

Static terms are summed as qarrays; dynamic terms with ScalarModulation time-dependence are wrapped via dynamiqs.modulated with a JAX-traceable _SignalCallable. tlist is passed through in metadata but not used for sampling — dynamiqs evaluates callables on the integrator’s adaptive grid.

Parameters:
  • description (Any)

  • tlist (Any | None)

Return type:

PreparedHamiltonian

prepare_batch(description, tlist)[source]

Lower a BatchedHamiltonianDescription into a single vmapped RHS.

Shared operators (static + per-slot dynamic) are converted exactly once via an id-keyed cache. 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) and wrapped in a single _BatchedSignalQArrayCallable so dq.timecallable can vmap the solve.

Raises:

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

Parameters:
  • description (Any)

  • tlist (Any)

Return type:

VmappedBatch

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

Solve a SolveBatch via a single native dynamiqs vmap.

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

Parameters:
Return type:

list[SolverResult]