Skip to content

Commit 2a1a504

Browse files
Merge pull request #1408 from qiboteam/router_sabre
Optimize shortestpath in SABRE
2 parents 3d87ba7 + 96a0bd8 commit 2a1a504

6 files changed

+80
-9
lines changed

src/qibo/transpiler/blocks.py

+9
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@ def remove_block(self, block: "Block"):
169169
"The block you are trying to remove is not present in the circuit blocks.",
170170
)
171171

172+
def return_last_block(self):
173+
"""Return the last block in the circuit blocks."""
174+
if len(self.block_list) == 0:
175+
raise_error(
176+
BlockingError,
177+
"No blocks found in the circuit blocks.",
178+
)
179+
return self.block_list[-1]
180+
172181

173182
def block_decomposition(circuit: Circuit, fuse: bool = True):
174183
"""Decompose a circuit into blocks of gates acting on two qubits.

src/qibo/transpiler/placer.py

+1
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ def __init__(
411411
):
412412
self.connectivity = connectivity
413413
self.routing_algorithm = routing_algorithm
414+
self.routing_algorithm.connectivity = connectivity
414415
self.depth = depth
415416

416417
def __call__(self, circuit: Circuit):

src/qibo/transpiler/router.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,18 @@ def update(self, swap: tuple):
257257
), self._circuit_logical.index(swap[1])
258258
self._circuit_logical[idx_0], self._circuit_logical[idx_1] = swap[1], swap[0]
259259

260+
def undo(self):
261+
"""Undo the last swap. Method works in-place."""
262+
last_swap_block = self._routed_blocks.return_last_block()
263+
swap = tuple(self.physical_to_logical(q) for q in last_swap_block.qubits)
264+
self._routed_blocks.remove_block(last_swap_block)
265+
self._swaps -= 1
266+
267+
idx_0, idx_1 = self._circuit_logical.index(
268+
swap[0]
269+
), self._circuit_logical.index(swap[1])
270+
self._circuit_logical[idx_0], self._circuit_logical[idx_1] = swap[1], swap[0]
271+
260272
def get_logical_qubits(self, block: Block):
261273
"""Returns the current logical qubits where a block is acting on.
262274
@@ -659,8 +671,7 @@ def __init__(
659671
self.circuit = None
660672
self._memory_map = None
661673
self._final_measurements = None
662-
self._temporary_added_swaps = 0
663-
self._saved_circuit = None
674+
self._temp_added_swaps = []
664675
random.seed(seed)
665676

666677
def __call__(self, circuit: Circuit, initial_layout: dict):
@@ -674,7 +685,6 @@ def __call__(self, circuit: Circuit, initial_layout: dict):
674685
(:class:`qibo.models.circuit.Circuit`, dict): routed circuit and final layout.
675686
"""
676687
self._preprocessing(circuit=circuit, initial_layout=initial_layout)
677-
self._saved_circuit = deepcopy(self.circuit)
678688
longest_path = np.max(self._dist_matrix)
679689

680690
while self._dag.number_of_nodes() != 0:
@@ -687,9 +697,12 @@ def __call__(self, circuit: Circuit, initial_layout: dict):
687697
# If the number of added swaps is too high, the algorithm is stuck.
688698
# Reset the circuit to the last saved state and make the nearest gate executable by manually adding SWAPs.
689699
if (
690-
self._temporary_added_swaps > self.swap_threshold * longest_path
700+
len(self._temp_added_swaps) > self.swap_threshold * longest_path
691701
): # threshold is arbitrary
692-
self.circuit = deepcopy(self._saved_circuit)
702+
while self._temp_added_swaps:
703+
swap = self._temp_added_swaps.pop()
704+
self.circuit.undo()
705+
self._temp_added_swaps = []
693706
self._shortest_path_routing()
694707

695708
circuit_kwargs = circuit.init_kwargs
@@ -800,7 +813,7 @@ def _find_new_mapping(self):
800813
for qubit in self.circuit.logical_to_physical(best_candidate, index=True):
801814
self._delta_register[qubit] += self.delta
802815
self.circuit.update(best_candidate)
803-
self._temporary_added_swaps += 1
816+
self._temp_added_swaps.append(best_candidate)
804817

805818
def _compute_cost(self, candidate: int):
806819
"""Compute the cost associated to a possible SWAP candidate."""
@@ -897,8 +910,7 @@ def _execute_blocks(self, blocklist: list):
897910
self._update_front_layer()
898911
self._memory_map = []
899912
self._delta_register = [1.0 for _ in self._delta_register]
900-
self._temporary_added_swaps = 0
901-
self._saved_circuit = deepcopy(self.circuit)
913+
self._temp_added_swaps = []
902914

903915
def _shortest_path_routing(self):
904916
"""Route a gate in the front layer using the shortest path. This method is executed when the standard SABRE fails to find an optimized solution.

tests/test_transpiler_blocks.py

+24
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,27 @@ def test_block_on_qubits():
367367
assert new_block.gates[2].qubits == (3,)
368368
assert new_block.gates[3].qubits == (3, 2)
369369
assert new_block.gates[4].qubits == (3,)
370+
371+
372+
def test_return_last_block():
373+
circ = Circuit(4)
374+
circ.add(gates.CZ(0, 1))
375+
circ.add(gates.CZ(1, 3))
376+
circ.add(gates.CZ(1, 2))
377+
circ.add(gates.CZ(2, 3))
378+
circuit_blocks = CircuitBlocks(circ)
379+
last_block = circuit_blocks.return_last_block()
380+
assert_gates_equality(last_block.gates, [gates.CZ(2, 3)])
381+
382+
circuit_blocks.remove_block(last_block)
383+
last_block_2 = circuit_blocks.return_last_block()
384+
assert_gates_equality(last_block_2.gates, [gates.CZ(1, 2)])
385+
386+
387+
def test_return_last_block_error():
388+
circ = Circuit(4)
389+
circuit_blocks = CircuitBlocks(circ)
390+
391+
# No blocks in the circuit
392+
with pytest.raises(BlockingError):
393+
last_block = circuit_blocks.return_last_block()

tests/test_transpiler_pipeline.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def test_custom_passes(placer, routing, gates, qubits):
216216
@pytest.mark.parametrize("gates", [5, 20])
217217
@pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal])
218218
@pytest.mark.parametrize("routing", [ShortestPaths, Sabre])
219-
def test_custom_passes_restict(gates, placer, routing):
219+
def test_custom_passes_restrict(gates, placer, routing):
220220
circ = generate_random_circuit(nqubits=3, ngates=gates)
221221
custom_passes = []
222222
custom_passes.append(Preprocessing(connectivity=star_connectivity()))

tests/test_transpiler_router.py

+25
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from qibo.models import Circuit
1010
from qibo.quantum_info.random_ensembles import random_unitary
1111
from qibo.transpiler._exceptions import ConnectivityError
12+
from qibo.transpiler.blocks import Block
1213
from qibo.transpiler.optimizer import Preprocessing
1314
from qibo.transpiler.pipeline import (
1415
assert_circuit_equivalence,
@@ -472,3 +473,27 @@ def test_star_router(nqubits, depth, middle_qubit, measurements, unitaries):
472473
final_map=final_qubit_map,
473474
initial_map=initial_layout,
474475
)
476+
477+
478+
def test_undo():
479+
circ = Circuit(4)
480+
initial_layout = {"q0": 0, "q1": 1, "q2": 2, "q3": 3}
481+
circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ)
482+
483+
# Two SWAP gates are added
484+
circuit_map.update((1, 2))
485+
circuit_map.update((2, 3))
486+
assert circuit_map._circuit_logical == [0, 3, 1, 2]
487+
assert len(circuit_map._routed_blocks.block_list) == 2
488+
489+
# Undo the last SWAP gate
490+
circuit_map.undo()
491+
assert circuit_map._circuit_logical == [0, 2, 1, 3]
492+
assert circuit_map._swaps == 1
493+
assert len(circuit_map._routed_blocks.block_list) == 1
494+
495+
# Undo the first SWAP gate
496+
circuit_map.undo()
497+
assert circuit_map._circuit_logical == [0, 1, 2, 3]
498+
assert circuit_map._swaps == 0
499+
assert len(circuit_map._routed_blocks.block_list) == 0

0 commit comments

Comments
 (0)