Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize shortestpath in SABRE #1408

Merged
merged 17 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/qibo/transpiler/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ def remove_block(self, block: "Block"):
"The block you are trying to remove is not present in the circuit blocks.",
)

def return_last_block(self):
"""Return the last block in the circuit blocks."""
if len(self.block_list) == 0:
raise_error(
BlockingError,
"No blocks found in the circuit blocks.",
)
return self.block_list[-1]


def block_decomposition(circuit: Circuit, fuse: bool = True):
"""Decompose a circuit into blocks of gates acting on two qubits.
Expand Down
1 change: 1 addition & 0 deletions src/qibo/transpiler/placer.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ def __init__(
):
self.connectivity = connectivity
self.routing_algorithm = routing_algorithm
self.routing_algorithm.connectivity = connectivity
self.depth = depth

def __call__(self, circuit: Circuit):
Expand Down
28 changes: 20 additions & 8 deletions src/qibo/transpiler/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,18 @@ def update(self, swap: tuple):
), self._circuit_logical.index(swap[1])
self._circuit_logical[idx_0], self._circuit_logical[idx_1] = swap[1], swap[0]

def undo(self):
"""Undo the last swap. Method works in-place."""
last_swap_block = self._routed_blocks.return_last_block()
swap = tuple(self.physical_to_logical(q) for q in last_swap_block.qubits)
self._routed_blocks.remove_block(last_swap_block)
self._swaps -= 1

idx_0, idx_1 = self._circuit_logical.index(
swap[0]
), self._circuit_logical.index(swap[1])
self._circuit_logical[idx_0], self._circuit_logical[idx_1] = swap[1], swap[0]

def get_logical_qubits(self, block: Block):
"""Returns the current logical qubits where a block is acting on.

Expand Down Expand Up @@ -659,8 +671,7 @@ def __init__(
self.circuit = None
self._memory_map = None
self._final_measurements = None
self._temporary_added_swaps = 0
self._saved_circuit = None
self._temp_added_swaps = []
random.seed(seed)

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

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

circuit_kwargs = circuit.init_kwargs
Expand Down Expand Up @@ -800,7 +813,7 @@ def _find_new_mapping(self):
for qubit in self.circuit.logical_to_physical(best_candidate, index=True):
self._delta_register[qubit] += self.delta
self.circuit.update(best_candidate)
self._temporary_added_swaps += 1
self._temp_added_swaps.append(best_candidate)

def _compute_cost(self, candidate: int):
"""Compute the cost associated to a possible SWAP candidate."""
Expand Down Expand Up @@ -897,8 +910,7 @@ def _execute_blocks(self, blocklist: list):
self._update_front_layer()
self._memory_map = []
self._delta_register = [1.0 for _ in self._delta_register]
self._temporary_added_swaps = 0
self._saved_circuit = deepcopy(self.circuit)
self._temp_added_swaps = []

def _shortest_path_routing(self):
"""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.
Expand Down
24 changes: 24 additions & 0 deletions tests/test_transpiler_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,27 @@ def test_block_on_qubits():
assert new_block.gates[2].qubits == (3,)
assert new_block.gates[3].qubits == (3, 2)
assert new_block.gates[4].qubits == (3,)


def test_return_last_block():
circ = Circuit(4)
circ.add(gates.CZ(0, 1))
circ.add(gates.CZ(1, 3))
circ.add(gates.CZ(1, 2))
circ.add(gates.CZ(2, 3))
circuit_blocks = CircuitBlocks(circ)
last_block = circuit_blocks.return_last_block()
assert_gates_equality(last_block.gates, [gates.CZ(2, 3)])

circuit_blocks.remove_block(last_block)
last_block_2 = circuit_blocks.return_last_block()
assert_gates_equality(last_block_2.gates, [gates.CZ(1, 2)])


def test_return_last_block_error():
circ = Circuit(4)
circuit_blocks = CircuitBlocks(circ)

# No blocks in the circuit
with pytest.raises(BlockingError):
last_block = circuit_blocks.return_last_block()
2 changes: 1 addition & 1 deletion tests/test_transpiler_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def test_custom_passes(placer, routing, gates, qubits):
@pytest.mark.parametrize("gates", [5, 20])
@pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal])
@pytest.mark.parametrize("routing", [ShortestPaths, Sabre])
def test_custom_passes_restict(gates, placer, routing):
def test_custom_passes_restrict(gates, placer, routing):
circ = generate_random_circuit(nqubits=3, ngates=gates)
custom_passes = []
custom_passes.append(Preprocessing(connectivity=star_connectivity()))
Expand Down
25 changes: 25 additions & 0 deletions tests/test_transpiler_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from qibo.models import Circuit
from qibo.quantum_info.random_ensembles import random_unitary
from qibo.transpiler._exceptions import ConnectivityError
from qibo.transpiler.blocks import Block
from qibo.transpiler.optimizer import Preprocessing
from qibo.transpiler.pipeline import (
assert_circuit_equivalence,
Expand Down Expand Up @@ -472,3 +473,27 @@ def test_star_router(nqubits, depth, middle_qubit, measurements, unitaries):
final_map=final_qubit_map,
initial_map=initial_layout,
)


def test_undo():
circ = Circuit(4)
initial_layout = {"q0": 0, "q1": 1, "q2": 2, "q3": 3}
circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ)

# Two SWAP gates are added
circuit_map.update((1, 2))
circuit_map.update((2, 3))
assert circuit_map._circuit_logical == [0, 3, 1, 2]
assert len(circuit_map._routed_blocks.block_list) == 2

# Undo the last SWAP gate
circuit_map.undo()
assert circuit_map._circuit_logical == [0, 2, 1, 3]
assert circuit_map._swaps == 1
assert len(circuit_map._routed_blocks.block_list) == 1

# Undo the first SWAP gate
circuit_map.undo()
assert circuit_map._circuit_logical == [0, 1, 2, 3]
assert circuit_map._swaps == 0
assert len(circuit_map._routed_blocks.block_list) == 0