cococo color code compilation¶
This submodule contains routing routines as described in in the papers (1) Lattice Surgery Compilation Beyond the Surface Code (arXiv:2504.10591) as well as (2) Exploiting Movable Logical Qubits for Lattice Surgery Compilation (arXiv:2512.04169).
The elementary routing routines for CNOT + T compilation in (1) assume a hexagonal routing graph and accessible Z and X operators along each boundary of a patch for lattice surgery. Hence, it is tailored to the color code, but could in principle be adapted for e.g. the folded surface code substrate as described in Fig. 8 of (1), by adapting the Dijkstra routine.
The routing routines for movable qubits (2) are tailored for the color code as well and would require more fundamental changes to allow other codes.
Layouts¶
Different layouts (“row”, “pair”, “hex”, “triple”, “single”) can be generated with the function gen_layout_scalable. One can place factory patches along the boundary. The construction of such layouts is described in the notebook scripts/cococo/layouts_general.ipynb.
A layout describes which nodes on the routing graph are used as logical data qubits and factory locations. The remainder is the routing ancilla space. The mapping of logical qubit labels onto those chosen data qubit locations on the graph is another task.
Randomly Sampled CNOT + T circuits¶
This submodule considers CNOT + T circuits without single qubit Clifford gates. Different types of random circuits can be generated using the functions generate_random_circuit, generate_max_parallel_circuit, generate_min_parallel_circuit as well as create_random_sequential_circuit_dag. However, one is welcome to write own circuit constructions.
Basic Routing and Qubit Label Allocation from (1)¶
Basic Compilation with given Layout and Mapping¶
The higher level compilation follows a simple greedy routine for solving the VDP problem. We greedily extended this to include paths to factories as well.
Note that the class BasicRouter
and particularly the method find_total_vdp_layers_dyn should be used to perform routing as described in the paper.
Optimization of Qubit Label Allocation by Hill Climbing¶
Once chosen a layout, one can optimize the qubit label allocation. This is important to exploit more parallelism of the original circuit. The class HillClimbing performs a simple hill climbing routine to optimize the qubit label mapping based on a heuristic metric which computes the initial crossing of shortest paths as well as a more reliable (yet expensive) metric which computes the routing for each Hill climbing iteration and directly aims to reduce the resulting layers. How it works can be seen in the notebook scripts/cococo/hill_climbing_examples.ipynb.
Plots shown in (1) can be reproduced from pickle files in scripts/cococo/evaluations_beyond_the_surface_code.
Microscopic Details of Snakes¶
In (1), we consider two microscopic substrates, both leading to a hexagonal routing graph. First, the class SnakeBuilderSTDW builds stabilizers and subsets of stabilizers to perform logical measurements for the color code connected by semi transparent domain walls (STDW). The class SnakeBuilderSC builds the surface code snakes required to perform lattice surgery between logical folded surface codes. However, this can only display snakes where you can easily embed the snake in 2d. A notebook with example constructions can be found in /scripts/cococo/snake_examples.ipynb.
Compilation with Movable Logical Qubits (2)¶
Compilation with movable logical qubits as described in (2) builds upon the BasicRouter from above. Based on the basic router we constructed a lookahead routine with simulated annealing which can be used via the TeleportationRouter. Examples are shown in the notebook scripts/cococo/movable_qubit_router_examples.ipynb.
Results shown in (2) can be reproduced in scripts/cococo/evaluations_movable_qubits
Selected Examples¶
Microscopic Color Code Snake¶
One can create the stabilizers of the joint codes after the merge between two logical qubits and a snake in between. For instance consider an example for the color code.
1import networkx as nx
2from mqt.qecc.cococo import snake_builder
3
4m = 12
5n = 12
6
7g = nx.hexagonal_lattice_graph(m=m, n=n, periodic=False, with_positions=True, create_using=None)
8
9# qubit positions within each patch, must be given in the right order of adjacent patches
10positions = [
11 [(1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (5, 2), (4, 2), (3, 2), (2, 2), (2, 3), (3, 3), (4, 3), (4, 4), (3, 4), (2, 4), (3, 5), (4, 5), (3, 6), (3, 7),],
12 [(6, 2), (6, 3), (6, 4), (7, 4), (7, 5), (6, 5), (5, 5), (5, 6), (6, 6), (7, 6), (8, 7), (7, 7), (6, 7), (5, 7), (4, 8), (5, 8), (6, 8), (7, 8), (8, 8),],
13 [(4, 10), (5, 10), (6, 10), (7, 10), (8, 10), (8, 11), (7, 11), (6, 11), (5, 11), (5, 12), (6, 12), (7, 12), (7, 13), (6, 13), (5, 13), (6, 14), (7, 14), (6, 15), (6, 16),],
14 [(9, 11), (9, 12), (9, 13), (10, 13), (10, 14), (9, 14), (8, 14), (8, 15), (9, 15), (10, 15), (11, 16), (10, 16), (9, 16), (8, 16), (7, 17), (8, 17), (9, 17), (10, 17), (11, 17),],
15]
16
17d = 5 #distance
18snake = snake_builder.SnakeBuilderSTDW(g, positions, d)
19
20z_plaquettes, x_plaquettes = snake.find_stabilizers()
21
22print("=====X stabilizers if ZZ merge=====")
23snake.plot_stabilizers(x_plaquettes)
24print("=====Z stabilizers if ZZ merge=====")
25snake.plot_stabilizers(z_plaquettes)
=====X stabilizers if ZZ merge=====
=====Z stabilizers if ZZ merge=====
Furthermore one can specify the subset of stabilizers to be measured to retrieve the logical ZZ result (Fig. 7 in the paper (1)).
1# consider the boundary patches to be logical and find the subset of stabilizers to measure the logical ZZ between them
2subset_stabs = snake.find_stabilizers_zz()
3assert snake.test_zz_stabs(subset_stabs) is True
4
5snake.plot_stabilizers(subset_stabs)
Compilation with Movable Qubits¶
To run the compilation with movable qubits, construct a layout first.
1import plotting
2
3import mqt.qecc.cococo.utils_routing as utils
4from mqt.qecc.cococo import circuit_construction, layouts
5
6layout_type = "triple"
7m = 2
8n = 2
9factories = []
10remove_edges = False
11g, data_qubit_locs, factory_ring = layouts.gen_layout_scalable(layout_type, m, n, factories, remove_edges)
12layout = dict(enumerate(data_qubit_locs)) #standard mapping
13
14plotting.plot_lattice_paths(g, {}, {}, layout, factories, size=(12, 4))
Then, choose a circuit.
1q = len(data_qubit_locs)
2j = 8 #the number of gates per logical layer
3num_gates = q * 2
4dag, pairs = circuit_construction.create_random_sequential_circuit_dag(
5 j,
6 q,
7 num_gates,
8)
9
10#randomly chosen circuit:
11pairs = [(8, 13), (15, 2), (9, 10), (0, 3), (23, 20), (1, 19), (4, 6), (22, 5), (13, 20), (2, 1), (10, 6), (0, 3), (23, 15), (9, 19), (5, 4), (8, 22), (3, 20), (1, 19), (6, 10), (4, 0), (2, 15), (22, 9), (13, 5), (8, 23), (3, 1), (8, 19)]
Then, one can run the basic router first to receive a reference result.
1terminal_pairs = layouts.translate_layout_circuit(pairs, layout) # let's stick to the simple layout
2t=4 #mock reset time for pure cnot circuit
3router = utils.BasicRouter(g, data_qubit_locs, factories, valid_path="cc", t=t, metric="exact", use_dag=True)
4layers = router.split_layer_terminal_pairs(terminal_pairs)
5vdp_layers, _ = router.find_total_vdp_layers_dyn(layers, data_qubit_locs, router.factory_times, layout, testing=False) #usually recommended to use `testing=True`
6print("Len of schedule without teleportation: ", len(vdp_layers))
Len of schedule without teleportation: 8
Afterward, let’s run the router with movable qubits, i.e. logical qubits can be moved during the execution of a CNOT gate as described in paper (2). A couple of parameters need to be defined, which are described in detail in optimize_layers.
1router = utils.TeleportationRouter(
2 g, data_qubit_locs, factories, valid_path="cc", t=t, metric="exact", use_dag=True, seed=1
3)
4layers = router.split_layer_terminal_pairs(terminal_pairs)
5
6max_iters = 100
7T_start = 100.0
8T_end = 0.1
9alpha = 0.95
10radius = 10
11k_lookahead = 5
12steiner_init_type = "full_random"
13jump_harvesting = True
14reduce_steiner = True
15idle_move_type = "later"
16reduce_init_steiner = False
17stimtest = True
18
19schedule, _ = router.optimize_layers(
20 terminal_pairs,
21 layout,
22 max_iters,
23 T_start,
24 T_end,
25 alpha,
26 radius=radius,
27 k_lookahead=k_lookahead,
28 steiner_init_type=steiner_init_type,
29 jump_harvesting=jump_harvesting,
30 reduce_steiner=reduce_steiner,
31 idle_move_type=idle_move_type,
32 reduce_init_steiner=reduce_init_steiner,
33 stimtest=stimtest,
34)
35
36print("Len of schedule with teleport router: ", len(schedule))
2026-06-01 12:12:31,764 [INFO] mqt.qecc.cococo.utils_routing: Iteration 0: |vdp_dict|=7, pushing |terminal_pairs_remainder|=1, remaining |layers|=4
2026-06-01 12:12:43,513 [INFO] mqt.qecc.cococo.utils_routing: Steiner found for this layer.
2026-06-01 12:12:43,514 [INFO] mqt.qecc.cococo.utils_routing: Final Temperature T = 5.920529e-01
2026-06-01 12:12:44,287 [INFO] mqt.qecc.cococo.utils_routing: Complexity of Steiner could be reduced.
2026-06-01 12:12:44,355 [INFO] mqt.qecc.cococo.utils_routing: Iteration 1: |vdp_dict|=2, pushing |terminal_pairs_remainder|=0, remaining |layers|=1
2026-06-01 12:12:44,356 [INFO] mqt.qecc.cococo.utils_routing: Number of gates in schedule and initial terminal_pairs coincides (:
2026-06-01 12:12:44,360 [INFO] mqt.qecc.cococo.utils_routing: Stim test succeeded: Pushing gates does not cause trouble(:
2026-06-01 12:12:44,361 [INFO] mqt.qecc.cococo.utils_routing: No duplicates found in any layer of the schedule - hence all good(:
2026-06-01 12:12:44,362 [INFO] mqt.qecc.cococo.utils_routing: No path/tree is placed on a logical pos. All good(:
2026-06-01 12:12:44,363 [INFO] mqt.qecc.cococo.utils_routing: All good with the reset times (:
Len of schedule with teleport router: 7
Overall the router with movable qubits reduces the schedule depth.
1print("Reduction Delta: ", len(vdp_layers) - len(schedule))
Reduction Delta: 1
Let’s plot the first few layers of the routing explicitly:
1plotting.plot_schedule(g, schedule[:3], factories, size = (12,4))
Step 1: Move Type - {((15, 0), (16, 0), (15, 2)): 'target', ((12, 0), (2, 2), (-2, 1)): 'target'}, Idle Move - None
vdp dict dict_keys([((15, 0), (16, 0)), ((6, 0), (10, 0)), ((6, 2), (2, 0)), ((18, 2), (13, 2)), ((0, 0), (5, 0)), ((12, 0), (2, 2)), ((17, 2), (7, 0))])
Step 2: Move Type - None, Idle Move - None
vdp dict dict_keys([((7, 0), (6, 0)), ((0, 0), (5, 0)), ((15, 2), (10, 0)), ((12, 0), (17, 2)), ((18, 2), (6, 2))])
Step 3: Move Type - None, Idle Move - None
vdp dict dict_keys([((10, 0), (15, 2)), ((6, 0), (0, 0)), ((12, 0), (18, 2)), ((1, 0), (12, 2))])
In the first layer one can see that a tree for moving a data qubit was found.
A larger example, with larger absolute improvement, can be found in the notebook mentioned above: scripts/cococo/movable_qubit_router_examples.ipynb.