Skip to content

Gate-by-gate simulation

TwSimulator fuses runs of gates into a matrix product operator (MPO) and applies each one to the state in a single sweep. TwDirectSimulator builds no MPO at all: every gate updates the state on its own. It takes the same execute(circuit, nsamples=..., seed=...) call and returns the same QCSResults, so switching between the two is a one-line change.

import mimiqcircuits as mc
from tensorweaver import TwDirectSimulator

c = mc.Circuit()
c.push(mc.GateH(), 0)
c.push(mc.GateCX(), 0, 9)          # qubits 0 and 9 are far apart on the chain
c.push(mc.Measure(), range(10), range(10))

results = TwDirectSimulator(bonddim=64).execute(c, nsamples=1000, seed=1)

How a gate is applied

  • A one-qubit gate acts on the site of its qubit.
  • A two-qubit gate acts on two neighbouring sites: they are contracted together, the gate is applied, and one truncated SVD splits them back. Qubits that are not neighbours are first brought together by swapping adjacent sites.
  • A gate on three to max_gate_qubits (default 4) qubits gathers them into one block of sites and acts there in one step. A larger gate is decomposed.

Qubits move and stay moved. The swaps that route a gate are not undone. The state keeps a map from each qubit to the site that holds it, so a second gate on the same pair needs no routing. Every read-out (sampling, measurement, Amplitude, ExpectationValue) answers in your qubit numbering, wherever the qubits sit, so nothing is sorted during execute. evolvezerostate sorts the chain back at the end and returns a plain MPS with qubit i on site i.

Every swap is a truncated SVD at sv_cutoff and bonddim, like a gate, and what it discards is included in the reported fidelity.

Routing

routing= decides which of a gate's two qubits moves.

  • "lookahead" (default) considers both. A move qualifies if every bond its swaps cross is at most half of bonddim: a swap there cannot truncate, since it at most doubles a bond. Among the qualifying moves it takes the one that leaves the next 16 two-qubit gates closest. When neither qualifies, it moves the gate's second qubit, as "target" does.
  • "target" always moves the gate's second qubit next to its first. For a CX that brings the target to the control, which keeps a control that acts on many targets in place.

Near the cap, keeping the first qubit fixed holds entangled qubits together; minimising swaps there instead costs fidelity. Where the state is weakly entangled, every swap is exact and the lookahead only saves work. On the 100-qubit MQT Bench u3+cx circuits the lookahead was 1.1 to 3.6x faster than "target" on 11 of 13 inputs, up to 1.4x slower on the other two.

Which simulator to use

TwSimulator TwDirectSimulator
Cost per gate one sweep per fused block of gates one SVD per two-qubit gate, plus the routing swaps
Strongest on deep, dense layers of two-qubit gates, ladders of CX that fuse into a small operator controlled long-range gates (QFT, phase estimation, amplitude estimation), circuits whose state stays weakly entangled
BondDim, SchmidtRank, VonNeumannEntropy yes no: they read a bond of the chain, which is in whatever order routing left it
Noise channels one or two qubits one or two qubits
GPU usegpu=True no

Measured on 100-qubit MQT Bench circuits (one thread, the circuit plus 1000 samples): TwDirectSimulator was 1.8 to 8.5x faster than TwSimulator on qft, qftentangled, amplitude and phase estimation and on the 125-qubit quantum walk. It could not run su2random and realamprandom at all: their ladders of CX gates fuse into a low-rank operator, while applied one at a time every intermediate state is fully entangled. TwSimulator runs both at bond dimension 16.

When in doubt, run the circuit on both at a small bonddim and compare the time and the reported fidelity.

Options

Option Default Meaning
bonddim 256 maximum bond dimension
sv_cutoff None (1e-7) singular values below this are discarded by every gate and swap
max_gate_qubits 4 the most qubits a gate may act on before it is decomposed; a gate on k qubits contracts k sites into one tensor of 4**k · χ² entries
routing "lookahead" which qubit a two-qubit gate moves, see above
seed, linalg as for TwSimulator

entdim, mpo_cutoff, algorithm, strict_bonddim, post_compress, buffer and traversal are MPO settings and have no counterpart here. reorderqubits= on execute works as for TwSimulator: a good starting layout means fewer swaps.

The routed state

RoutedMPS is the state behind the simulator, usable on its own:

import numpy as np
from tensorweaver import RoutedMPS

state = RoutedMPS.zero_state(6, 32)
h = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)
cx = np.eye(4, dtype=complex)[[0, 1, 3, 2]]
state.apply_gate(h, [0])
state.apply_gate(cx, [0, 5])    # qubit 5 is swapped next to qubit 0, and stays
print(state.layout)             # [0, 2, 3, 4, 5, 1]: qubit 5 sits on site 1
mps, fidelity = state.to_mps()  # sort back: qubit i on site i

The engine always moves a gate's second qubit; the "lookahead" routing is applied by this package, which picks the mover by the order it passes the qubits in. See the API reference for every method, and example 14_direct_simulator for a walk-through.