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

SABRE + CircuitMap Optimization #1419

Merged
merged 25 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ddbc78d
update v1
Aug 14, 2024
0111a6e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 14, 2024
18f4d66
Update src/qibo/transpiler/router.py
csookim Aug 26, 2024
47ef393
change func blocks_qubits_pairs name
Aug 26, 2024
204b9ac
add _update_mappings_swap / remove swap_l, swap_p
Aug 26, 2024
9917b5d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 26, 2024
8eccc76
add setter and getter of p2l and l2p
Aug 26, 2024
4fe9f7d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 26, 2024
6fe1749
Merge branch 'master' into router_sabre
Aug 27, 2024
2a3438e
circuit -> circuit_map / docstring / one-line for
Aug 28, 2024
7d128ae
Update src/qibo/transpiler/router.py
csookim Aug 28, 2024
e46f341
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 28, 2024
1506b78
enable custom qubit names / wire_names
Aug 29, 2024
19d3737
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 29, 2024
32364fc
add helper func / update test func
Sep 5, 2024
2b8cdc0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 5, 2024
387afb4
add test func
Sep 5, 2024
6dd6ced
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 5, 2024
c5a4af8
Update src/qibo/transpiler/router.py
csookim Sep 9, 2024
9ba69dc
Update src/qibo/transpiler/router.py
csookim Sep 9, 2024
98fd9f1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 9, 2024
8641a0c
remove comments
Sep 9, 2024
fced73c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 9, 2024
5e25ecf
merge master
Sep 9, 2024
24ecc7a
Merge branch 'master' into router_sabre
Sep 16, 2024
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
2 changes: 1 addition & 1 deletion src/qibo/transpiler/placer.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def assert_mapping_consistency(layout: dict, connectivity: nx.Graph = None):
ref_keys = (
["q" + str(i) for i in nodes] if isinstance(physical_qubits[0], str) else nodes
)
if physical_qubits != ref_keys:
if sorted(physical_qubits) != sorted(ref_keys):
raise_error(
PlacementError,
"Some physical qubits in the layout may be missing or duplicated.",
Expand Down
33 changes: 20 additions & 13 deletions src/qibo/transpiler/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,27 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit):
if len(gate.qubits) > 2 and not isinstance(gate, gates.M):
raise_error(ConnectivityError, f"{gate.name} acts on more than two qubits.")
if len(gate.qubits) == 2:
if (gate.qubits[0], gate.qubits[1]) not in connectivity.edges:
# physical_qubits = tuple(sorted((circuit.wire_names[gate.qubits[0]], circuit.wire_names[gate.qubits[1]])))
physical_qubits = tuple(sorted(gate.qubits)) # for q_i naming
if physical_qubits not in connectivity.edges:
raise_error(
ConnectivityError,
f"Circuit does not respect connectivity. {gate.name} acts on {gate.qubits}.",
f"Circuit does not respect connectivity. {gate.name} acts on {physical_qubits}.",
csookim marked this conversation as resolved.
Show resolved Hide resolved
)


def _relabel_connectivity(connectivity, initial_layout):
node_mapping = {}
initial_layout = dict(
sorted(initial_layout.items(), key=lambda item: int(item[0][1:]))
) # for q_i naming
for i, node in enumerate(list(initial_layout.keys())):
# node_mapping[node] = i
node_mapping[int(node[1:])] = i # for q_i naming
new_connectivity = nx.relabel_nodes(connectivity, node_mapping)
return new_connectivity
csookim marked this conversation as resolved.
Show resolved Hide resolved


class StarConnectivityRouter(Router):
"""Transforms an arbitrary circuit to one that can be executed on hardware.

Expand Down Expand Up @@ -183,16 +197,15 @@ def __init__(
self._temporary = temp
if self._temporary: # 2# if temporary circuit, no need to store the blocks
return

self._nqubits = circuit.nqubits # 1# number of qubits
if circuit is None:
elif circuit is None:
raise_error(ValueError, "Circuit must be provided.")

if blocks is not None:
self.circuit_blocks = blocks
else:
self.circuit_blocks = CircuitBlocks(circuit, index_names=True)

self._nqubits = circuit.nqubits # 1# number of qubits
self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits))
self._swaps = 0

Expand Down Expand Up @@ -584,10 +597,7 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict):
"""

# 1# Relabel the nodes of the connectivity graph
node_mapping = {}
for i, node in enumerate(list(initial_layout.keys())):
node_mapping[node] = i
self.connectivity = nx.relabel_nodes(self.connectivity, node_mapping)
self.connectivity = _relabel_connectivity(self.connectivity, initial_layout)

copied_circuit = circuit.copy(deep=True)
self._final_measurements = self._detach_final_measurements(copied_circuit)
Expand Down Expand Up @@ -747,10 +757,7 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict):
"""

# 1# Relabel the nodes of the connectivity graph
node_mapping = {}
for i, node in enumerate(list(initial_layout.keys())):
node_mapping[node] = i
self.connectivity = nx.relabel_nodes(self.connectivity, node_mapping)
self.connectivity = _relabel_connectivity(self.connectivity, initial_layout)

copied_circuit = circuit.copy(deep=True)
self._final_measurements = self._detach_final_measurements(copied_circuit)
Expand Down
155 changes: 119 additions & 36 deletions tests/test_transpiler_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ def grid_connectivity():
return chip


def line_connectivity(n):
Q = [i for i in range(n)]
chip = nx.Graph()
chip.add_nodes_from(Q)
graph_list = [(Q[i], (Q[i] + 1) % n) for i in range(n - 1)]
chip.add_edges_from(graph_list)
return chip


def generate_random_circuit(nqubits, ngates, seed=42):
"""Generate a random circuit with RX and CZ gates."""
np.random.seed(seed)
Expand Down Expand Up @@ -136,6 +145,29 @@ def test_random_circuits_5q(gates, placer, connectivity):
)


def test_random_circuits_15q_50g():
nqubits, ngates = 15, 50
connectivity = line_connectivity(nqubits)
placer = Random(connectivity=connectivity)
layout_circ = Circuit(nqubits)
initial_layout = placer(layout_circ)
transpiler = Sabre(connectivity=connectivity)
circuit = generate_random_circuit(nqubits=nqubits, ngates=ngates)
transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout)
assert transpiler.added_swaps >= 0
assert_connectivity(connectivity, transpiled_circuit)
assert_placement(transpiled_circuit, final_qubit_map)
assert ngates + transpiler.added_swaps == transpiled_circuit.ngates
qubit_matcher = Preprocessing(connectivity=connectivity)
new_circuit = qubit_matcher(circuit=circuit)
assert_circuit_equivalence(
original_circuit=new_circuit,
transpiled_circuit=transpiled_circuit,
final_map=final_qubit_map,
initial_map=initial_layout,
)


def test_star_circuit():
placer = Subgraph(star_connectivity())
initial_layout = placer(star_circuit())
Expand Down Expand Up @@ -272,40 +304,72 @@ def test_circuit_map():
circ.add(gates.CZ(0, 1))
circ.add(gates.CZ(2, 3))
initial_layout = {"q0": 2, "q1": 0, "q2": 1, "q3": 3}
# circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ)
# block_list = circuit_map.circuit_blocks
# # test blocks_qubits_pairs
# assert circuit_map.blocks_qubits_pairs() == [(0, 1), (1, 2), (0, 1), (2, 3)]
# # test execute_block and routed_circuit
# circuit_map.execute_block(block_list.search_by_index(0))
# routed_circuit = circuit_map.routed_circuit()
# assert isinstance(routed_circuit.queue[0], gates.H)
# assert len(routed_circuit.queue) == 4
# assert routed_circuit.queue[2].qubits == (1, 2)
# # test update
# circuit_map.update((0, 2))
# routed_circuit = circuit_map.routed_circuit()
# assert isinstance(routed_circuit.queue[4], gates.SWAP)
# assert routed_circuit.queue[4].qubits == (1, 0)
# assert circuit_map._swaps == 1
# assert circuit_map._circuit_logical == [2, 1, 0, 3]
# circuit_map.update((1, 2))
# routed_circuit = circuit_map.routed_circuit()
# assert routed_circuit.queue[5].qubits == (2, 0)
# assert circuit_map._circuit_logical == [1, 2, 0, 3]
circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ)
block_list = circuit_map.circuit_blocks
# test blocks_qubits_pairs
assert circuit_map.blocks_logical_qubits_pairs() == [(0, 1), (1, 2), (0, 1), (2, 3)]
# test execute_block and routed_circuit
circuit_map.execute_block(block_list.search_by_index(0))
routed_circuit = circuit_map.routed_circuit()
assert isinstance(routed_circuit.queue[0], gates.H)
assert len(routed_circuit.queue) == 4
qubits = routed_circuit.queue[2].qubits
assert (
routed_circuit.wire_names[qubits[0]] == "q1"
and routed_circuit.wire_names[qubits[1]] == "q2"
)

# test update 1
circuit_map.update((0, 2))
routed_circuit = circuit_map.routed_circuit()
assert isinstance(routed_circuit.queue[4], gates.SWAP)
qubits = routed_circuit.queue[4].qubits
assert (
routed_circuit.wire_names[qubits[0]] == "q1"
and routed_circuit.wire_names[qubits[1]] == "q0"
)
assert circuit_map._swaps == 1
assert circuit_map.physical_to_logical == [0, 2, 1, 3]
assert circuit_map.logical_to_physical == [0, 2, 1, 3]

# test update 2
circuit_map.update((1, 2))
routed_circuit = circuit_map.routed_circuit()
assert isinstance(routed_circuit.queue[5], gates.SWAP)
qubits = routed_circuit.queue[5].qubits
assert (
routed_circuit.wire_names[qubits[0]] == "q2"
and routed_circuit.wire_names[qubits[1]] == "q1"
)
assert circuit_map._swaps == 2
assert circuit_map.physical_to_logical == [0, 1, 2, 3]
assert circuit_map.logical_to_physical == [0, 1, 2, 3]

# # test execute_block after multiple swaps
# circuit_map.execute_block(block_list.search_by_index(1))
# circuit_map.execute_block(block_list.search_by_index(2))
# circuit_map.execute_block(block_list.search_by_index(3))
# routed_circuit = circuit_map.routed_circuit()
# assert isinstance(routed_circuit.queue[6], gates.CZ)
# # circuit to logical map: [1,2,0,3]. initial map: {"q0": 2, "q1": 0, "q2": 1, "q3": 3}.
# assert routed_circuit.queue[6].qubits == (0, 1) # initial circuit qubits (1,2)
# assert routed_circuit.queue[7].qubits == (2, 0) # (0,1)
# assert routed_circuit.queue[8].qubits == (1, 3) # (2,3)
# assert len(circuit_map.circuit_blocks()) == 0
# # test final layout
# assert circuit_map.final_layout() == {"q0": 1, "q1": 2, "q2": 0, "q3": 3}
circuit_map.execute_block(block_list.search_by_index(1))
circuit_map.execute_block(block_list.search_by_index(2))
circuit_map.execute_block(block_list.search_by_index(3))
routed_circuit = circuit_map.routed_circuit()
assert isinstance(routed_circuit.queue[6], gates.CZ)

qubits = routed_circuit.queue[6].qubits
assert (
routed_circuit.wire_names[qubits[0]] == "q1"
and routed_circuit.wire_names[qubits[1]] == "q2"
)
qubits = routed_circuit.queue[7].qubits
assert (
routed_circuit.wire_names[qubits[0]] == "q0"
and routed_circuit.wire_names[qubits[1]] == "q1"
)
qubits = routed_circuit.queue[8].qubits
assert (
routed_circuit.wire_names[qubits[0]] == "q2"
and routed_circuit.wire_names[qubits[1]] == "q3"
)
assert len(circuit_map.circuit_blocks()) == 0
# test final layout
assert circuit_map.final_layout() == {"q0": 0, "q1": 1, "q2": 2, "q3": 3}


def test_sabre_matched():
Expand Down Expand Up @@ -483,17 +547,36 @@ def test_undo():
# 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 circuit_map.physical_to_logical == [0, 3, 1, 2]
assert circuit_map.logical_to_physical == [0, 2, 3, 1]
assert len(circuit_map._routed_blocks.block_list) == 2
assert circuit_map._swaps == 2

# Undo the last SWAP gate
circuit_map.undo()
# assert circuit_map._circuit_logical == [0, 2, 1, 3]
assert circuit_map.physical_to_logical == [0, 2, 1, 3]
assert circuit_map.logical_to_physical == [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.physical_to_logical == [0, 1, 2, 3]
assert circuit_map.logical_to_physical == [0, 1, 2, 3]
assert circuit_map._swaps == 0
assert len(circuit_map._routed_blocks.block_list) == 0


def test_circuitmap_no_circuit():
# If a `CircuitMap` is not a temporary instance and is created without a circuit, it should raise an error.
with pytest.raises(ValueError):
circuit_map = CircuitMap()


def test_logical_to_physical_setter():
circ = Circuit(4)
initial_layout = {"q0": 0, "q1": 3, "q2": 2, "q3": 1}
circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ)
circuit_map.logical_to_physical = [2, 0, 1, 3]
assert circuit_map.logical_to_physical == [2, 0, 1, 3]
assert circuit_map.physical_to_logical == [1, 2, 0, 3]