-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Implements the LineInititialMapper strategy #5831
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
Changes from 57 commits
62100c1
9df051b
8610ae2
d9a7a3c
c774674
a06c240
c12cc03
83e976f
24acc3d
2e1cec7
79061de
dfe80a9
cb798dc
6ee60a9
0c6de8b
ecadfdb
7819263
5dd4577
cc79064
fecc4b0
b3a1445
9bcf54f
f4dae72
9ecb52a
719b284
8cbbe8f
735cba8
9ba9cef
211bb2d
82cdbc0
632dfe6
fca9052
34ef897
67546d8
e120be2
0d2345c
209cc35
2544069
4493826
8827b91
70de81e
60ed0ac
eefc089
c7506bd
d92f515
d41cedb
7509aa9
c21eae2
854e867
bad20d3
26f14bd
4170236
6bed99a
5f4b848
e1de30a
3237bd7
64f6ba8
a2d8d2d
1b8c315
06ab64e
80b1f0e
0f1454d
7c4ebbd
9835eb1
c72de13
2ecb278
54e96ca
6989acc
6c8a661
b6c4bb3
cfe8704
c69a0db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| # Copyright 2022 The Cirq Developers | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """Concrete implementation of AbstractInitialMapper that places lines of qubits onto the device.""" | ||
|
|
||
| from typing import Dict, List, Set, TYPE_CHECKING | ||
| import networkx as nx | ||
|
|
||
| from cirq.transformers.routing import AbstractInitialMapper | ||
|
ammareltigani marked this conversation as resolved.
Outdated
|
||
| from cirq import protocols | ||
|
|
||
| if TYPE_CHECKING: | ||
| import cirq | ||
|
|
||
|
|
||
| class LineInitialMapper(AbstractInitialMapper): | ||
| """Places logical qubits in the circuit onto physical qubits on the device.""" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update docstring to reflect that specific strategy used by the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated the docstring in
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a brief description to class docstring as well, highlighting high level details like an overview of strategy, expected complexity etc. |
||
|
|
||
| def __init__(self, device_graph: nx.Graph) -> None: | ||
| """Initializes a LineInitialMapper. | ||
|
|
||
| Args: | ||
| device_graph: device graph | ||
| """ | ||
| self.device_graph = device_graph | ||
| self.mapped_physicals: Set['cirq.Qid'] = set() | ||
| self.partners: Dict['cirq.Qid', 'cirq.Qid'] = {} | ||
|
ammareltigani marked this conversation as resolved.
Outdated
|
||
|
|
||
| def _make_circuit_graph(self, circuit: 'cirq.AbstractCircuit') -> List[List['cirq.Qid']]: | ||
| """Creates a (potentially incomplete) qubit connectivity graph of the circuit. | ||
|
|
||
| Iterates over moments in the circuit from left to right and adds edges between logical | ||
| qubits if the logical qubit pair l1 and l2 | ||
| (1) have degree < 2, | ||
| (2) are involved in a 2-qubit operation in the current moment, and | ||
| (3) adding such an edge will not produce a cycle in the graph. | ||
|
|
||
| Args: | ||
| circuit: the input circuit with logical qubits | ||
|
|
||
| Returns: | ||
| The (potentially incomplete) qubit connectivity graph of the circuit, which is | ||
| guaranteed to be a forest of line graphs. | ||
| """ | ||
| circuit_graph: List[List['cirq.Qid']] = [[q] for q in sorted(circuit.all_qubits())] | ||
|
ammareltigani marked this conversation as resolved.
Outdated
|
||
| component_id: Dict['cirq.Qid', int] = {q[0]: i for i, q in enumerate(circuit_graph)} | ||
|
|
||
| def degree_lt_two(q: 'cirq.Qid'): | ||
| return any(circuit_graph[component_id[q]][i] == q for i in [-1, 0]) | ||
|
|
||
| for op in circuit.all_operations(): | ||
| if protocols.num_qubits(op) != 2: | ||
| continue | ||
|
|
||
| q0, q1 = op.qubits | ||
| c0, c1 = component_id[q0], component_id[q1] | ||
|
|
||
| # Keep track of partners for mapping isolated qubits later. | ||
| if q0 not in self.partners: | ||
| self.partners[q0] = q1 | ||
| if q1 not in self.partners: | ||
| self.partners[q1] = q0 | ||
|
|
||
| if not (degree_lt_two(q0) and degree_lt_two(q1) and c0 != c1): | ||
| continue | ||
|
|
||
| # Make sure c0/q0 are for the largest component. | ||
| if len(circuit_graph[c0]) < len(circuit_graph[c1]): | ||
| c0, c1, q0, q1 = c1, c0, q1, q0 | ||
|
|
||
| # copy smaller component into larger one. | ||
| if circuit_graph[c0][0] == q0: | ||
| if circuit_graph[c1][0] == q1: | ||
| for q in circuit_graph[c1]: | ||
| circuit_graph[c0].insert(0, q) | ||
|
ammareltigani marked this conversation as resolved.
Outdated
|
||
| component_id[q] = c0 | ||
| else: | ||
| for q in reversed(circuit_graph[c1]): | ||
| circuit_graph[c0].insert(0, q) | ||
| component_id[q] = c0 | ||
| else: | ||
| if circuit_graph[c1][0] == q1: | ||
| for q in circuit_graph[c1]: | ||
| circuit_graph[c0].append(q) | ||
| component_id[q] = c0 | ||
| else: | ||
| for q in reversed(circuit_graph[c1]): | ||
| circuit_graph[c0].append(q) | ||
| component_id[q] = c0 | ||
|
|
||
| return sorted([circuit_graph[c] for c in set(component_id.values())], key=len, reverse=True) | ||
|
|
||
| def initial_mapping(self, circuit: 'cirq.AbstractCircuit') -> Dict['cirq.Qid', 'cirq.Qid']: | ||
| """Maps disjoint lines of logical qubits onto lines of physical qubits. | ||
|
|
||
| Starting from the center physical qubit on the device, attempts to map disjoint lines of | ||
| logical qubits given by the circuit graph onto one long line of physical qubits on the | ||
| device, greedily maximizing each physical qubit's degree. | ||
| If this mapping cannot be completed as one long line of qubits in the circuit graph mapped | ||
| to qubits in the device graph, the line can be split as several line segments and then we: | ||
| (i) Map first line segment. | ||
| (ii) Find another high degree vertex in G near the center. | ||
| (iii) Map the second line segment | ||
| (iv) etc. | ||
| A line is split by mapping the next logical qubit to the nearest available physical qubit | ||
| to the center of the device graph. | ||
|
|
||
| Args: | ||
| circuit: the input circuit with logical qubits | ||
|
|
||
| Returns: | ||
| a dictionary that maps logical qubits in the circuit (keys) to physical qubits on the | ||
| device (values). | ||
| """ | ||
| qubit_map: Dict['cirq.Qid', 'cirq.Qid'] = {} | ||
| circuit_graph = self._make_circuit_graph(circuit) | ||
| physical_center = nx.center(self.device_graph)[0] | ||
|
ammareltigani marked this conversation as resolved.
Outdated
|
||
|
|
||
| def next_physical(current_physical: 'cirq.Qid') -> 'cirq.Qid': | ||
| # Greedily map to highest degree neighbor that that is available | ||
|
ammareltigani marked this conversation as resolved.
Outdated
|
||
| neighbors = sorted(self.device_graph.neighbors(current_physical)) | ||
| sorted_neighbors = sorted( | ||
| neighbors, | ||
| key=lambda x: self.device_graph.degree(x), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be sorting based on "unused degree", i.e.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll keep note of this change and try it during benchmarking! |
||
| reverse=True, | ||
| ) | ||
| for neighbor in sorted_neighbors: | ||
| if neighbor not in self.mapped_physicals: | ||
| return neighbor | ||
| # If cannot map onto one long line of physical qubits, then break down into multiple | ||
| # small lines by finding nearest available qubit to the physical center | ||
| return self._closest_unmapped_qubit(physical_center) | ||
|
|
||
| pq = physical_center | ||
| first_isolated_idx = len(circuit_graph) | ||
| for idx, logical_line in enumerate(circuit_graph): | ||
| if len(logical_line) == 1: | ||
| first_isolated_idx = idx | ||
| break | ||
|
|
||
| for lq in logical_line: | ||
| self.mapped_physicals.add(pq) | ||
| qubit_map[lq] = pq | ||
| # Edge case: if mapping n qubits on an n-qubit device should not call next_physical | ||
| # when finished mapping the last logical qubit else will raise an error | ||
| if len(circuit.all_qubits()) != len(self.mapped_physicals): | ||
|
ammareltigani marked this conversation as resolved.
Outdated
|
||
| pq = next_physical(pq) | ||
|
|
||
| for i in range(first_isolated_idx, len(circuit_graph)): | ||
| lq = circuit_graph[i][0] | ||
| partner = qubit_map[self.partners[lq]] if lq in self.partners else physical_center | ||
| pq = self._closest_unmapped_qubit(partner) | ||
| self.mapped_physicals.add(pq) | ||
| qubit_map[lq] = pq | ||
|
|
||
| return qubit_map | ||
|
tanujkhattar marked this conversation as resolved.
|
||
|
|
||
| def _closest_unmapped_qubit(self, source: 'cirq.Qid') -> 'cirq.Qid': | ||
| """Finds the closest available neighbor to a physical qubit 'source' on the device. | ||
|
|
||
| Args: | ||
| source: a physical qubit on the device. | ||
|
|
||
| Returns: | ||
| the closest available physical qubit to 'source'. | ||
|
|
||
| Raises: | ||
| ValueError: if there are no available qubits left on the device. | ||
| """ | ||
| for _, successors in nx.bfs_successors(self.device_graph, source): | ||
| for successor in successors: | ||
| if successor not in self.mapped_physicals: | ||
| return successor | ||
| raise ValueError("No available physical qubits left on the device.") | ||
|
|
||
| def __eq__(self, other) -> bool: | ||
| return nx.utils.graphs_equal(self.device_graph, other.device_graph) | ||
|
|
||
| def __repr__(self): | ||
| graph_type = type(self.device_graph).__name__ | ||
| return f'cirq.LineInitialMapper(nx.{graph_type}({dict(self.device_graph.adjacency())}))' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| # Copyright 2022 The Cirq Developers | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import networkx as nx | ||
| import pytest | ||
|
|
||
| import cirq | ||
|
|
||
|
|
||
| def construct_small_circuit(): | ||
| return cirq.Circuit( | ||
| [ | ||
| cirq.Moment(cirq.CNOT(cirq.NamedQubit('1'), cirq.NamedQubit('3'))), | ||
| cirq.Moment(cirq.CNOT(cirq.NamedQubit('2'), cirq.NamedQubit('3'))), | ||
| cirq.Moment( | ||
| cirq.CNOT(cirq.NamedQubit('4'), cirq.NamedQubit('3')), cirq.X(cirq.NamedQubit('5')) | ||
| ), | ||
| ] | ||
| ) | ||
|
|
||
|
|
||
| def construct_step_circuit(k: int): | ||
| q = cirq.LineQubit.range(k) | ||
| return cirq.Circuit([cirq.CNOT(q[i], q[i + 1]) for i in range(k - 1)]) | ||
|
|
||
|
|
||
| def test_line_breaking_on_grid_device(): | ||
| # tests | ||
| # -if strategy is able to map into several small lines if fails to map onto one long line | ||
| # -if # of physical qubits <= # of logical qubits then strategy should succeed | ||
|
|
||
| step_circuit = construct_step_circuit(49) | ||
| device = cirq.testing.construct_grid_device(7, 7) | ||
| device_graph = device.metadata.nx_graph | ||
| mapper = cirq.LineInitialMapper(device_graph) | ||
| mapping = mapper.initial_mapping(step_circuit) | ||
|
|
||
| # all qubits in the input circuit are placed on the device | ||
| assert set(mapping.keys()) == set(step_circuit.all_qubits()) | ||
|
|
||
| # the induced graph of the device on the physical qubits in the map is connected | ||
| assert nx.is_connected(nx.induced_subgraph(device_graph, mapping.values())) | ||
|
|
||
| step_circuit = construct_step_circuit(50) | ||
| with pytest.raises(ValueError, match="No available physical qubits left on the device"): | ||
| mapper.initial_mapping(step_circuit) | ||
|
|
||
|
|
||
| def test_small_circuit_on_grid_device(): | ||
| circuit = construct_small_circuit() | ||
|
|
||
| device_graph = cirq.testing.construct_grid_device(7, 7).metadata.nx_graph | ||
| mapper = cirq.LineInitialMapper(device_graph) | ||
| mapping = mapper.initial_mapping(circuit) | ||
|
|
||
| assert nx.center(device_graph)[0] == cirq.GridQubit(3, 3) | ||
| mapped_circuit = circuit.transform_qubits(mapping) | ||
| diagram = """(2, 2): ───@─────────── | ||
| │ | ||
| (2, 3): ───┼───────X─── | ||
| │ | ||
| (3, 2): ───X───X───X─── | ||
| │ │ | ||
| (3, 3): ───────@───┼─── | ||
| │ | ||
| (4, 2): ───────────@───""" | ||
| cirq.testing.assert_has_diagram(mapped_circuit, diagram) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "qubits, n_moments, op_density, random_state", | ||
| [ | ||
| (5 * size, 20 * size, density, seed) | ||
| for size in range(1, 3) | ||
| for seed in range(10) | ||
|
ammareltigani marked this conversation as resolved.
Outdated
|
||
| for density in [0.4, 0.5, 0.6] | ||
| ], | ||
| ) | ||
| def test_random_circuits_grid_device( | ||
| qubits: int, n_moments: int, op_density: float, random_state: int | ||
| ): | ||
| c_orig = cirq.testing.random_circuit( | ||
| qubits=qubits, n_moments=n_moments, op_density=op_density, random_state=random_state | ||
| ) | ||
| device = cirq.testing.construct_grid_device(7, 7) | ||
| device_graph = device.metadata.nx_graph | ||
| mapper = cirq.LineInitialMapper(device_graph) | ||
| mapping = mapper.initial_mapping(c_orig) | ||
|
|
||
| assert set(mapping.keys()) == set(c_orig.all_qubits()) | ||
| assert nx.is_connected(nx.induced_subgraph(device_graph, mapping.values())) | ||
|
|
||
|
|
||
| def test_repr(): | ||
| device_graph = cirq.testing.construct_grid_device(7, 7).metadata.nx_graph | ||
| mapper = cirq.LineInitialMapper(device_graph) | ||
| cirq.testing.assert_equivalent_repr(mapper, setup_code='import cirq\nimport networkx as nx') | ||
Uh oh!
There was an error while loading. Please reload this page.