Skip to content

Commit 3cd7c1b

Browse files
committed
GoogleDevice, minus gateset and gate durations
1 parent ecf6a83 commit 3cd7c1b

File tree

9 files changed

+651
-2
lines changed

9 files changed

+651
-2
lines changed

cirq-core/cirq/devices/grid_device_metadata.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,9 @@ def _json_dict_(self):
169169

170170
@classmethod
171171
def _from_json_dict_(cls, qubit_pairs, gateset, gate_durations, all_qubits, **kwargs):
172-
return cls(qubit_pairs, gateset, dict(gate_durations), all_qubits)
172+
return cls(
173+
qubit_pairs,
174+
gateset,
175+
None if gate_durations is None else dict(gate_durations),
176+
all_qubits,
177+
)

cirq-core/cirq/ops/gateset.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,10 @@ def _value_equality_values_(self) -> Any:
406406

407407
def __repr__(self) -> str:
408408
name_str = f'name = "{self.name}", ' if self.name is not None else ''
409+
gates_str = f'{self._gates_repr_str}, ' if len(self._gates_repr_str) > 0 else ''
409410
return (
410411
f'cirq.Gateset('
411-
f'{self._gates_repr_str}, '
412+
f'{gates_str}'
412413
f'{name_str}'
413414
f'unroll_circuit_op = {self._unroll_circuit_op},'
414415
f'accept_global_phase_op = {self._accept_global_phase_op})'

cirq-google/cirq_google/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from cirq_google.devices import (
6060
Bristlecone,
6161
Foxtail,
62+
GoogleDevice,
6263
SerializableDevice,
6364
Sycamore,
6465
Sycamore23,

cirq-google/cirq_google/devices/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
Sycamore23,
2020
)
2121

22+
from cirq_google.devices.google_device import (
23+
GoogleDevice,
24+
)
25+
2226
from cirq_google.devices.serializable_device import (
2327
SerializableDevice,
2428
)
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
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."""
16+
17+
from typing import Any, Set, Tuple, cast
18+
import cirq
19+
from cirq_google.api import v2
20+
21+
22+
@cirq.value_equality
23+
class GoogleDevice(cirq.Device):
24+
"""Device object representing Google devices.
25+
26+
For end users, instances of this class are typically accessed via
27+
`Engine.get_processor('processor_name').get_device()`.
28+
29+
This class is compliant with the core `cirq.Device` abstraction. In particular:
30+
* Device information is captured in the `metadata` property.
31+
* An instance of `GoogleDevice` can be used to validate circuits, moments, and operations.
32+
33+
Example use cases:
34+
35+
* Get an instance of a Google device.
36+
>>> device = cirq_google.get_engine().get_processor('weber').get_device()
37+
38+
* Print the grid layout of the device.
39+
>>> print(device)
40+
41+
* Determine whether a circuit can be run on the device.
42+
>>> device.validate_circuit(circuit) # Raises an exception if the circuit is invalid.
43+
44+
* Determine whether an operation can be run on the device.
45+
>>> device.validate_operation(operation) # Raises an exception if the operation is invalid.
46+
47+
* Get the `cirq.Gateset` containing valid gates for the device, and inspect the full list
48+
of valid gates.
49+
>>> gateset = device.metadata.gateset
50+
>>> print(gateset)
51+
52+
* Determine whether a gate is available on the device.
53+
>>> gate in device.metadata.gateset
54+
55+
* Get a collection of valid qubits on the device.
56+
>>> device.metadata.qubit_set
57+
58+
* Get a collection of valid qubit pairs for two-qubit gates.
59+
>>> device.metadata.qubit_pairs
60+
61+
* Get a collection of isolated qubits, i.e. qubits which are not part of any qubit pair.
62+
>>> device.metadata.isolated_qubits
63+
64+
* Get a collection of approximate durations of performing each gate supported by the device.
65+
>>> device.metadata.gate_durations
66+
67+
TODO(#5050) Add compilation_target_gatesets example.
68+
69+
Notes for cirq_google internal implementation:
70+
71+
For Google devices, the
72+
[DeviceSpecification proto](
73+
https://github.com/quantumlib/Cirq/blob/3969c2d3964cea56df33b329f036ba6810683882/cirq-google/cirq_google/api/v2/device.proto#L13
74+
)
75+
is the main specification for device information surfaced by the Quantum Computing Service.
76+
Thus, this class is should be instantiated using a `DeviceSpecification` proto via the
77+
`from_proto()` class method.
78+
"""
79+
80+
def __init__(self, metadata: cirq.GridDeviceMetadata):
81+
"""Creates a GoogleDevice object.
82+
83+
This constructor typically should not be used directly. Use `from_proto()` instead.
84+
"""
85+
self._metadata = metadata
86+
87+
@classmethod
88+
def from_proto(cls, proto: v2.device_pb2.DeviceSpecification) -> 'GoogleDevice':
89+
"""Create a `GoogleDevice` from a DeviceSpecification proto.
90+
91+
This class only supports `cirq.GridQubit`s and `cirq.NamedQubit`s. If a
92+
`DeviceSpecification.valid_qubits` string is in the form `<int>_<int>`, it is parsed as a
93+
GridQubit. Otherwise it is parsed as a NamedQubit.
94+
95+
Args:
96+
proto: The `DeviceSpecification` proto describing a Google device.
97+
98+
Raises:
99+
ValueError: If the given `DeviceSpecification` is invalid.
100+
"""
101+
102+
# Create qubit set
103+
all_qubits = [_qid_from_str(q) for q in proto.valid_qubits]
104+
105+
# Create qubit pair set
106+
#
107+
# While the `GateSpecification` proto message contains qubit target references, they are
108+
# ignored here because the following assumptions make them unnecessary currently:
109+
# * All valid qubit pairs work for all two-qubit gates.
110+
# * All valid qubits work for all single-qubit gates.
111+
# * Measurement gate can always be applied to all subset of qubits.
112+
#
113+
# TODO(#5169) Consider adding the reversed pair, depending on the issue's solution.
114+
qubit_pairs = [
115+
(_qid_from_str(target.ids[0]), _qid_from_str(target.ids[1]))
116+
for ts in proto.valid_targets
117+
for target in ts.targets
118+
if len(target.ids) == 2 and ts.target_ordering == v2.device_pb2.TargetSet.SYMMETRIC
119+
]
120+
121+
# TODO(#5050) implement gate durations
122+
try:
123+
metadata = cirq.GridDeviceMetadata(
124+
qubit_pairs=qubit_pairs,
125+
gateset=cirq.Gateset(), # TODO(#5050) implement
126+
all_qubits=all_qubits,
127+
)
128+
except ValueError as ve:
129+
raise ValueError("DeviceSpecification is invalid.") from ve
130+
131+
return GoogleDevice(metadata)
132+
133+
@property
134+
def metadata(self):
135+
"""Get metadata information for the device."""
136+
return self._metadata
137+
138+
def validate_operation(self, operation: cirq.Operation) -> None:
139+
"""Raises an exception if an operation is not valid.
140+
141+
An operation is valid if
142+
* The operation is in the device gateset.
143+
* The operation targets a valid qubit
144+
* The operation targets a valid qubit pair, if it is a two-qubit operation.
145+
146+
Args:
147+
operation: The operation to validate.
148+
149+
Raises:
150+
ValueError: The operation isn't valid for this device.
151+
"""
152+
# TODO(#5050) uncomment once gateset logic is implemented
153+
# if operation not in self._metadata.gateset:
154+
# raise ValueError(f'Operation {operation} is not a supported gate')
155+
156+
for q in operation.qubits:
157+
if q not in self._metadata.qubit_set:
158+
raise ValueError(f'Qubit not on device: {q!r}')
159+
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:
162+
raise ValueError(f'Qubit pair is not valid on device: {operation.qubits!r}')
163+
164+
def __str__(self) -> str:
165+
# If all qubits are grid qubits, render an appropriate text diagram.
166+
if all(isinstance(q, cirq.GridQubit) for q in self._metadata.qubit_set):
167+
diagram = cirq.TextDiagramDrawer()
168+
169+
qubits = cast(Set[cirq.GridQubit], self._metadata.qubit_set)
170+
171+
# Don't print out extras newlines if the row/col doesn't start at 0
172+
min_col = min(q.col for q in qubits)
173+
min_row = min(q.row for q in qubits)
174+
175+
for q in qubits:
176+
diagram.write(q.col - min_col, q.row - min_row, str(q))
177+
178+
# Find pairs that are connected by two-qubit gates.
179+
Pair = Tuple[cirq.GridQubit, cirq.GridQubit]
180+
pairs = sorted({cast(Pair, pair) for pair in self._metadata.qubit_pairs})
181+
182+
# Draw lines between connected pairs. Limit to horizontal/vertical
183+
# lines since that is all the diagram drawer can handle.
184+
for q1, q2 in pairs:
185+
if q1.row == q2.row or q1.col == q2.col:
186+
diagram.grid_line(
187+
q1.col - min_col, q1.row - min_row, q2.col - min_col, q2.row - min_row
188+
)
189+
190+
return diagram.render(
191+
horizontal_spacing=3, vertical_spacing=2, use_unicode_characters=True
192+
)
193+
194+
return super().__str__()
195+
196+
def _repr_pretty_(self, p: Any, cycle: bool) -> None:
197+
"""Creates ASCII diagram for Jupyter, IPython, etc."""
198+
# There should never be a cycle, but just in case use the default repr.
199+
p.text(repr(self) if cycle else str(self))
200+
201+
def __repr__(self) -> str:
202+
return f'cirq_google.GoogleDevice({repr(self._metadata)})'
203+
204+
def _json_dict_(self):
205+
return {
206+
'metadata': self._metadata,
207+
}
208+
209+
@classmethod
210+
def _from_json_dict_(cls, metadata, **kwargs):
211+
return cls(metadata)
212+
213+
def _value_equality_values_(self):
214+
return self._metadata
215+
216+
217+
def _qid_from_str(id_str: str) -> cirq.Qid:
218+
"""Translates a qubit id string info cirq.Qid objects.
219+
220+
Tries to translate to GridQubit if possible (e.g. '4_3'), otherwise
221+
falls back to using NamedQubit.
222+
"""
223+
try:
224+
return v2.grid_qubit_from_proto_id(id_str)
225+
except ValueError:
226+
return v2.named_qubit_from_proto_id(id_str)

0 commit comments

Comments
 (0)