Skip to content

Commit 4531a10

Browse files
authored
cirq_google.GridDevice, minus gateset and gate durations (#5203)
Part of #5050 * Implemented qubit set, qubit pairs, and op validation. Gateset and gate duration will be done in a follow-up. * Added a `device_specification_validation` module shell, to be used by the QCS server side to validate the DeviceSpecification proto against things like: * Qubit self-loops in qubit pairs * Qubit pairs contain qubits not in the valid qubit set. * Gate durations contains a gate which is missing in the gateset. `__str__()` and `_repr_pretty_` are nearly all copied from `SerializableDevice`. Part of #5050 Also fixes #5197 **Question:** Does this definition of device equality make sense, or should it contain device name as well? Implementing equality is convenient for json and repr tests but not sure if it make sense in general. @dstrain115 @MichaelBroughton
1 parent 5dc1fbc commit 4531a10

File tree

9 files changed

+835
-1
lines changed

9 files changed

+835
-1
lines changed

cirq-core/cirq/devices/grid_device_metadata.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def _from_json_dict_(
189189
return cls(
190190
qubit_pairs,
191191
gateset,
192-
None if gate_durations is None else dict(gate_durations),
192+
dict(gate_durations) if gate_durations is not None else None,
193193
all_qubits,
194194
compilation_target_gatesets,
195195
)

cirq-google/cirq_google/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
Bristlecone,
5959
Foxtail,
6060
GoogleNoiseProperties,
61+
GridDevice,
6162
NoiseModelFromGoogleNoiseProperties,
6263
SerializableDevice,
6364
Sycamore,

cirq-google/cirq_google/api/v2/device.proto

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ message DeviceSpecification {
2222
// A list of allowed ids for qubits within the Program.
2323
// Any programs with ids not in this list will be rejected.
2424
// If empty, all qubit values are allowed (e.g. in a simulator)
25+
// Only grid qubits are supported. Strings must be in the form '<int>_<int>'.
2526
repeated string valid_qubits = 2;
2627

2728
// A list of targets that gates can use.

cirq-google/cirq_google/devices/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

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

22+
from cirq_google.devices.grid_device import GridDevice
23+
2224
from cirq_google.devices.serializable_device import SerializableDevice
2325

2426
from cirq_google.devices.xmon_device import XmonDevice
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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+
"""Device object representing Google devices with a grid qubit layout."""
16+
17+
import re
18+
19+
from typing import Any, Set, Tuple, cast
20+
import cirq
21+
from cirq_google.api import v2
22+
23+
24+
def _validate_device_specification(proto: v2.device_pb2.DeviceSpecification) -> None:
25+
"""Raises a ValueError if the `DeviceSpecification` proto is invalid."""
26+
27+
qubit_set = set()
28+
for q_name in proto.valid_qubits:
29+
# Qubit names must be unique.
30+
if q_name in qubit_set:
31+
raise ValueError(
32+
f"Invalid DeviceSpecification: valid_qubits contains duplicate qubit '{q_name}'."
33+
)
34+
# Qubit names must be in the form <int>_<int> to be parsed as cirq.GridQubits.
35+
if re.match(r'^[0-9]+\_[0-9]+$', q_name) is None:
36+
raise ValueError(
37+
f"Invalid DeviceSpecification: valid_qubits contains the qubit '{q_name}' which is"
38+
" not in the GridQubit form '<int>_<int>."
39+
)
40+
qubit_set.add(q_name)
41+
42+
for target_set in proto.valid_targets:
43+
44+
# Check for unknown qubits in targets.
45+
for target in target_set.targets:
46+
for target_id in target.ids:
47+
if target_id not in proto.valid_qubits:
48+
raise ValueError(
49+
f"Invalid DeviceSpecification: valid_targets contain qubit '{target_id}'"
50+
" which is not in valid_qubits."
51+
)
52+
53+
# Symmetric and asymmetric targets should not have repeated qubits.
54+
if (
55+
target_set.target_ordering == v2.device_pb2.TargetSet.SYMMETRIC
56+
or target_set.target_ordering == v2.device_pb2.TargetSet.ASYMMETRIC
57+
):
58+
for target in target_set.targets:
59+
if len(target.ids) > len(set(target.ids)):
60+
raise ValueError(
61+
f"Invalid DeviceSpecification: the target set '{target_set.name}' is either"
62+
" SYMMETRIC or ASYMMETRIC but has a target which contains repeated qubits:"
63+
f" {target.ids}."
64+
)
65+
66+
# A SUBSET_PERMUTATION target should contain exactly one qubit.
67+
# SUBSET_PERMUTATION describes a target set (rather than a target), where a gate can have
68+
# any subset of the targets, with each target being exactly 1 qubit.
69+
# See the `DeviceSpecification` proto definition for a detailed description.
70+
if target_set.target_ordering == v2.device_pb2.TargetSet.SUBSET_PERMUTATION:
71+
for target in target_set.targets:
72+
if len(target.ids) != 1:
73+
raise ValueError(
74+
f"Invalid DeviceSpecification: the target set '{target_set.name}' is of"
75+
" type SUBSET_PERMUTATION but contains a target which does not have exactly"
76+
f" 1 qubit: {target.ids}."
77+
)
78+
79+
80+
@cirq.value_equality
81+
class GridDevice(cirq.Device):
82+
"""Device object representing Google devices with a grid qubit layout.
83+
84+
For end users, instances of this class are typically accessed via
85+
`Engine.get_processor('processor_name').get_device()`.
86+
87+
This class is compliant with the core `cirq.Device` abstraction. In particular:
88+
* Device information is captured in the `metadata` property.
89+
* An instance of `GridDevice` can be used to validate circuits, moments, and operations.
90+
91+
Example use cases:
92+
93+
* Get an instance of a Google grid device.
94+
>>> device = cirq_google.get_engine().get_processor('processor_name').get_device()
95+
96+
* Print the grid layout of the device.
97+
>>> print(device)
98+
99+
* Determine whether a circuit can be run on the device.
100+
>>> device.validate_circuit(circuit) # Raises a ValueError if the circuit is invalid.
101+
102+
* Determine whether an operation can be run on the device.
103+
>>> device.validate_operation(operation) # Raises a ValueError if the operation is invalid.
104+
105+
* Get the `cirq.Gateset` containing valid gates for the device, and inspect the full list
106+
of valid gates.
107+
>>> gateset = device.metadata.gateset
108+
>>> print(gateset)
109+
110+
* Determine whether a gate is available on the device.
111+
>>> gate in device.metadata.gateset
112+
113+
* Get a collection of valid qubits on the device.
114+
>>> device.metadata.qubit_set
115+
116+
* Get a collection of valid qubit pairs for two-qubit gates.
117+
>>> device.metadata.qubit_pairs
118+
119+
* Get a collection of isolated qubits, i.e. qubits which are not part of any qubit pair.
120+
>>> device.metadata.isolated_qubits
121+
122+
* Get a collection of approximate gate durations for every gate supported by the device.
123+
>>> device.metadata.gate_durations
124+
125+
TODO(#5050) Add compilation_target_gatesets example.
126+
127+
Notes for cirq_google internal implementation:
128+
129+
For Google devices, the
130+
[DeviceSpecification proto](
131+
https://github.com/quantumlib/Cirq/blob/master/cirq-google/cirq_google/api/v2/device.proto
132+
)
133+
is the main specification for device information surfaced by the Quantum Computing Service.
134+
Thus, this class is should be instantiated using a `DeviceSpecification` proto via the
135+
`from_proto()` class method.
136+
"""
137+
138+
def __init__(self, metadata: cirq.GridDeviceMetadata):
139+
"""Creates a GridDevice object.
140+
141+
This constructor typically should not be used directly. Use `from_proto()` instead.
142+
"""
143+
self._metadata = metadata
144+
145+
@classmethod
146+
def from_proto(cls, proto: v2.device_pb2.DeviceSpecification) -> 'GridDevice':
147+
"""Create a `GridDevice` from a `DeviceSpecification` proto.
148+
149+
Args:
150+
proto: The `DeviceSpecification` proto describing a Google device.
151+
152+
Raises:
153+
ValueError: If the given `DeviceSpecification` is invalid. It is invalid if:
154+
* A `DeviceSpecification.valid_qubits` string is not in the form `<int>_<int>`, thus
155+
cannot be parsed as a `cirq.GridQubit`.
156+
* `DeviceSpecification.valid_targets` refer to qubits which are not in
157+
`DeviceSpecification.valid_qubits`.
158+
* A target set in `DeviceSpecification.valid_targets` has type `SYMMETRIC` or
159+
`ASYMMETRIC` but contains targets with repeated qubits, e.g. a qubit pair with a
160+
self loop.
161+
* A target set in `DeviceSpecification.valid_targets` has type `SUBSET_PERMUTATION`
162+
but contains targets which do not have exactly one element. A `SUBSET_PERMUTATION`
163+
target set uses each target to represent a single qubit, and a gate can be applied
164+
to any subset of qubits in the target set.
165+
"""
166+
167+
_validate_device_specification(proto)
168+
169+
# Create qubit set
170+
all_qubits = {v2.grid_qubit_from_proto_id(q) for q in proto.valid_qubits}
171+
172+
# Create qubit pair set
173+
#
174+
# While the `GateSpecification` proto message contains qubit target references, they are
175+
# ignored here because the following assumptions make them unnecessary currently:
176+
# * All valid qubit pairs work for all two-qubit gates.
177+
# * All valid qubits work for all single-qubit gates.
178+
# * Measurement gate can always be applied to all subset of qubits.
179+
#
180+
# TODO(#5050) Consider removing `GateSpecification.valid_targets` and
181+
# ASYMMETRIC and SUBSET_PERMUTATION target types.
182+
# If they are not removed, then their validation should be tightened.
183+
qubit_pairs = [
184+
(v2.grid_qubit_from_proto_id(target.ids[0]), v2.grid_qubit_from_proto_id(target.ids[1]))
185+
for ts in proto.valid_targets
186+
for target in ts.targets
187+
if len(target.ids) == 2 and ts.target_ordering == v2.device_pb2.TargetSet.SYMMETRIC
188+
]
189+
190+
# TODO(#5050) implement gate durations
191+
try:
192+
metadata = cirq.GridDeviceMetadata(
193+
qubit_pairs=qubit_pairs,
194+
gateset=cirq.Gateset(), # TODO(#5050) implement
195+
all_qubits=all_qubits,
196+
)
197+
except ValueError as ve: # coverage: ignore
198+
# Spec errors should have been caught in validation above.
199+
raise ValueError("DeviceSpecification is invalid.") from ve # coverage: ignore
200+
201+
return GridDevice(metadata)
202+
203+
@property
204+
def metadata(self) -> cirq.GridDeviceMetadata:
205+
"""Get metadata information for the device."""
206+
return self._metadata
207+
208+
def validate_operation(self, operation: cirq.Operation) -> None:
209+
"""Raises an exception if an operation is not valid.
210+
211+
An operation is valid if
212+
* The operation is in the device gateset.
213+
* The operation targets a valid qubit
214+
* The operation targets a valid qubit pair, if it is a two-qubit operation.
215+
216+
Args:
217+
operation: The operation to validate.
218+
219+
Raises:
220+
ValueError: The operation isn't valid for this device.
221+
"""
222+
# TODO(#5050) uncomment once gateset logic is implemented
223+
# if operation not in self._metadata.gateset:
224+
# raise ValueError(f'Operation {operation} is not a supported gate')
225+
226+
for q in operation.qubits:
227+
if q not in self._metadata.qubit_set:
228+
raise ValueError(f'Qubit not on device: {q!r}')
229+
230+
if (
231+
len(operation.qubits) == 2
232+
and frozenset(operation.qubits) not in self._metadata.qubit_pairs
233+
):
234+
raise ValueError(f'Qubit pair is not valid on device: {operation.qubits!r}')
235+
236+
def __str__(self) -> str:
237+
diagram = cirq.TextDiagramDrawer()
238+
239+
qubits = cast(Set[cirq.GridQubit], self._metadata.qubit_set)
240+
241+
# Don't print out extras newlines if the row/col doesn't start at 0
242+
min_col = min(q.col for q in qubits)
243+
min_row = min(q.row for q in qubits)
244+
245+
for q in qubits:
246+
diagram.write(q.col - min_col, q.row - min_row, str(q))
247+
248+
# Find pairs that are connected by two-qubit gates.
249+
Pair = Tuple[cirq.GridQubit, cirq.GridQubit]
250+
pairs = sorted({cast(Pair, tuple(pair)) for pair in self._metadata.qubit_pairs})
251+
252+
# Draw lines between connected pairs. Limit to horizontal/vertical
253+
# lines since that is all the diagram drawer can handle.
254+
for q1, q2 in pairs:
255+
if q1.row == q2.row or q1.col == q2.col:
256+
diagram.grid_line(
257+
q1.col - min_col, q1.row - min_row, q2.col - min_col, q2.row - min_row
258+
)
259+
260+
return diagram.render(horizontal_spacing=3, vertical_spacing=2, use_unicode_characters=True)
261+
262+
def _repr_pretty_(self, p: Any, cycle: bool) -> None:
263+
"""Creates ASCII diagram for Jupyter, IPython, etc."""
264+
# There should never be a cycle, but just in case use the default repr.
265+
p.text(repr(self) if cycle else str(self))
266+
267+
def __repr__(self) -> str:
268+
return f'cirq_google.GridDevice({repr(self._metadata)})'
269+
270+
def _json_dict_(self):
271+
return {'metadata': self._metadata}
272+
273+
@classmethod
274+
def _from_json_dict_(cls, metadata, **kwargs):
275+
return cls(metadata)
276+
277+
def _value_equality_values_(self):
278+
return self._metadata

0 commit comments

Comments
 (0)