Skip to content

Commit a778e13

Browse files
MichaelBroughtonrht
authored andcommitted
Deprecate qid_pairs (quantumlib#4900)
Deprecate `qid_pairs`. Next step on quantumlib#4744 . This also makes a small update to the DeviceMetadata to make optional fields required (since the metadata property itself is optional this seems fine).
1 parent cda9841 commit a778e13

File tree

12 files changed

+141
-134
lines changed

12 files changed

+141
-134
lines changed

cirq-core/cirq/contrib/graph_device/graph_device.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from typing import Iterable, Optional, FrozenSet, TYPE_CHECKING, Tuple, cast
1919

20-
from cirq import devices, ops, value
20+
from cirq import _compat, devices, ops, value
2121

2222
from cirq.contrib.graph_device.hypergraph import UndirectedHypergraph
2323

@@ -162,14 +162,19 @@ def qubit_set(self) -> FrozenSet['cirq.Qid']:
162162
def edges(self):
163163
return tuple(sorted(self.device_graph.edges))
164164

165+
@_compat.deprecated(
166+
deadline='v0.15',
167+
fix='qubit coupling data can now be found in device.metadata.nx_graph if provided.',
168+
)
165169
def qid_pairs(self) -> FrozenSet['cirq.SymmetricalQidPair']:
166-
return frozenset(
167-
[
168-
devices.SymmetricalQidPair(*edge) # type: ignore
169-
for edge in self.device_graph.edges
170-
if len(edge) == 2 and all(isinstance(q, ops.Qid) for q in edge)
171-
]
172-
)
170+
with _compat.block_overlapping_deprecation('device\\.metadata'):
171+
return frozenset(
172+
[
173+
devices.SymmetricalQidPair(*edge) # type: ignore
174+
for edge in self.device_graph.edges
175+
if len(edge) == 2 and all(isinstance(q, ops.Qid) for q in edge)
176+
]
177+
)
173178

174179
@property
175180
def labelled_edges(self):

cirq-core/cirq/contrib/graph_device/graph_device_test.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,9 @@ def test_qubit_set():
182182
assert device.qubit_set() == {a, b, c, d}
183183

184184

185-
def test_qid_pairs():
185+
def test_qid_pairs_deprecated():
186186
a, b, c, d = cirq.LineQubit.range(4)
187187
device_graph = ccgd.UndirectedHypergraph(labelled_edges={(a, b): None, (c, d): None})
188188
device = ccgd.UndirectedGraphDevice(device_graph=device_graph)
189-
assert len(device.qid_pairs()) == 2
189+
with cirq.testing.assert_deprecated('device.metadata', deadline='v0.15', count=1):
190+
assert len(device.qid_pairs()) == 2

cirq-core/cirq/devices/device.py

+43-51
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@
2222
Iterator,
2323
Iterable,
2424
)
25-
2625
import networkx as nx
27-
from cirq import value
26+
from cirq import _compat, value
2827
from cirq.devices.grid_qubit import _BaseGridQid
2928
from cirq.devices.line_qubit import _BaseLineQid
3029

@@ -59,6 +58,10 @@ def qubit_set(self) -> Optional[AbstractSet['cirq.Qid']]:
5958
# Default to the qubits being unknown.
6059
return None
6160

61+
@_compat.deprecated(
62+
deadline='v0.15',
63+
fix='qubit coupling data can now be found in device.metadata if provided.',
64+
)
6265
def qid_pairs(self) -> Optional[FrozenSet['cirq.SymmetricalQidPair']]:
6366
"""Returns a set of qubit edges on the device, if possible.
6467
@@ -74,27 +77,28 @@ def qid_pairs(self) -> Optional[FrozenSet['cirq.SymmetricalQidPair']]:
7477
`cirq.UnconstrainedDevice` has this property), then `None` is
7578
returned.
7679
"""
77-
qs = self.qubit_set()
78-
if qs is None:
79-
return None
80-
if all(isinstance(q, _BaseGridQid) for q in qs):
81-
return frozenset(
82-
[
83-
SymmetricalQidPair(q, q2)
84-
for q in [cast(_BaseGridQid, q) for q in qs]
85-
for q2 in [q + (0, 1), q + (1, 0)]
86-
if q2 in qs
87-
]
88-
)
89-
if all(isinstance(q, _BaseLineQid) for q in qs):
90-
return frozenset(
91-
[
92-
SymmetricalQidPair(q, q + 1)
93-
for q in [cast(_BaseLineQid, q) for q in qs]
94-
if q + 1 in qs
95-
]
96-
)
97-
return frozenset([SymmetricalQidPair(q, q2) for q in qs for q2 in qs if q < q2])
80+
with _compat.block_overlapping_deprecation('device\\.metadata'):
81+
qs = self.qubit_set()
82+
if qs is None:
83+
return None
84+
if all(isinstance(q, _BaseGridQid) for q in qs):
85+
return frozenset(
86+
[
87+
SymmetricalQidPair(q, q2)
88+
for q in [cast(_BaseGridQid, q) for q in qs]
89+
for q2 in [q + (0, 1), q + (1, 0)]
90+
if q2 in qs
91+
]
92+
)
93+
if all(isinstance(q, _BaseLineQid) for q in qs):
94+
return frozenset(
95+
[
96+
SymmetricalQidPair(q, q + 1)
97+
for q in [cast(_BaseLineQid, q) for q in qs]
98+
if q + 1 in qs
99+
]
100+
)
101+
return frozenset([SymmetricalQidPair(q, q2) for q in qs for q2 in qs if q < q2])
98102

99103
def decompose_operation(self, operation: 'cirq.Operation') -> 'cirq.OP_TREE':
100104
"""Returns a device-valid decomposition for the given operation.
@@ -166,6 +170,10 @@ def can_add_operation_into_moment(
166170
return not moment.operates_on(operation.qubits)
167171

168172

173+
@_compat.deprecated_class(
174+
deadline='v0.15',
175+
fix='Qid coupling information can now be found in device.metadata if applicable.',
176+
)
169177
@value.value_equality
170178
class SymmetricalQidPair:
171179
def __init__(self, qid1: 'cirq.Qid', qid2: 'cirq.Qid'):
@@ -204,8 +212,8 @@ class DeviceMetadata:
204212

205213
def __init__(
206214
self,
207-
qubits: Optional[Iterable['cirq.Qid']] = None,
208-
nx_graph: Optional['nx.graph'] = None,
215+
qubits: Iterable['cirq.Qid'],
216+
nx_graph: 'nx.graph',
209217
):
210218
"""Construct a DeviceMetadata object.
211219
@@ -216,16 +224,11 @@ def __init__(
216224
directional coupling, undirected edges indicate bi-directional
217225
coupling.
218226
"""
219-
if qubits is not None:
220-
qubits = frozenset(qubits)
221-
self._qubits_set: Optional[FrozenSet['cirq.Qid']] = (
222-
None if qubits is None else frozenset(qubits)
223-
)
224-
227+
self._qubits_set: FrozenSet['cirq.Qid'] = frozenset(qubits)
225228
self._nx_graph = nx_graph
226229

227230
@property
228-
def qubit_set(self) -> Optional[FrozenSet['cirq.Qid']]:
231+
def qubit_set(self) -> FrozenSet['cirq.Qid']:
229232
"""Returns a set of qubits on the device, if possible.
230233
231234
Returns:
@@ -234,7 +237,7 @@ def qubit_set(self) -> Optional[FrozenSet['cirq.Qid']]:
234237
return self._qubits_set
235238

236239
@property
237-
def nx_graph(self) -> Optional['nx.Graph']:
240+
def nx_graph(self) -> 'nx.Graph':
238241
"""Returns a nx.Graph where nodes are qubits and edges are couple-able qubits.
239242
240243
Returns:
@@ -243,31 +246,20 @@ def nx_graph(self) -> Optional['nx.Graph']:
243246
return self._nx_graph
244247

245248
def _value_equality_values_(self):
246-
graph_equality = None
247-
if self._nx_graph is not None:
248-
graph_equality = (
249-
tuple(sorted(self._nx_graph.nodes())),
250-
tuple(sorted(self._nx_graph.edges(data='directed'))),
251-
)
249+
graph_equality = (
250+
tuple(sorted(self._nx_graph.nodes())),
251+
tuple(sorted(self._nx_graph.edges(data='directed'))),
252+
)
252253

253254
return self._qubits_set, graph_equality
254255

255256
def _json_dict_(self):
256-
graph_payload = ''
257-
if self._nx_graph is not None:
258-
graph_payload = nx.readwrite.json_graph.node_link_data(self._nx_graph)
259-
260-
qubits_payload = ''
261-
if self._qubits_set is not None:
262-
qubits_payload = sorted(list(self._qubits_set))
257+
graph_payload = nx.readwrite.json_graph.node_link_data(self._nx_graph)
258+
qubits_payload = sorted(list(self._qubits_set))
263259

264260
return {'qubits': qubits_payload, 'nx_graph': graph_payload}
265261

266262
@classmethod
267263
def _from_json_dict_(cls, qubits, nx_graph, **kwargs):
268-
if qubits == '':
269-
qubits = None
270-
graph_obj = None
271-
if nx_graph != '':
272-
graph_obj = nx.readwrite.json_graph.node_link_graph(nx_graph)
264+
graph_obj = nx.readwrite.json_graph.node_link_graph(nx_graph)
273265
return cls(qubits, graph_obj)

cirq-core/cirq/devices/device_test.py

+17-26
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,26 @@ def test_qid_pairs():
3939
class RawDevice(cirq.Device):
4040
pass
4141

42-
assert RawDevice().qid_pairs() is None
42+
with cirq.testing.assert_deprecated('device.metadata', deadline='v0.15', count=1):
43+
assert RawDevice().qid_pairs() is None
4344

4445
class QubitFieldDevice(cirq.Device):
4546
def __init__(self, qubits):
4647
self.qubits = qubits
4748

48-
assert len(QubitFieldDevice(cirq.LineQubit.range(10)).qid_pairs()) == 9
49-
assert len(QubitFieldDevice(cirq.GridQubit.rect(10, 10)).qid_pairs()) == 180
50-
assert len(QubitFieldDevice([cirq.NamedQubit(str(s)) for s in range(10)]).qid_pairs()) == 45
49+
with cirq.testing.assert_deprecated('device.metadata', deadline='v0.15', count=3):
5150

51+
assert len(QubitFieldDevice(cirq.LineQubit.range(10)).qid_pairs()) == 9
52+
assert len(QubitFieldDevice(cirq.GridQubit.rect(10, 10)).qid_pairs()) == 180
53+
assert len(QubitFieldDevice([cirq.NamedQubit(str(s)) for s in range(10)]).qid_pairs()) == 45
5254

53-
def test_qid_pair():
55+
56+
def test_qid_pair_deprecated():
5457
q0, q1, q2, q3 = cirq.LineQubit.range(4)
55-
e1 = cirq.SymmetricalQidPair(q0, q1)
56-
e2 = cirq.SymmetricalQidPair(q1, q0)
57-
e3 = cirq.SymmetricalQidPair(q2, q3)
58+
with cirq.testing.assert_deprecated('device.metadata', deadline='v0.15', count=3):
59+
e1 = cirq.SymmetricalQidPair(q0, q1)
60+
e2 = cirq.SymmetricalQidPair(q1, q0)
61+
e3 = cirq.SymmetricalQidPair(q2, q3)
5862
assert e1 == e2
5963
assert e2 != e3
6064
assert repr(e1) == "cirq.QidPair(cirq.LineQubit(0), cirq.LineQubit(1))"
@@ -74,8 +78,9 @@ def test_qid_pair():
7478
assert len(set1) == 1
7579
assert len(set2) == 2
7680

77-
with pytest.raises(ValueError, match='A QidPair cannot have identical qids.'):
78-
cirq.SymmetricalQidPair(q0, q0)
81+
with cirq.testing.assert_deprecated('device.metadata', deadline='v0.15', count=1):
82+
with pytest.raises(ValueError, match='A QidPair cannot have identical qids.'):
83+
cirq.SymmetricalQidPair(q0, q0)
7984

8085

8186
def test_device_metadata():
@@ -92,10 +97,6 @@ def test_metadata():
9297
assert metadata.qubit_set == frozenset(qubits)
9398
assert metadata.nx_graph == graph
9499

95-
metadata = cirq.DeviceMetadata()
96-
assert metadata.qubit_set is None
97-
assert metadata.nx_graph is None
98-
99100

100101
def test_metadata_json_load_logic():
101102
qubits = cirq.LineQubit.range(4)
@@ -104,13 +105,6 @@ def test_metadata_json_load_logic():
104105
str_rep = cirq.to_json(metadata)
105106
assert metadata == cirq.read_json(json_text=str_rep)
106107

107-
qubits = None
108-
graph = None
109-
metadata = cirq.DeviceMetadata(qubits, graph)
110-
str_rep = cirq.to_json(metadata)
111-
output = cirq.read_json(json_text=str_rep)
112-
assert metadata == output
113-
114108

115109
def test_metadata_equality():
116110
qubits = cirq.LineQubit.range(4)
@@ -121,8 +115,5 @@ def test_metadata_equality():
121115

122116
eq = cirq.testing.EqualsTester()
123117
eq.add_equality_group(cirq.DeviceMetadata(qubits, graph))
124-
eq.add_equality_group(cirq.DeviceMetadata(None, graph))
125-
eq.add_equality_group(cirq.DeviceMetadata(qubits, None))
126-
eq.add_equality_group(cirq.DeviceMetadata(None, None))
127-
128-
assert cirq.DeviceMetadata(None, graph) != cirq.DeviceMetadata(None, graph2)
118+
eq.add_equality_group(cirq.DeviceMetadata(qubits, graph2))
119+
eq.add_equality_group(cirq.DeviceMetadata(qubits[1:], graph))

cirq-core/cirq/ion/ion_device.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
# limitations under the License.
1414

1515
from typing import Any, FrozenSet, Iterable, Optional, Set, TYPE_CHECKING
16-
17-
from cirq import circuits, value, devices, ops, protocols
16+
from cirq import _compat, circuits, value, devices, ops, protocols
1817
from cirq.ion import convert_to_ion_gates
1918

2019
if TYPE_CHECKING:
@@ -74,14 +73,19 @@ def __init__(
7473
def qubit_set(self) -> FrozenSet['cirq.LineQubit']:
7574
return self.qubits
7675

76+
@_compat.deprecated(
77+
deadline='v0.15',
78+
fix='qubit coupling data can now be found in device.metadata if provided.',
79+
)
7780
def qid_pairs(self) -> FrozenSet['cirq.SymmetricalQidPair']:
7881
"""Qubits have all-to-all connectivity, so returns all pairs.
7982
8083
Returns:
8184
All qubit pairs on the device.
8285
"""
83-
qs = self.qubits
84-
return frozenset([devices.SymmetricalQidPair(q, q2) for q in qs for q2 in qs if q < q2])
86+
with _compat.block_overlapping_deprecation('device\\.metadata'):
87+
qs = self.qubits
88+
return frozenset([devices.SymmetricalQidPair(q, q2) for q in qs for q2 in qs if q < q2])
8589

8690
def decompose_operation(self, operation: ops.Operation) -> ops.OP_TREE:
8791
return convert_to_ion_gates.ConvertToIonGates().convert_one(operation)

cirq-core/cirq/ion/ion_device_test.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,6 @@ def test_qubit_set():
211211
assert ion_device(3).qubit_set() == frozenset(cirq.LineQubit.range(3))
212212

213213

214-
def test_qid_pairs():
215-
assert len(ion_device(10).qid_pairs()) == 45
214+
def test_qid_pairs_deprecated():
215+
with cirq.testing.assert_deprecated('device.metadata', deadline='v0.15', count=1):
216+
assert len(ion_device(10).qid_pairs()) == 45

cirq-core/cirq/protocols/json_test_data/spec.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@
184184
'Unique',
185185
'DEFAULT_RESOLVERS',
186186
],
187-
deprecated={'GlobalPhaseOperation': 'v0.16'},
187+
deprecated={
188+
'GlobalPhaseOperation': 'v0.16',
189+
'SymmetricalQidPair': 'v0.15',
190+
},
188191
tested_elsewhere=[
189192
# SerializableByKey does not follow common serialization rules.
190193
# It is tested separately in test_context_serialization.

cirq-google/cirq_google/devices/serializable_device.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
Type,
2727
FrozenSet,
2828
)
29-
3029
import cirq
30+
from cirq import _compat
3131
from cirq_google.serialization import serializable_gate_set
3232
from cirq_google.api import v2
3333

@@ -259,23 +259,28 @@ def __str__(self) -> str:
259259

260260
return super().__str__()
261261

262+
@_compat.deprecated(
263+
deadline='v0.15',
264+
fix='qubit coupling data can now be found in device.metadata if provided.',
265+
)
262266
def qid_pairs(self) -> FrozenSet['cirq.SymmetricalQidPair']:
263267
"""Returns a list of qubit edges on the device, defined by the gate
264268
definitions.
265269
266270
Returns:
267271
The list of qubit edges on the device.
268272
"""
269-
return frozenset(
270-
[
271-
cirq.SymmetricalQidPair(pair[0], pair[1])
272-
for gate_defs in self.gate_definitions.values()
273-
for gate_def in gate_defs
274-
if gate_def.number_of_qubits == 2
275-
for pair in gate_def.target_set
276-
if len(pair) == 2 and pair[0] < pair[1]
277-
]
278-
)
273+
with _compat.block_overlapping_deprecation('device\\.metadata'):
274+
return frozenset(
275+
[
276+
cirq.SymmetricalQidPair(pair[0], pair[1])
277+
for gate_defs in self.gate_definitions.values()
278+
for gate_def in gate_defs
279+
if gate_def.number_of_qubits == 2
280+
for pair in gate_def.target_set
281+
if len(pair) == 2 and pair[0] < pair[1]
282+
]
283+
)
279284

280285
def _repr_pretty_(self, p: Any, cycle: bool) -> None:
281286
"""Creates ASCII diagram for Jupyter, IPython, etc."""

0 commit comments

Comments
 (0)