TopoLS compiles a quantum circuit into a lattice-surgery pipe diagram on the surface code, minimising space–time volume, and exports it in a form that TQEC can simulate.
TopoLS performs compilation in three stages:
The circuit is turned into a ZX diagram and simplified by spider fusion. The diagram is then layer-sliced by topological connectivity, which exposes merge–split operations directly and enables space–time reductions invisible in a gate-based representation.
Each layer of the ZX diagram is embedded into a 3D grid (two spatial axes plus time) by Monte Carlo Tree Search: spiders become cubes, wires are routed between them, and the search minimises the resulting space–time volume while keeping every pipe colour-consistent.
Large circuits are partitioned into blocks by spider connectivity, which bounds the size of each embedding problem while preserving the topological optimization.
Resources:
- 📄 Paper — TopoLS: Lattice Surgery Compilation via Topological Program Transformations
- 🎥 Talk — TopoLS presentation at TQEC
- 📊 Slides — TopoLS slides
- 🏗 How the code is organised —
docs/ARCHITECTURE.md
Compilation results on a 16-qubit GHZ state and a 500-qubit random circuit:
Requires Python ≥ 3.10.
git clone https://github.com/tqec/TopoLS.git
cd TopoLS
# with uv
uv sync
# or with pip, inside a virtual environment
python3 -m venv .venv && source .venv/bin/activate
python -m pip install -e .All scripts run from the docs/ directory and read circuits from
docs/benchmark/<name>.qasm.
cd docs
# 1. compile the 16-qubit GHZ circuit
uv run prog.py -f ghz_16 -b 20 -zx 1 -dir 1 -l 4 -r 0 -s 2 -t 2 -i 1000 -csv result -sp 0 --backtrack 1
# -> result/topols/ghz_16.pkl (embedding) result/topols/result.csv (one row of metrics)
# 2. export to TQEC and render
uv run 2tqec.py -f ghz_16 -p True # result/bgraph/ghz_16.bgraph + result/visualization/ghz_16.png
uv run 2tqec.py -f ghz_16 -i True # result/visualization/ghz_16_interactive.html (drag / rotate / zoom)
# 3. simulate a block graph with TQEC (small circuits only): compile and export a CNOT, then simulate
uv run prog.py -f CNOT -b 20 -zx 1 -dir 1 -l 4 -r 0 -s 2 -t 2 -i 1000 -csv result -sp 0
uv run 2tqec.py -f CNOT
uv run python -m topols.tools.pipe_sim -f CNOT # result/simulation/CNOT_*.html, CNOT_lep_*.pngWith pip, replace uv run by python3 (and uv run python by python3).
The same workflow, step by step, is in
docs/tutorial.ipynb; open it with
uv run --with jupyterlab jupyter lab docs/tutorial.ipynb # or: pip install jupyterlabThe scripts in docs/ are
thin command-line front ends; everything they do is available as functions
in the topols package (topols.pipeline.prepare_graph,
topols.driver.operation, topols.export.bgraph.build_pipe_diagram, …).
The search (topols.driver.operation and everything below it) also exists
as a Rust crate, rust/topols_core, exposed to Python as the extension
module topols_core. It is a line-by-line port validated against the
Python implementation: for the same circuit and options both engines
produce the same embedding, bit for bit (positions, orientations,
paths, volume). The Python implementation stays the reference; the Rust
core is 10-45x faster depending on the circuit.
# build the extension into the current environment (needs a Rust toolchain: https://rustup.rs)
uv run maturin develop --release -m rust/topols_core/Cargo.toml # or: pip install maturin && maturin develop --release -m rust/topols_core/Cargo.toml
cd docs
uv run prog.py -f ghz_16 ... --engine rust # force the Rust core
uv run prog.py -f ghz_16 ... --engine python # force the Python reference
uv run prog.py -f ghz_16 ... # auto: Rust when importable, else Pythonprog.py prints Engine: rust or Engine: python. Without the extension
everything works unchanged in pure Python.
To reproduce the benchmarks with the Rust core run uv run exp_rust.py in
docs/ (same benchmarks and settings as exp.py, results in
result/topols/result_<config>_rust.csv and summary_rust.csv); the header
of exp_rust.py lists the reference volumes and times of both engines.
docs/tutorial_rust.ipynb is the tutorial run
through the Rust core, including the build step.
| option | meaning |
|---|---|
-f NAME |
circuit docs/benchmark/NAME.qasm |
-b N |
maximum block size for circuit slicing (default 20) |
-zx 0/1 |
ZX simplification off/on |
-dir 0/1 |
direction (orientation) optimization off/on |
-l N |
qubits per row of the 2D footprint (e.g. 4 for 16 qubits) |
-r N, -s K |
first random seed, and how many consecutive seeds to search in parallel |
-t SEC |
search budget per MCTS call (each layer, each seed), in seconds of work on the reference machine; the budget is counted in A* expansions, so results do not depend on the machine's speed |
-i N |
maximum MCTS iterations per call |
--backtrack K |
when a layer cannot be embedded from the best previous-layer state, retry it from up to K of the other seeds' previous-layer states before falling back to coarser strategies (0 = off) |
-sp N |
for dense circuits: spread gates over rows so that no row holds more than N gates (0 = off) |
-csv NAME |
append the metrics row to result/topols/NAME.csv |
--engine auto/rust/python |
search implementation (see Rust core); auto picks the Rust core when installed |
The search is anytime and deterministic: for a fixed seed and starting
state the sequence of MCTS iterations is fixed and the budget is counted
in units of work rather than wall-clock time, so a compile reproduces
exactly on any machine, and a larger -t or -i only extends the same
search and cannot return a worse layer. More seeds (-s) explore
independent searches in parallel (one process per seed, up to the CPUs
available) and keep the best; --backtrack guards against a low-volume
layer state that turns out to be a dead end for the next layer.
Outputs: result/topols/<name>.pkl holds the embedding (positions,
orientations, spider types, routed paths, I/O ports, volume, compile time);
the CSV row repeats the metrics. 2tqec.py writes result/bgraph/ and
result/visualization/; topols.tools.pipe_sim writes result/simulation/.
docs/exp.py runs the nine benchmarks of the paper in its three TopoLS
configurations — Full-Opt (-b 20 -dir 1), Part-Opt (-b 20 -dir 0) and
Place-Opt (-b 5 -dir 1) — with the per-benchmark search settings
(-s, -t, --backtrack) recorded in the script:
cd docs
uv run exp.py # all three configurations, several hours
uv run exp.py full # or any subset of: full part placeEach compile appends a row to result/topols/result_<config>.csv, and the
script ends with a summary table (space–time volume in surface-code cubes
and compile time per benchmark and configuration), also written to
result/topols/summary.csv.
2tqec.py -f NAME -p True— static image of the pipe diagram (result/visualization/NAME.png);-i True— interactive HTML (NAME_interactive.html). Cubes are coloured by their X/Z boundaries, S gates green, T gates purple, input/output ports grey; a yellow band on a pipe marks a colour change (a Hadamard).python -m topols.tools.viz_region -f NAME --xmin .. --xmax .. --ymin .. --ymax .. --zmin .. --zmax .. -o OUT— interactive rendering of one region of a large diagram, with node ids.python -m topols.tools.pipe_sim -f NAME— rebuilds the exported.bgraphas a TQECBlockGraphand simulates it with sinter (result/simulation/).
Both read result/ relative to the current directory, so run them from
docs/.
TopoLS compiles circuits into a lattice-surgery pipe diagram that TQEC can consume directly for simulation and resource evaluation.
- Magic states. All magic-state gates are treated as T gates, since they share the same execution pattern in lattice surgery; translate other magic gates to T before compiling.
If you use TopoLS in your research, please cite:
@misc{zhou2026topols,
author = {{Zhou}, Junyu and {Liu}, Yuhao and {Decker}, Ethan and {Kalloor}, Justin and {Weiden}, Mathias and {Chen}, Kean and {Iancu}, Costin and {Li}, Gushu},
title = {{TopoLS: Lattice Surgery Compilation via Topological Program Transformations}},
journal = {arXiv e-prints},
keywords = {Quantum Physics},
year = 2026,
month = jan,
eid = {arXiv:2601.23109},
pages = {arXiv:2601.23109},
doi = {10.48550/arXiv.2601.23109},
archiveprefix = {arXiv},
eprint = {2601.23109},
primaryclass = {quant-ph},
}

