quchip.backend.containers

Backend-agnostic solver-result and IR-lowering containers.

These frozen/auto dataclasses are the payloads exchanged across the backend boundary. The engine emits them (or backends produce them) without committing to any solver’s native storage: a SolverResult holds native states but exposes a backend-free shape, a PreparedHamiltonian / PreparedBatch carries whatever RHS the backend’s solver accepts opaquely, and an EigensystemData defers per-column ket materialization so the dressing / sweep hot path never pays for allocations it does not use.

Kept separate from quchip.backend.protocol (the Backend ABC) so the contract and its payloads can evolve independently.

References

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

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

Module Attributes

PreparedBatch

What Backend.prepare_batch() may return — the batching strategy is explicit in the type, and Backend.solve_batch() dispatches on it.

Classes

DeferredBatch(shared, batch_size[, ...])

Batched-solve payload whose RHS construction is deferred.

EagerBatch(rhs_list, batch_size[, metadata, ...])

Batched-solve payload with one native RHS per element.

EigensystemData(eigenvalues, eigenvector_matrix)

Hermitian eigensystem returned in a single diagonalization call.

PreparedHamiltonian(rhs[, metadata])

Backend-native Hamiltonian produced by Backend.prepare_hamiltonian().

SolverResult(times[, states, expect, ...])

Backend-agnostic container for a single time-evolution solve.

VmappedBatch(rhs, batch_size[, metadata, tlist])

Batched-solve payload with a single natively batched RHS.

class quchip.backend.containers.SolverResult(times, states=None, expect=None, final_state=None, stats=<factory>, solver='')[source]

Bases: object

Backend-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). None if store_states was disabled.

Type:

list[Any] | None

expect

Expectation-value traces. list[list[float]] indexed by e_ops-then-time, or a dict when the engine supplied labelled observables (keys are either strings or (drive_label, op) pairs).

Type:

list[list[float]] | dict[str | tuple[str, str], Any] | None

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.

Type:

dict[str, Any]

solver

The dispatched solver name ("sesolve" or "mesolve").

Type:

str

times: Any
states: list[Any] | None = None
expect: list[list[float]] | dict[str | tuple[str, str], Any] | None = None
final_state: Any | None = None
stats: dict[str, Any]
solver: str = ''
class quchip.backend.containers.PreparedHamiltonian(rhs, metadata=<factory>)[source]

Bases: object

Backend-native Hamiltonian produced by Backend.prepare_hamiltonian().

rhs is whatever the backend’s solver accepts directly — a Qobj / QobjEvo for QuTiP, a dynamiqs TimeQArray / sum of them for dynamiqs. metadata passes engine-level hints (e.g. spectral_bound_ghz for integrator step heuristics) through opaquely.

Parameters:
rhs: Any
metadata: dict[str, Any]
class quchip.backend.containers.EagerBatch(rhs_list, batch_size, metadata=<factory>, tlist=None)[source]

Bases: object

Batched-solve payload with one native RHS per element.

The default Backend.solve_batch() dispatches each element’s RHS through Backend.batched_sesolve() / Backend.batched_mesolve().

Parameters:
rhs_list: list[Any]
batch_size: int
metadata: dict[str, Any]
tlist: Any | None = None
class quchip.backend.containers.VmappedBatch(rhs, batch_size, metadata=<factory>, tlist=None)[source]

Bases: object

Batched-solve payload with a single natively batched RHS.

rhs covers 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.

Parameters:
rhs: Any
batch_size: int
metadata: dict[str, Any]
tlist: Any | None = None
class quchip.backend.containers.DeferredBatch(shared, batch_size, metadata=<factory>, tlist=None)[source]

Bases: object

Batched-solve payload whose RHS construction is deferred.

shared carries backend-private state; the producing backend must override Backend.solve_batch() to consume it (the QuTiP path — final QobjEvo assembly happens inside loky workers so main-process overhead stays O(1) in batch size).

Parameters:
shared: Any
batch_size: int
metadata: dict[str, Any]
tlist: Any | None = None
quchip.backend.containers.PreparedBatch: TypeAlias = quchip.backend.containers.EagerBatch | quchip.backend.containers.VmappedBatch | quchip.backend.containers.DeferredBatch

What Backend.prepare_batch() may return — the batching strategy is explicit in the type, and Backend.solve_batch() dispatches on it.

class quchip.backend.containers.EigensystemData(eigenvalues, eigenvector_matrix, _states_builder=None, _states_cache=None)[source]

Bases: object

Hermitian eigensystem returned in a single diagonalization call.

eigenvalues is ascending. eigenvector_matrix stacks the eigenvectors as columns in the bare-product basis used for the diagonalization. The per-column backend-native kets are exposed lazily via the eigenstates property so the dressing / sweep hot path (which reads only eigenvalues + eigenvector_matrix + labeling) never pays for D backend-ket allocations and a second O(D**2) densification it does not use.

Backends populate either _states_builder (a callable that materializes the ket list on demand) or prime _states_cache directly (when the diagonalizer already produced the kets, e.g. QuTiP’s Qobj.eigenstates).

Parameters:
eigenvalues: Any
eigenvector_matrix: Any
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/vmap the 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.