Backend Simulators

This page is the backend-focused companion to Choosing a Backend and Modeling Tradeoffs. It names the current built-in backend families, the representation types that select them, and the interface points used when attaching a new backend.

Built-In Backend Families

QuantumClifford

CliffordRepr() selects the stabilizer backend built on QuantumClifford.MixedDestabilizer.

Use it when all of the following are approximately true:

  • the modeled subsystems are qubits,
  • the protocol stays near Clifford dynamics,
  • the important noise can be represented as Pauli-like noise, and
  • simulation scale matters more than generality.

This is the most specific built-in backend. In exchange for that restriction, it is usually the cheapest option for repeater-style and stabilizer-native network models.

QuantumOptics

QuantumOpticsRepr() selects the general QuantumOptics backend, and QuantumMCRepr() uses the same symbolic lowering path with a Monte Carlo style state representation. For backgrounds with a Kraus representation, QuantumMCRepr() samples a normalized pure-state trajectory instead of converting the state to a density operator.

Slots using QuantumMCRepr() store that trajectory in an internal MCKet wrapper around the underlying QuantumOptics Ket. The wrapper preserves Monte Carlo semantics through initialization, instantaneous operations, compositions whose factors are all MCKets, and projective measurements. Composing with a plain Ket produces a plain Ket, while composing with an Operator produces an Operator after density-matrix promotion.

ConstantHamiltonianEvolution preserves MCKet: it uses Schrödinger evolution without active backgrounds and Monte Carlo wave-function evolution when Lindblad jump operators are present. Standalone background evolution also preserves MCKet: it samples a Kraus branch when available and otherwise uses Monte Carlo wave-function evolution with a zero Hamiltonian.

Partial trace also preserves MCKet. It samples the discarded subsystem in that subsystem's native canonical basis and stores the corresponding conditional pure-state trajectory. Individual trajectories therefore depend on this canonical-basis unraveling, while their ensemble is the exact partial trace. Use QuantumOpticsRepr() when an exact deterministic reduced density matrix is needed, or project_traceout! when the sampled outcome itself is needed. When one traceout! call includes every live slot of a shared state, the register layer deletes the complete group without backend reduction or trajectory sampling.

Because MCKet is the stored state type, stateref.state[] exposes it directly and StateRef displays identify its implementation module as QuantumSavory. Combining an MCKet with an existing Operator still promotes the ket through dm and produces an Operator.

Use this family when:

  • you need general qubit operations beyond the stabilizer regime,
  • you need explicit ket or operator style simulation,
  • you want one backend that can handle both qubits and bosonic modes, or
  • you are validating a cheaper approximation on smaller systems.

This is the most flexible built-in path, but it also has the least structural compression.

Gabs

GabsRepr(...) selects the Gaussian backend from Gabs.

Use it when:

  • the modeled subsystems are bosonic modes,
  • the state stays in the Gaussian regime,
  • the operations are Gaussian, and
  • homodyne-style continuous-variable measurements are central to the model.

This is the right backend for continuous-variable models that would be awkward or expensive to force into a generic wavefunction description.

Choosing Precisely

The three built-in families answer different needs:

  • CliffordRepr() for large qubit stabilizer workloads.
  • QuantumOpticsRepr() for general qubit or mode simulations when flexibility matters more than asymptotic speed.
  • GabsRepr(...) for Gaussian continuous-variable simulations.

If you are not sure which one to start with, the safest workflow is:

  1. choose the cheapest backend that preserves the effect you care about;
  2. validate that modeling choice on a smaller instance with a more general backend if needed.

How Backend Selection Enters A Model

A register can specify representations slot by slot:

reg = Register(
    [Qubit(), Qumode()],
    [QuantumOpticsRepr(), GabsRepr(QuadBlockBasis)],
)

If you do not specify a representation, the slot trait decides the default. Today, both Qubit() and Qumode() default to QuantumOpticsRepr().

Symbolic states and operators cross the backend boundary through express. That conversion is used by initialize!, apply!, observable, and related register operations.

Backend Extension Points

A new backend does not need to replace the register API. It needs to provide the methods that let the register API lower symbolic objects and act on native state types.

In practice, a backend integration usually defines:

  • a representation type such as CliffordRepr(), QuantumOpticsRepr(), or GabsRepr(...);
  • newstate(::QuantumStateTrait, ::YourRepr) for empty-slot initialization;
  • default_repr(...) for native backend state types;
  • nsubsystems and subsystemcompose for factorized state management;
  • native implementations of apply!, observable, project_traceout!, and traceout!;
  • symbolic lowering through express(..., ::YourRepr) or express_nolookup;
  • and, if the backend supports background evolution, uptotime! plus the background helpers it needs such as paulinoise, krausops, or lindbladop.

Those are the concrete points where the built-in backends connect today.

What Changes And What Stays Stable

When you switch backends, the following usually stays the same:

  • the register and network structure,
  • the symbolic states and operators,
  • the protocol control flow, and
  • the metadata and messaging logic.

What changes is the numerical representation used once symbolic objects are lowered and the set of operations that can be executed efficiently.

Performance Considerations

Operations that span separately factorized states compose those states into a larger tensor product. In particular, apply! and non-instant operations merge the touched states, while observable builds a temporary composition without changing the register. Repeated cross-state observables therefore repeat that tensor-product work.

The cost of composition depends on the backend. QuantumOpticsRepr() tensors state vectors or density operators; composing a ket with a density operator first promotes the ket to a density operator. CliffordRepr() and GabsRepr(...) keep their compact tableau and Gaussian representations, respectively.

Symbolic stabilizer projectors evaluate directly on selected, reordered subsystems of pure or mixed Clifford tableaux. A dense QuantumOpticsBase.Operator observable is however more problematic: for a pure Clifford state, QuantumSavory converts the entire stabilizer state to a dense ket. Its size grows exponentially with the number of qubits, even if the observable addresses only some subsystems.

Where To Go Next