Skip to content

Commit e690ca7

Browse files
Implements the LineInititialMapper strategy (quantumlib#5831)
This PR implements the main initial mapping strategy highlighted in http://tinyurl.com/cirq-qubit-routing
1 parent 2947d81 commit e690ca7

File tree

6 files changed

+422
-1
lines changed

6 files changed

+422
-1
lines changed

Diff for: cirq/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@
348348
expand_composite,
349349
HardCodedInitialMapper,
350350
is_negligible_turn,
351+
LineInitialMapper,
351352
MappingManager,
352353
map_moments,
353354
map_operations,

Diff for: cirq/protocols/json_test_data/spec.py

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
# Routing utilities
8787
'HardCodedInitialMapper',
8888
'MappingManager',
89+
'LineInitialMapper',
8990
# global objects
9091
'CONTROL_TAG',
9192
'PAULI_BASIS',

Diff for: cirq/transformers/__init__.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@
4444
two_qubit_gate_product_tabulation,
4545
)
4646

47-
from cirq.transformers.routing import AbstractInitialMapper, HardCodedInitialMapper, MappingManager
47+
48+
from cirq.transformers.routing import (
49+
AbstractInitialMapper,
50+
HardCodedInitialMapper,
51+
LineInitialMapper,
52+
MappingManager,
53+
)
4854

4955
from cirq.transformers.target_gatesets import (
5056
create_transformer_with_kwargs,

Diff for: cirq/transformers/routing/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616

1717
from cirq.transformers.routing.initial_mapper import AbstractInitialMapper, HardCodedInitialMapper
1818
from cirq.transformers.routing.mapping_manager import MappingManager
19+
from cirq.transformers.routing.line_initial_mapper import LineInitialMapper

Diff for: cirq/transformers/routing/line_initial_mapper.py

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Copyright 2022 The Cirq Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Maps logical to physical qubits by greedily placing lines of logical qubits on the device.
16+
17+
This is the default placement strategy used in the CQC router.
18+
19+
It first creates a partial connectivity graph between logical qubits in the given circuit and then
20+
maps these logical qubits on physical qubits on the device by starting at the center of the device
21+
and greedily choosing the highest degree neighbor.
22+
23+
If some logical qubits are unampped after this first procedure then there are two cases:
24+
(1) These unmammep logical qubits do interact in the circuit with some other logical partner.
25+
In this case we map such a qubit to the nearest available physical qubit on the device to the
26+
one that its partner was mapped to.
27+
28+
(2) These unampped logical qubits only have single qubit operations on them (i.e they do not
29+
interact with any other logical qubit at any point in the circuit). In this case we map them to
30+
the nearest available neighbor to the center of the device.
31+
"""
32+
33+
from typing import Deque, Dict, List, Set, Tuple, TYPE_CHECKING
34+
from collections import deque
35+
import networkx as nx
36+
37+
from cirq.transformers.routing import initial_mapper
38+
from cirq import protocols, value
39+
40+
if TYPE_CHECKING:
41+
import cirq
42+
43+
44+
@value.value_equality
45+
class LineInitialMapper(initial_mapper.AbstractInitialMapper):
46+
"""Places logical qubits in the circuit onto physical qubits on the device.
47+
48+
Starting from the center physical qubit on the device, attempts to map disjoint lines of
49+
logical qubits given by the circuit graph onto one long line of physical qubits on the
50+
device, greedily maximizing each physical qubit's degree.
51+
If this mapping cannot be completed as one long line of qubits in the circuit graph mapped
52+
to qubits in the device graph, the line can be split as several line segments and then we:
53+
(i) Map first line segment.
54+
(ii) Find another high degree vertex in G near the center.
55+
(iii) Map the second line segment
56+
(iv) etc.
57+
A line is split by mapping the next logical qubit to the nearest available physical qubit
58+
to the center of the device graph.
59+
60+
The expected runtime of this strategy is O(m logn + n^2) where m is the # of operations in the
61+
given circuit and n is the number of qubits. The first term corresponds to the runtime of
62+
'make_circuit_graph()' and the second for 'initial_mapping()'.
63+
"""
64+
65+
def __init__(self, device_graph: nx.Graph) -> None:
66+
"""Initializes a LineInitialMapper.
67+
68+
Args:
69+
device_graph: device graph
70+
"""
71+
if nx.is_directed(device_graph):
72+
self.device_graph = nx.DiGraph()
73+
self.device_graph.add_nodes_from(sorted(list(device_graph.nodes(data=True))))
74+
self.device_graph.add_edges_from(sorted(list(device_graph.edges)))
75+
else:
76+
self.device_graph = nx.Graph()
77+
self.device_graph.add_nodes_from(sorted(list(device_graph.nodes(data=True))))
78+
self.device_graph.add_edges_from(
79+
sorted(list(sorted(edge) for edge in device_graph.edges))
80+
)
81+
self.center = nx.center(self.device_graph)[0]
82+
83+
def _make_circuit_graph(
84+
self, circuit: 'cirq.AbstractCircuit'
85+
) -> Tuple[List[Deque['cirq.Qid']], Dict['cirq.Qid', 'cirq.Qid']]:
86+
"""Creates a (potentially incomplete) qubit connectivity graph of the circuit.
87+
88+
Iterates over moments in the circuit from left to right and adds edges between logical
89+
qubits if the logical qubit pair l1 and l2
90+
(1) have degree < 2,
91+
(2) are involved in a 2-qubit operation in the current moment, and
92+
(3) adding such an edge will not produce a cycle in the graph.
93+
94+
Args:
95+
circuit: the input circuit with logical qubits
96+
97+
Returns:
98+
The (potentially incomplete) qubit connectivity graph of the circuit, which is
99+
guaranteed to be a forest of line graphs.
100+
"""
101+
circuit_graph: List[Deque['cirq.Qid']] = [deque([q]) for q in sorted(circuit.all_qubits())]
102+
component_id: Dict['cirq.Qid', int] = {q[0]: i for i, q in enumerate(circuit_graph)}
103+
partners: Dict['cirq.Qid', 'cirq.Qid'] = {}
104+
105+
def degree_lt_two(q: 'cirq.Qid'):
106+
return any(circuit_graph[component_id[q]][i] == q for i in [-1, 0])
107+
108+
for op in circuit.all_operations():
109+
if protocols.num_qubits(op) != 2:
110+
continue
111+
112+
q0, q1 = op.qubits
113+
c0, c1 = component_id[q0], component_id[q1]
114+
# Keep track of partners for mapping isolated qubits later.
115+
partners[q0] = partners[q0] if q0 in partners else q1
116+
partners[q1] = partners[q1] if q1 in partners else q0
117+
118+
if not (degree_lt_two(q0) and degree_lt_two(q1) and c0 != c1):
119+
continue
120+
121+
# Make sure c0/q0 are for the largest component.
122+
if len(circuit_graph[c0]) < len(circuit_graph[c1]):
123+
c0, c1, q0, q1 = c1, c0, q1, q0
124+
125+
# copy smaller component into larger one.
126+
c1_order = (
127+
reversed(circuit_graph[c1])
128+
if circuit_graph[c1][-1] == q1
129+
else iter(circuit_graph[c1])
130+
)
131+
for q in c1_order:
132+
if circuit_graph[c0][0] == q0:
133+
circuit_graph[c0].appendleft(q)
134+
else:
135+
circuit_graph[c0].append(q)
136+
component_id[q] = c0
137+
138+
graph = sorted(
139+
[circuit_graph[c] for c in set(component_id.values())], key=len, reverse=True
140+
)
141+
return graph, partners
142+
143+
def initial_mapping(self, circuit: 'cirq.AbstractCircuit') -> Dict['cirq.Qid', 'cirq.Qid']:
144+
"""Maps disjoint lines of logical qubits onto lines of physical qubits.
145+
146+
Args:
147+
circuit: the input circuit with logical qubits
148+
149+
Returns:
150+
a dictionary that maps logical qubits in the circuit (keys) to physical qubits on the
151+
device (values).
152+
"""
153+
mapped_physicals: Set['cirq.Qid'] = set()
154+
qubit_map: Dict['cirq.Qid', 'cirq.Qid'] = {}
155+
circuit_graph, partners = self._make_circuit_graph(circuit)
156+
157+
def next_physical(
158+
current_physical: 'cirq.Qid', partner: 'cirq.Qid', isolated: bool = False
159+
) -> 'cirq.Qid':
160+
# Handle the first physical qubit getting mapped.
161+
if current_physical not in mapped_physicals:
162+
return current_physical
163+
# Greedily map to highest degree neighbor that is available
164+
if not isolated:
165+
sorted_neighbors = sorted(
166+
self.device_graph.neighbors(current_physical),
167+
key=lambda x: self.device_graph.degree(x),
168+
reverse=True,
169+
)
170+
for neighbor in sorted_neighbors:
171+
if neighbor not in mapped_physicals:
172+
return neighbor
173+
# If cannot map onto one long line of physical qubits, then break down into multiple
174+
# small lines by finding nearest available qubit to the physical center
175+
return self._closest_unmapped_qubit(partner, mapped_physicals)
176+
177+
pq = self.center
178+
for logical_line in circuit_graph:
179+
for lq in logical_line:
180+
is_isolated = len(logical_line) == 1
181+
partner = (
182+
qubit_map[partners[lq]] if (lq in partners and is_isolated) else self.center
183+
)
184+
pq = next_physical(pq, partner, isolated=is_isolated)
185+
mapped_physicals.add(pq)
186+
qubit_map[lq] = pq
187+
188+
return qubit_map
189+
190+
def _closest_unmapped_qubit(
191+
self, source: 'cirq.Qid', mapped_physicals: Set['cirq.Qid']
192+
) -> 'cirq.Qid':
193+
"""Finds the closest available neighbor to a physical qubit 'source' on the device.
194+
195+
Args:
196+
source: a physical qubit on the device.
197+
198+
Returns:
199+
the closest available physical qubit to 'source'.
200+
201+
Raises:
202+
ValueError: if there are no available qubits left on the device.
203+
"""
204+
for _, successors in nx.bfs_successors(self.device_graph, source):
205+
for successor in successors:
206+
if successor not in mapped_physicals:
207+
return successor
208+
raise ValueError("No available physical qubits left on the device.")
209+
210+
def _value_equality_values_(self):
211+
return (
212+
tuple(self.device_graph.nodes),
213+
tuple(self.device_graph.edges),
214+
nx.is_directed(self.device_graph),
215+
)
216+
217+
def __repr__(self):
218+
graph_type = type(self.device_graph).__name__
219+
return f'cirq.LineInitialMapper(nx.{graph_type}({dict(self.device_graph.adjacency())}))'

0 commit comments

Comments
 (0)