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