Skip to content

Commit 2edef8f

Browse files
committed
Addressed Michael's comments
1 parent ad6dd90 commit 2edef8f

File tree

8 files changed

+140
-61
lines changed

8 files changed

+140
-61
lines changed

cirq-google/cirq_google/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@
5757
from cirq_google.devices import (
5858
Bristlecone,
5959
Foxtail,
60-
GoogleDevice,
6160
GoogleNoiseProperties,
61+
GridDevice,
6262
NoiseModelFromGoogleNoiseProperties,
6363
SerializableDevice,
6464
Sycamore,

cirq-google/cirq_google/devices/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from cirq_google.devices.known_devices import Bristlecone, Foxtail, Sycamore, Sycamore23
2121

22-
from cirq_google.devices.google_device import GoogleDevice
22+
from cirq_google.devices.grid_device import GridDevice
2323

2424
from cirq_google.devices.serializable_device import SerializableDevice
2525

cirq-google/cirq_google/devices/google_device.py renamed to cirq-google/cirq_google/devices/grid_device.py

+65-18
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,73 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""Device object representing Google devices."""
15+
"""Device object representing Google devices with a grid qubit layout."""
1616

1717
from typing import Any, Set, Tuple, cast
1818
import cirq
1919
from cirq_google.api import v2
2020

2121

22+
def _validate_device_specification(proto: v2.device_pb2.DeviceSpecification) -> None:
23+
"""Validates the DeviceSpecification proto.
24+
25+
Args:
26+
proto: The DeviceSpecification proto to validate.
27+
28+
Raises:
29+
ValueError: If the DeviceSpecification is invalid.
30+
31+
"""
32+
33+
for target_set in proto.valid_targets:
34+
35+
# Check for unknown qubits in targets.
36+
for target in target_set.targets:
37+
for target_id in target.ids:
38+
if target_id not in proto.valid_qubits:
39+
raise ValueError(
40+
f"Invalid DeviceSpecification: valid_targets contain qubit '{target_id}'"
41+
" which is not in valid_qubits."
42+
)
43+
44+
# Symmetric and asymmetric targets should not have repeated qubits.
45+
if (
46+
target_set.target_ordering == v2.device_pb2.TargetSet.SYMMETRIC
47+
or target_set.target_ordering == v2.device_pb2.TargetSet.ASYMMETRIC
48+
):
49+
for target in target_set.targets:
50+
if len(target.ids) > len(set(target.ids)):
51+
raise ValueError(
52+
f"Invalid DeviceSpecification: the target set '{target_set.name}' is either"
53+
" SYMMETRIC or ASYMMETRIC but has a target which contains repeated qubits:"
54+
f" {target.ids}."
55+
)
56+
57+
# A SUBSET_PERMUTATION target should contain exactly one qubit.
58+
if target_set.target_ordering == v2.device_pb2.TargetSet.SUBSET_PERMUTATION:
59+
for target in target_set.targets:
60+
if len(target.ids) != 1:
61+
raise ValueError(
62+
f"Invalid DeviceSpecification: the target set '{target_set.name}' is of"
63+
" type SUBSET_PERMUTATION but contains a target which does not have exactly"
64+
f" 1 qubit: {target.ids}."
65+
)
66+
67+
2268
@cirq.value_equality
23-
class GoogleDevice(cirq.Device):
24-
"""Device object representing Google devices.
69+
class GridDevice(cirq.Device):
70+
"""Device object representing Google devices with a grid qubit layout.
2571
2672
For end users, instances of this class are typically accessed via
2773
`Engine.get_processor('processor_name').get_device()`.
2874
2975
This class is compliant with the core `cirq.Device` abstraction. In particular:
3076
* Device information is captured in the `metadata` property.
31-
* An instance of `GoogleDevice` can be used to validate circuits, moments, and operations.
77+
* An instance of `GridDevice` can be used to validate circuits, moments, and operations.
3278
3379
Example use cases:
3480
35-
* Get an instance of a Google device.
81+
* Get an instance of a Google grid device.
3682
>>> device = cirq_google.get_engine().get_processor('weber').get_device()
3783
3884
* Print the grid layout of the device.
@@ -78,15 +124,15 @@ class GoogleDevice(cirq.Device):
78124
"""
79125

80126
def __init__(self, metadata: cirq.GridDeviceMetadata):
81-
"""Creates a GoogleDevice object.
127+
"""Creates a GridDevice object.
82128
83129
This constructor typically should not be used directly. Use `from_proto()` instead.
84130
"""
85131
self._metadata = metadata
86132

87133
@classmethod
88-
def from_proto(cls, proto: v2.device_pb2.DeviceSpecification) -> 'GoogleDevice':
89-
"""Create a `GoogleDevice` from a DeviceSpecification proto.
134+
def from_proto(cls, proto: v2.device_pb2.DeviceSpecification) -> 'GridDevice':
135+
"""Create a `GridDevice` from a DeviceSpecification proto.
90136
91137
This class only supports `cirq.GridQubit`s and `cirq.NamedQubit`s. If a
92138
`DeviceSpecification.valid_qubits` string is in the form `<int>_<int>`, it is parsed as a
@@ -99,6 +145,8 @@ def from_proto(cls, proto: v2.device_pb2.DeviceSpecification) -> 'GoogleDevice':
99145
ValueError: If the given `DeviceSpecification` is invalid.
100146
"""
101147

148+
_validate_device_specification(proto)
149+
102150
# Create qubit set
103151
all_qubits = [_qid_from_str(q) for q in proto.valid_qubits]
104152

@@ -110,7 +158,6 @@ def from_proto(cls, proto: v2.device_pb2.DeviceSpecification) -> 'GoogleDevice':
110158
# * All valid qubits work for all single-qubit gates.
111159
# * Measurement gate can always be applied to all subset of qubits.
112160
#
113-
# TODO(#5169) Consider adding the reversed pair, depending on the issue's solution.
114161
qubit_pairs = [
115162
(_qid_from_str(target.ids[0]), _qid_from_str(target.ids[1]))
116163
for ts in proto.valid_targets
@@ -128,10 +175,10 @@ def from_proto(cls, proto: v2.device_pb2.DeviceSpecification) -> 'GoogleDevice':
128175
except ValueError as ve:
129176
raise ValueError("DeviceSpecification is invalid.") from ve
130177

131-
return GoogleDevice(metadata)
178+
return GridDevice(metadata)
132179

133180
@property
134-
def metadata(self):
181+
def metadata(self) -> cirq.GridDeviceMetadata:
135182
"""Get metadata information for the device."""
136183
return self._metadata
137184

@@ -157,8 +204,10 @@ def validate_operation(self, operation: cirq.Operation) -> None:
157204
if q not in self._metadata.qubit_set:
158205
raise ValueError(f'Qubit not on device: {q!r}')
159206

160-
# TODO(#5169) May need to check the reverse pair depending on the issue's solution.
161-
if len(operation.qubits) == 2 and tuple(operation.qubits) not in self._metadata.qubit_pairs:
207+
if (
208+
len(operation.qubits) == 2
209+
and frozenset(operation.qubits) not in self._metadata.qubit_pairs
210+
):
162211
raise ValueError(f'Qubit pair is not valid on device: {operation.qubits!r}')
163212

164213
def __str__(self) -> str:
@@ -177,7 +226,7 @@ def __str__(self) -> str:
177226

178227
# Find pairs that are connected by two-qubit gates.
179228
Pair = Tuple[cirq.GridQubit, cirq.GridQubit]
180-
pairs = sorted({cast(Pair, pair) for pair in self._metadata.qubit_pairs})
229+
pairs = sorted({cast(Pair, tuple(pair)) for pair in self._metadata.qubit_pairs})
181230

182231
# Draw lines between connected pairs. Limit to horizontal/vertical
183232
# lines since that is all the diagram drawer can handle.
@@ -199,12 +248,10 @@ def _repr_pretty_(self, p: Any, cycle: bool) -> None:
199248
p.text(repr(self) if cycle else str(self))
200249

201250
def __repr__(self) -> str:
202-
return f'cirq_google.GoogleDevice({repr(self._metadata)})'
251+
return f'cirq_google.GridDevice({repr(self._metadata)})'
203252

204253
def _json_dict_(self):
205-
return {
206-
'metadata': self._metadata,
207-
}
254+
return {'metadata': self._metadata}
208255

209256
@classmethod
210257
def _from_json_dict_(cls, metadata, **kwargs):

cirq-google/cirq_google/devices/google_device_test.py renamed to cirq-google/cirq_google/devices/grid_device_test.py

+51-30
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,20 @@ def _create_device_spec_with_horizontal_couplings():
3333
# x -- x
3434

3535
grid_qubits = [cirq.GridQubit(i, j) for i in range(GRID_HEIGHT) for j in range(2)]
36+
3637
spec = v2.device_pb2.DeviceSpecification()
3738
spec.valid_qubits.extend([v2.qubit_to_proto_id(q) for q in grid_qubits])
3839
grid_targets = spec.valid_targets.add()
3940
grid_targets.name = '2_qubit_targets'
4041
grid_targets.target_ordering = v2.device_pb2.TargetSet.SYMMETRIC
41-
for row in range(GRID_HEIGHT):
42+
for row in range(int(GRID_HEIGHT / 2)):
4243
new_target = grid_targets.targets.add()
4344
new_target.ids.extend([v2.qubit_to_proto_id(cirq.GridQubit(row, j)) for j in range(2)])
45+
for row in range(int(GRID_HEIGHT / 2), GRID_HEIGHT):
46+
# Flip the qubit pair order for the second half of qubits
47+
# to verify GridDevice properly handles pair symmetry.
48+
new_target = grid_targets.targets.add()
49+
new_target.ids.extend([v2.qubit_to_proto_id(cirq.GridQubit(row, 1 - j)) for j in range(2)])
4450
gate = spec.valid_gates.add()
4551
gate.syc.SetInParent()
4652
gate.gate_duration_picos = 12000
@@ -74,7 +80,7 @@ def _create_device_spec_with_all_couplings():
7480
return grid_qubits, spec
7581

7682

77-
def _create_device_spec_with_qubit_pair_self_loops() -> v2.device_pb2.DeviceSpecification:
83+
def _create_device_spec_qubit_pair_self_loops() -> v2.device_pb2.DeviceSpecification:
7884
q_proto_id = v2.qubit_to_proto_id(cirq.NamedQubit('q'))
7985

8086
spec = v2.device_pb2.DeviceSpecification()
@@ -88,7 +94,7 @@ def _create_device_spec_with_qubit_pair_self_loops() -> v2.device_pb2.DeviceSpec
8894
return spec
8995

9096

91-
def _create_device_spec_with_invalid_qubit_in_qubit_pair() -> v2.device_pb2.DeviceSpecification:
97+
def _create_device_spec_invalid_qubit_in_qubit_pair() -> v2.device_pb2.DeviceSpecification:
9298
q_proto_ids = [v2.qubit_to_proto_id(cirq.GridQubit(0, i)) for i in range(2)]
9399

94100
spec = v2.device_pb2.DeviceSpecification()
@@ -102,22 +108,36 @@ def _create_device_spec_with_invalid_qubit_in_qubit_pair() -> v2.device_pb2.Devi
102108
return spec
103109

104110

105-
def test_google_device_from_proto_and_validation():
111+
def _create_device_spec_invalid_subset_permutation_target() -> v2.device_pb2.DeviceSpecification:
112+
q_proto_ids = [v2.qubit_to_proto_id(cirq.GridQubit(0, i)) for i in range(2)]
113+
114+
spec = v2.device_pb2.DeviceSpecification()
115+
spec.valid_qubits.extend(q_proto_ids)
116+
targets = spec.valid_targets.add()
117+
targets.name = 'test_targets'
118+
targets.target_ordering = v2.device_pb2.TargetSet.SUBSET_PERMUTATION
119+
new_target = targets.targets.add()
120+
new_target.ids.extend(q_proto_ids) # should only have 1 qubit instead
121+
122+
return spec
123+
124+
125+
def test_grid_device_from_proto_and_validation():
106126
grid_qubits, spec = _create_device_spec_with_horizontal_couplings()
107127

108-
device = cirq_google.GoogleDevice.from_proto(spec)
128+
device = cirq_google.GridDevice.from_proto(spec)
109129

110130
assert len(device.metadata.qubit_set) == len(grid_qubits)
111131
assert device.metadata.qubit_set == frozenset(grid_qubits)
112132
assert all(
113-
(cirq.GridQubit(row, 0), cirq.GridQubit(row, 1)) in device.metadata.qubit_pairs
133+
frozenset((cirq.GridQubit(row, 0), cirq.GridQubit(row, 1))) in device.metadata.qubit_pairs
114134
for row in range(GRID_HEIGHT)
115135
)
116136

117137

118-
def test_google_device_validate_operations_positive():
138+
def test_grid_device_validate_operations_positive():
119139
grid_qubits, spec = _create_device_spec_with_horizontal_couplings()
120-
device = cirq_google.GoogleDevice.from_proto(spec)
140+
device = cirq_google.GridDevice.from_proto(spec)
121141

122142
for q in grid_qubits:
123143
device.validate_operation(cirq.X(q))
@@ -129,9 +149,9 @@ def test_google_device_validate_operations_positive():
129149
# TODO(#5050) verify validate_operations gateset support
130150

131151

132-
def test_google_device_validate_operations_negative():
152+
def test_grid_device_validate_operations_negative():
133153
grid_qubits, spec = _create_device_spec_with_horizontal_couplings()
134-
device = cirq_google.GoogleDevice.from_proto(spec)
154+
device = cirq_google.GridDevice.from_proto(spec)
135155

136156
q = cirq.GridQubit(10, 10)
137157
with pytest.raises(ValueError, match='Qubit not on device'):
@@ -145,31 +165,32 @@ def test_google_device_validate_operations_negative():
145165
# TODO(#5050) verify validate_operations gateset errors
146166

147167

148-
@pytest.mark.parametrize(
149-
'spec',
150-
[
151-
# TODO(#5050) implement once gateset support is implemented
152-
# _create_device_spec_with_missing_gate_durations(),
153-
_create_device_spec_with_qubit_pair_self_loops(),
154-
_create_device_spec_with_invalid_qubit_in_qubit_pair(),
155-
],
156-
)
157-
def test_google_device_invalid_device_spec(spec):
158-
with pytest.raises(ValueError, match='DeviceSpecification is invalid'):
159-
cirq_google.GoogleDevice.from_proto(spec)
168+
def test_grid_device_invalid_qubit_in_qubit_pair():
169+
with pytest.raises(ValueError, match='which is not in valid_qubits'):
170+
cirq_google.GridDevice.from_proto(_create_device_spec_invalid_qubit_in_qubit_pair())
171+
172+
173+
def test_grid_device_invalid_target_self_loops():
174+
with pytest.raises(ValueError, match='contains repeated qubits'):
175+
cirq_google.GridDevice.from_proto(_create_device_spec_qubit_pair_self_loops())
176+
177+
178+
def test_grid_device_invalid_subset_permutation_target():
179+
with pytest.raises(ValueError, match='does not have exactly 1 qubit'):
180+
cirq_google.GridDevice.from_proto(_create_device_spec_invalid_subset_permutation_target())
160181

161182

162-
def test_google_device_repr_json():
183+
def test_grid_device_repr_json():
163184
_, spec = _create_device_spec_with_horizontal_couplings()
164-
device = cirq_google.GoogleDevice.from_proto(spec)
185+
device = cirq_google.GridDevice.from_proto(spec)
165186

166187
assert eval(repr(device)) == device
167188
assert cirq.read_json(json_text=cirq.to_json(device)) == device
168189

169190

170-
def test_google_device_str_grid_qubits():
191+
def test_grid_device_str_grid_qubits():
171192
_, spec = _create_device_spec_with_all_couplings()
172-
device = cirq_google.GoogleDevice.from_proto(spec)
193+
device = cirq_google.GridDevice.from_proto(spec)
173194

174195
assert (
175196
str(device)
@@ -191,17 +212,17 @@ def test_google_device_str_grid_qubits():
191212

192213

193214
@pytest.mark.parametrize('cycle,func', [(False, str), (True, repr)])
194-
def test_google_device_repr_pretty(cycle, func):
215+
def test_grid_device_repr_pretty(cycle, func):
195216
_, spec = _create_device_spec_with_all_couplings()
196-
device = cirq_google.GoogleDevice.from_proto(spec)
217+
device = cirq_google.GridDevice.from_proto(spec)
197218
printer = mock.Mock()
198219
device._repr_pretty_(printer, cycle)
199220
printer.text.assert_called_once_with(func(device))
200221

201222

202-
def test_serializable_device_str_named_qubits():
223+
def test_grid_device_str_named_qubits():
203224
q_proto_id = v2.qubit_to_proto_id(cirq.NamedQubit('q'))
204225
spec = v2.device_pb2.DeviceSpecification()
205226
spec.valid_qubits.extend([q_proto_id])
206-
device = cirq_google.GoogleDevice.from_proto(spec)
227+
device = cirq_google.GridDevice.from_proto(spec)
207228
assert device.__class__.__name__ in str(device)

cirq-google/cirq_google/json_resolver_cache.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def _class_resolver_dictionary() -> Dict[str, ObjectFactory]:
3333
'GoogleNoiseProperties': cirq_google.GoogleNoiseProperties,
3434
'SycamoreGate': cirq_google.SycamoreGate,
3535
'GateTabulation': cirq_google.GateTabulation,
36-
'GoogleDevice': cirq_google.GoogleDevice,
36+
'GridDevice': cirq_google.GridDevice,
3737
'PhysicalZTag': cirq_google.PhysicalZTag,
3838
'FSimGateFamily': cirq_google.FSimGateFamily,
3939
'FloquetPhasedFSimCalibrationOptions': cirq_google.FloquetPhasedFSimCalibrationOptions,

cirq-google/cirq_google/json_test_data/GoogleDevice.repr

-1
This file was deleted.

0 commit comments

Comments
 (0)