Skip to content

Commit 0ac6cc1

Browse files
authored
Add edges to device (quantumlib#3993)
Closes quantumlib#3696
1 parent 46fe1ce commit 0ac6cc1

File tree

13 files changed

+207
-2
lines changed

13 files changed

+207
-2
lines changed

cirq/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
NO_NOISE,
9191
NOISE_MODEL_LIKE,
9292
NoiseModel,
93+
SymmetricalQidPair,
9394
UNCONSTRAINED_DEVICE,
9495
)
9596

cirq/contrib/graph_device/graph_device.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,15 @@ def qubit_set(self) -> FrozenSet['cirq.Qid']:
159159
def edges(self):
160160
return tuple(sorted(self.device_graph.edges))
161161

162+
def qid_pairs(self) -> FrozenSet['cirq.SymmetricalQidPair']:
163+
return frozenset(
164+
[
165+
devices.SymmetricalQidPair(*edge) # type: ignore
166+
for edge in self.device_graph.edges
167+
if len(edge) == 2 and all(isinstance(q, ops.Qid) for q in edge)
168+
]
169+
)
170+
162171
@property
163172
def labelled_edges(self):
164173
return self.device_graph.labelled_edges

cirq/contrib/graph_device/graph_device_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,10 @@ def test_qubit_set():
180180
device_graph = ccgd.UndirectedHypergraph(labelled_edges={(a, b): None, (c, d): None})
181181
device = ccgd.UndirectedGraphDevice(device_graph=device_graph)
182182
assert device.qubit_set() == {a, b, c, d}
183+
184+
185+
def test_qid_pairs():
186+
a, b, c, d = cirq.LineQubit.range(4)
187+
device_graph = ccgd.UndirectedHypergraph(labelled_edges={(a, b): None, (c, d): None})
188+
device = ccgd.UndirectedGraphDevice(device_graph=device_graph)
189+
assert len(device.qid_pairs()) == 2

cirq/devices/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""Types for devices, device-specific qubits, and noise models."""
1616
from cirq.devices.device import (
1717
Device,
18+
SymmetricalQidPair,
1819
)
1920

2021
from cirq.devices.grid_qubit import (

cirq/devices/device.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
# limitations under the License.
1414

1515
import abc
16-
from typing import TYPE_CHECKING, Optional, AbstractSet
16+
from typing import TYPE_CHECKING, Optional, AbstractSet, cast, FrozenSet, Iterator
17+
18+
from cirq import value
19+
from cirq.devices.grid_qubit import _BaseGridQid
20+
from cirq.devices.line_qubit import _BaseLineQid
1721

1822
if TYPE_CHECKING:
1923
import cirq
@@ -46,6 +50,43 @@ def qubit_set(self) -> Optional[AbstractSet['cirq.Qid']]:
4650
# Default to the qubits being unknown.
4751
return None
4852

53+
def qid_pairs(self) -> Optional[FrozenSet['cirq.SymmetricalQidPair']]:
54+
"""Returns a set of qubit edges on the device, if possible.
55+
56+
This property can be overridden in child classes for special handling.
57+
The default handling is: GridQids and LineQids will have neighbors as
58+
edges, and others will be fully connected.
59+
60+
Returns:
61+
If the device has a finite set of qubits, then a set of all edges
62+
on the device is returned.
63+
64+
If the device has no well defined finite set of qubits (e.g.
65+
`cirq.UnconstrainedDevice` has this property), then `None` is
66+
returned.
67+
"""
68+
qs = self.qubit_set()
69+
if qs is None:
70+
return None
71+
if all(isinstance(q, _BaseGridQid) for q in qs):
72+
return frozenset(
73+
[
74+
SymmetricalQidPair(q, q2)
75+
for q in [cast(_BaseGridQid, q) for q in qs]
76+
for q2 in [q + (0, 1), q + (1, 0)]
77+
if q2 in qs
78+
]
79+
)
80+
if all(isinstance(q, _BaseLineQid) for q in qs):
81+
return frozenset(
82+
[
83+
SymmetricalQidPair(q, q + 1)
84+
for q in [cast(_BaseLineQid, q) for q in qs]
85+
if q + 1 in qs
86+
]
87+
)
88+
return frozenset([SymmetricalQidPair(q, q2) for q in qs for q2 in qs if q < q2])
89+
4990
def decompose_operation(self, operation: 'cirq.Operation') -> 'cirq.OP_TREE':
5091
"""Returns a device-valid decomposition for the given operation.
5192
@@ -105,3 +146,36 @@ def can_add_operation_into_moment(
105146
Whether or not the moment will validate after adding the operation.
106147
"""
107148
return not moment.operates_on(operation.qubits)
149+
150+
151+
@value.value_equality
152+
class SymmetricalQidPair:
153+
def __init__(self, qid1: 'cirq.Qid', qid2: 'cirq.Qid'):
154+
if qid1 == qid2:
155+
raise ValueError('A QidPair cannot have identical qids.')
156+
self.qids = frozenset([qid1, qid2])
157+
158+
def _value_equality_values_(self):
159+
return self.qids
160+
161+
def __repr__(self):
162+
return f'cirq.QidPair({repr(sorted(self.qids))[1:-1]})'
163+
164+
def _json_dict_(self):
165+
return {
166+
'qids': sorted(self.qids),
167+
'cirq_type': self.__class__.__name__,
168+
}
169+
170+
@classmethod
171+
def _from_json_dict_(cls, qids, **kwargs):
172+
return cls(qids[0], qids[1])
173+
174+
def __len__(self) -> int:
175+
return 2
176+
177+
def __iter__(self) -> Iterator['cirq.Qid']:
178+
yield from sorted(self.qids)
179+
180+
def __contains__(self, item: 'cirq.Qid') -> bool:
181+
return item in self.qids

cirq/devices/device_test.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pytest
12
import cirq
23

34

@@ -30,3 +31,46 @@ def _qubits(self):
3031
return cirq.LineQubit.range(6)
3132

3233
assert PrivateQubitMethodDevice().qubit_set() == frozenset(cirq.LineQubit.range(6))
34+
35+
36+
def test_qid_pairs():
37+
class RawDevice(cirq.Device):
38+
pass
39+
40+
assert RawDevice().qid_pairs() is None
41+
42+
class QubitFieldDevice(cirq.Device):
43+
def __init__(self, qubits):
44+
self.qubits = qubits
45+
46+
assert len(QubitFieldDevice(cirq.LineQubit.range(10)).qid_pairs()) == 9
47+
assert len(QubitFieldDevice(cirq.GridQubit.rect(10, 10)).qid_pairs()) == 180
48+
assert len(QubitFieldDevice([cirq.NamedQubit(str(s)) for s in range(10)]).qid_pairs()) == 45
49+
50+
51+
def test_qid_pair():
52+
q0, q1, q2, q3 = cirq.LineQubit.range(4)
53+
e1 = cirq.SymmetricalQidPair(q0, q1)
54+
e2 = cirq.SymmetricalQidPair(q1, q0)
55+
e3 = cirq.SymmetricalQidPair(q2, q3)
56+
assert e1 == e2
57+
assert e2 != e3
58+
assert repr(e1) == "cirq.QidPair(cirq.LineQubit(0), cirq.LineQubit(1))"
59+
60+
assert len(e1) == 2
61+
a, b = e1
62+
assert (a, b) == (q0, q1)
63+
a, b = e2
64+
assert (a, b) == (q0, q1)
65+
66+
assert q0 in e1
67+
assert q1 in e1
68+
assert q2 not in e1
69+
70+
set1 = frozenset([e1, e2])
71+
set2 = frozenset([e2, e3])
72+
assert len(set1) == 1
73+
assert len(set2) == 2
74+
75+
with pytest.raises(ValueError, match='A QidPair cannot have identical qids.'):
76+
cirq.SymmetricalQidPair(q0, q0)

cirq/ion/ion_device.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ def __init__(
5252
def qubit_set(self) -> FrozenSet['cirq.LineQubit']:
5353
return self.qubits
5454

55+
def qid_pairs(self) -> FrozenSet['cirq.SymmetricalQidPair']:
56+
"""Qubits have all-to-all connectivity, so returns all pairs.
57+
58+
Returns:
59+
All qubit pairs on the device.
60+
"""
61+
qs = self.qubits
62+
return frozenset([devices.SymmetricalQidPair(q, q2) for q in qs for q2 in qs if q < q2])
63+
5564
def decompose_operation(self, operation: ops.Operation) -> ops.OP_TREE:
5665
return convert_to_ion_gates.ConvertToIonGates().convert_one(operation)
5766

cirq/ion/ion_device_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,7 @@ def test_at():
198198

199199
def test_qubit_set():
200200
assert ion_device(3).qubit_set() == frozenset(cirq.LineQubit.range(3))
201+
202+
203+
def test_qid_pairs():
204+
assert len(ion_device(10).qid_pairs()) == 45

cirq/json_resolver_cache.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,16 @@ def two_qubit_matrix_gate(matrix):
119119
'PhasedISwapPowGate': cirq.PhasedISwapPowGate,
120120
'PhasedXPowGate': cirq.PhasedXPowGate,
121121
'PhasedXZGate': cirq.PhasedXZGate,
122-
'QuantumFourierTransformGate': cirq.QuantumFourierTransformGate,
123122
'RandomGateChannel': cirq.RandomGateChannel,
123+
'QuantumFourierTransformGate': cirq.QuantumFourierTransformGate,
124124
'RepetitionsStoppingCriteria': cirq.work.RepetitionsStoppingCriteria,
125125
'ResetChannel': cirq.ResetChannel,
126126
'SingleQubitMatrixGate': single_qubit_matrix_gate,
127127
'SingleQubitPauliStringGateOperation': cirq.SingleQubitPauliStringGateOperation,
128128
'SingleQubitReadoutCalibrationResult': cirq.experiments.SingleQubitReadoutCalibrationResult,
129129
'StabilizerStateChForm': cirq.StabilizerStateChForm,
130130
'SwapPowGate': cirq.SwapPowGate,
131+
'SymmetricalQidPair': cirq.SymmetricalQidPair,
131132
'TaggedOperation': cirq.TaggedOperation,
132133
'ThreeDQubit': cirq.pasqal.ThreeDQubit,
133134
'Result': cirq.Result,

cirq/pasqal/pasqal_device.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
from typing import FrozenSet, Callable, List, Sequence, Any, Union, Dict
15+
1516
import numpy as np
1617

1718
import cirq
@@ -361,6 +362,22 @@ def _value_equality_values_(self) -> Any:
361362
def _json_dict_(self) -> Dict[str, Any]:
362363
return cirq.protocols.obj_to_dict_helper(self, ['control_radius', 'qubits'])
363364

365+
def qid_pairs(self) -> FrozenSet['cirq.SymmetricalQidPair']:
366+
"""Returns a list of qubit edges on the device.
367+
368+
Returns:
369+
All qubit pairs that are less or equal to the control radius apart.
370+
"""
371+
qs = self.qubits
372+
return frozenset(
373+
[
374+
cirq.SymmetricalQidPair(q, q2)
375+
for q in qs
376+
for q2 in qs
377+
if q < q2 and self.distance(q, q2) <= self.control_radius
378+
]
379+
)
380+
364381

365382
class PasqalConverter(cirq.neutral_atoms.ConvertToNeutralAtomGates):
366383
"""A gate converter for compatibility with Pasqal processors.

cirq/pasqal/pasqal_device_test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,27 @@ def test_to_json():
278278
"control_radius": 2,
279279
"qubits": [cirq.pasqal.TwoDQubit(0, 0)],
280280
}
281+
282+
283+
def test_qid_pairs():
284+
dev = PasqalVirtualDevice(
285+
1,
286+
qubits=[
287+
ThreeDQubit(0, 0, 0),
288+
ThreeDQubit(1, 0, 0),
289+
ThreeDQubit(0, 1, 0),
290+
ThreeDQubit(1, 1, 0),
291+
ThreeDQubit(1, 1, 1),
292+
],
293+
)
294+
assert len(dev.qid_pairs()) == 5
295+
dev1 = PasqalVirtualDevice(
296+
5,
297+
qubits=[
298+
TwoDQubit(0, 0),
299+
TwoDQubit(3, 2),
300+
TwoDQubit(3, 4),
301+
TwoDQubit(3, 6),
302+
],
303+
)
304+
assert len(dev1.qid_pairs()) == 5
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"cirq_type": "SymmetricalQidPair",
3+
"qids": [
4+
{
5+
"cirq_type": "LineQubit",
6+
"x": 0
7+
},
8+
{
9+
"cirq_type": "LineQubit",
10+
"x": 1
11+
}
12+
]
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cirq.SymmetricalQidPair(cirq.LineQubit(0), cirq.LineQubit(1))

0 commit comments

Comments
 (0)