quchip.backend.protocol

Backend protocol and backend-agnostic solver-result containers.

quchip keeps the physics description separate from the solver conversion. The engine (quchip.engine) emits structured IR (EngineResult, SolveProblem and SolveBatch). This IR carries ordinary-GHz frequencies and backend-free operator payloads (CanonicalOperator), which each backend translates into the native form best suited to its solver. This module sets only the contract (see Backend), not the storage.

Unit convention

All frequencies that cross the backend boundary are ordinary (not angular) GHz, with time in ns. The single 2π conversion occurs at the engine boundary (assembly.py), and backends never rescale.

Aliases

Operator and State are both typing.Any. Concrete backends bind them to their native type: qutip.Qobj for QuTiPBackend, dynamiqs.QArray for DynamiqsBackend.

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)

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

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

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

Classes

Backend()

Abstract contract implemented by every quchip operator backend.

class quchip.backend.protocol.Backend[source]

Bases: ABC

Abstract 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, and eigensystem helpers. Concrete operators support native Python arithmetic (+ - * @), so quchip never adds scale/add wrappers.

  • State algebra: basis/coherent, overlap, ptrace, expect, and ket ↔ density-matrix conversion.

  • IR lowering & solve dispatch: prepare_hamiltonian / prepare_batch convert engine IR into native RHS form, and sesolve / mesolve run the solver. solve_problem / parallel_solve_problems / solve_batch drive the full pipeline from engine to result.

The NumPy-based defaults are correct for all backends. Subclasses override them only where they can do better (e.g. QuTiP reuses Qobj.eigenstates and dynamiqs reuses dq.expect).

Solver Hamiltonians use angular frequencies, while public device parameters use ordinary GHz. For operator and circuit-QED conventions, see Blais et al., Rev. Mod. Phys. 93, 025005 (2021).

abstract 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.

abstractmethod 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 Frobenius / ℓ²-norm of a state or operator.

A concrete float for CPU-only backends. A JAX-traceable backend can return a native 0-d array instead, so a traced amplitude stays differentiable. A caller that needs a concrete value passes the result through maybe_concrete_scalar().

Parameters:

state_or_op (State or Operator) – Ket or matrix whose Euclidean/Frobenius norm is required.

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.

trace(op)[source]

Return the scalar trace Tr(op).

Parameters:

op (Operator) – Backend-native operator.

eager_operators()[source]

Return a context manager for operator construction during JAX compile-time evaluation.

The default implementation leaves backend state unchanged.

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.

diag(values, dims=None)[source]

Construct a backend-native diagonal operator from main-diagonal values.

A backend with sparse layouts (for example, dynamiqs sparse-DIA) should return its sparse representation so that element-wise composition with other diagonal operators stays sparse. The default builds a dense np.diag and then calls from_array().

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.

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.

abstractmethod to_canonical_operator(op)[source]

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

Parameters:

op (Operator) – Backend-native operator.

abstractmethod 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.

matmul(a, b)[source]

Return the matrix product a @ b.

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]

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.

eigenenergies(op)[source]

Return the ascending eigenvalues of a Hermitian operator.

Parameters:

op (Operator) – Hermitian operator to diagonalize.

eigenstates(op)[source]

Return (eigenvalues, eigenstates) of a Hermitian operator, ascending.

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.

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.

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.

overlap_over_time(target, stacked_states)[source]

Return ⟨target|ψ(t)⟩ for every save point, as one (T,) array.

For a ket stack, the result is the phase-sensitive complex amplitude ⟨target|ψ(t)⟩ (never |·|²), so phase-dependent gradients flow through it. A density-matrix stack has no single phase, so the method returns the target-state population ⟨target|ρ(t)|target⟩. The calculation stays in array_module for JAX traceability.

Parameters:
  • target (State) – Reference ket in the same Hilbert space.

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

populations_over_time(stacked_states)[source]

Return full-chip diagonal populations (T, ∏dims) (real) for every save point.

For a ket stack, the result is |ψ(t)|² along the level axis (the density matrix is never built). For a DM stack, it is the real diagonal. The calculation stays in array_module for JAX traceability.

Parameters:

stacked_states (array_like) – Backend-stacked saved states. The trailing dimensions encode ket or matrix entries.

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.

embed(op, device_index, dims)[source]

Embed a single-device operator at device_index into the full tensor space.

Validation is shared, and the identity factors and tensor product go through the backend’s identity() / tensor(), so each backend keeps its native-optimal layout.

Parameters:
  • op (Any) – Single-device operator whose dimension matches dims[device_index].

  • device_index (int) – Position of op in the tensor product.

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

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

as_density_matrix(state)[source]

Promote a ket to its density matrix, and pass a density matrix through unchanged.

Parameters:

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

abstractmethod tensor(*operators)[source]

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

Parameters:

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

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/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.

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

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

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)[source]

Convert a EngineResult into a native solver RHS.

The engine supplies frequencies already converted by 2π (angular rad/ns), so backends must not rescale. Concrete backends override this method to select their native time-dependence representation.

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

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

Merge user options with backend-side defaults and metadata heuristics.

The default is a shallow copy. Concrete backends use metadata (for example, spectral_bound_ghz) to fill in integrator step budgets the user did not specify.

Parameters:
  • options (dict) – User integration settings, copied before missing defaults are filled.

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

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

Returns:

Resolved options. The input mapping is unchanged.

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

prepare_stationary(engine_result, *, prepared=None)[source]

Prepare once, or reuse the same backend and captured operating point.

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

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

Returns:

Native stationary generator tied to this operating point.

Return type:

PreparedStationary

stationary_bytes(engine_result)[source]

Return the estimated peak bytes of a stationary solve of a static model, or None.

Parameters:

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

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

Solve one static Lindblad problem in the backend’s native representation.

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

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

  • guess (State or None, default None) – Native stationary state of a related generator, such as the same model in a neighbouring frame. If this state is stationary here to round-off, a backend can return it instead of solving. The diagnostics are then evaluated on this problem’s generator.

Returns:

Stationary density matrix and convergence diagnostics.

Return type:

SteadyStateSolverResult

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.

sector_generator(sizes, hamiltonian, lowering, conserving)[source]

Return the truncated Lindblad generator of a conserving model on the totals N <= K.

The generator solves stationary states, resolvents and regression propagations matrix-free on excitation sectors. This default solves on host NumPy and SciPy arrays for every backend, so it needs concrete values.

Parameters:
  • sizes (sequence of int) – Dimension of each sector N = 0..K.

  • hamiltonian (scipy.sparse matrix) – Static Hamiltonian in rad/ns, in the lab frame and relative to the vacuum energy, with states ordered by N.

  • lowering (sequence of scipy.sparse matrix) – Channel couplings in 1/sqrt(ns) that lower N by one or conserve it.

  • conserving (sequence of scipy.sparse matrix) – Channel couplings in 1/sqrt(ns) that lower N by one or conserve it.

Returns:

Generator with stationary(), resolvent() and propagate().

Return type:

SectorGenerator

sector_solve_bytes(sizes, *, smallest=False)[source]

Return the estimated peak bytes of a stationary solve on excitation sectors.

Parameters:
  • sizes (sequence of int) – Dimension of each sector N = 0..K.

  • smallest (bool, default=False) – Estimate at the shortest Krylov restart, which is the least memory that a solve can run with, instead of the default restart.

sector_unique(sizes, lowering)[source]

Return whether the lowering channels prove one stationary state on the sectors N <= K.

The proof holds whatever the Hamiltonian, a probe and the conserving channels. Without a probe, that state is the vacuum.

Parameters:
  • sizes (sequence of int) – Dimension of each sector N = 0..K.

  • lowering (sequence of scipy.sparse matrix) – Channel couplings in 1/sqrt(ns) that lower N by one.

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]

Dispatch structurally heterogeneous problems in parallel.

Parameters:
  • problems (list['SolveProblem']) – 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, for backends that solve in parallel. None lets the backend choose, 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

prepare_batch(batch)[source]

Lower each explicit batch problem into a prepared batch.

The return type declares the batching strategy: EagerBatch (one RHS per element), VmappedBatch (one prebuilt native RHS), or DeferredBatch (backend-private payload for an overridden solve_batch()). Default: lowers each element independently with prepare_hamiltonian() into an EagerBatch.

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]

Lower and solve a SolveBatch.

Default: prepares the batch, then dispatches each element’s RHS through batched_sesolve() / batched_mesolve(). Native backends override this method to avoid the per-element unpack.

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, for backends that solve in parallel. None lets the backend choose, and 1 solves in this process.

Returns:

Results in batch order.

Return type:

list of SolverResult