State Vector Backend
Alongside the default MPS backend, QuantumCircuitsMPS.jl also ships an exact, dense state-vector backend. It stores the full wavefunction as a Vector{ComplexF64} and applies gates by direct matrix multiplication, no SVD, no bond-dimension truncation, no cutoff/maxdim bookkeeping. Every gate, measurement, and observable that works on the MPS backend works identically here (apply!, track!, record!, simulate! are all backend-agnostic); only the SimulationState(...) constructor call changes.
When to use it:
- Cross-validating MPS results against an exact reference with zero truncation error
- Small systems (
L ≲ 25qubits) where the dense wavefunction fits comfortably in RAM - Producing exact reference trajectories for debugging suspected MPS truncation artifacts
When not to use it: anything beyond L ≈ 25-27 qubits, or whenever you need the MPS backend's L=100+ scalability (see MPS Backend). Memory grows as local_dim^L, exponentially, with no way around it for a dense representation.
Quick Example
using QuantumCircuitsMPS
# Exact state-vector simulation for small systems (L ≲ 25)
L = 8
state = SimulationState(L=L, bc=:open, backend=:statevector,
rng=RNGRegistry(gates_spacetime=1, gates_realization=2, born_measurement=3))
initialize!(state, ProductState(binary_int=0))
track!(state, :entropy => EntanglementEntropy(cut=L÷2))
apply!(state, HaarRandom(2), AdjacentPair(L÷2))
record!(state)
println("Entropy: $(state.observables[:entropy][end])")Physics: AdjacentPair(L÷2) places the Haar-random two-qubit gate directly across the entanglement cut at L÷2, so the resulting nonzero entropy is the exact entanglement generated by that single gate, no truncation, unlike an MPS run at finite maxdim.
Memory Requirements
The state vector is dense: local_dim^L × 16 bytes (ComplexF64, 16 bytes per amplitude), with no compression:
| System | local_dim | Memory |
|---|---|---|
| L=20 qubits | 2 | ≈ 16 MB |
| L=25 qubits | 2 | ≈ 512 MB |
| L=30 qubits | 2 | ≈ 16 GB |
The MPS backend's memory usage instead scales with bond dimension (maxdim), staying roughly flat in L — this is why MPS remains the default for L=100+ production runs, while the state-vector backend serves as an exact, small-L cross-validation and debugging tool.
Engine Selection
The state-vector backend has two interchangeable gate-application engines, chosen via the engine keyword:
engine=:builtin(default): reshape → matrix-multiply → reshape-back. Simple and easy to audit; the reference implementation every correctness check is validated against.engine=:optimized: a stride-loop gate-application kernel that skips the reshape/permute overhead, faster for largerL. Produces bitwise-identical results toengine=:builtinon the same input (see the Yao.jl pattern it's based on, credited in the top-levelREADME).
state = SimulationState(L=20, bc=:open, backend=:statevector, engine=:optimized,
rng=RNGRegistry(gates_spacetime=1, gates_realization=2, born_measurement=3))Use :builtin when auditability matters most (e.g. debugging an observable formula); reach for :optimized once you're pushing L toward the upper end of the state-vector backend's practical range and want faster gate application.