Skip to content

Commit

Permalink
GoogleDevice, minus gateset and gate durations
Browse files Browse the repository at this point in the history
  • Loading branch information
verult committed Apr 6, 2022
1 parent ecf6a83 commit 3cd7c1b
Show file tree
Hide file tree
Showing 9 changed files with 651 additions and 2 deletions.
7 changes: 6 additions & 1 deletion cirq-core/cirq/devices/grid_device_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,9 @@ def _json_dict_(self):

@classmethod
def _from_json_dict_(cls, qubit_pairs, gateset, gate_durations, all_qubits, **kwargs):
return cls(qubit_pairs, gateset, dict(gate_durations), all_qubits)
return cls(
qubit_pairs,
gateset,
None if gate_durations is None else dict(gate_durations),
all_qubits,
)
3 changes: 2 additions & 1 deletion cirq-core/cirq/ops/gateset.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,9 +406,10 @@ def _value_equality_values_(self) -> Any:

def __repr__(self) -> str:
name_str = f'name = "{self.name}", ' if self.name is not None else ''
gates_str = f'{self._gates_repr_str}, ' if len(self._gates_repr_str) > 0 else ''
return (
f'cirq.Gateset('
f'{self._gates_repr_str}, '
f'{gates_str}'
f'{name_str}'
f'unroll_circuit_op = {self._unroll_circuit_op},'
f'accept_global_phase_op = {self._accept_global_phase_op})'
Expand Down
1 change: 1 addition & 0 deletions cirq-google/cirq_google/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
from cirq_google.devices import (
Bristlecone,
Foxtail,
GoogleDevice,
SerializableDevice,
Sycamore,
Sycamore23,
Expand Down
4 changes: 4 additions & 0 deletions cirq-google/cirq_google/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
Sycamore23,
)

from cirq_google.devices.google_device import (
GoogleDevice,
)

from cirq_google.devices.serializable_device import (
SerializableDevice,
)
Expand Down
226 changes: 226 additions & 0 deletions cirq-google/cirq_google/devices/google_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# Copyright 2022 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Device object representing Google devices."""

from typing import Any, Set, Tuple, cast
import cirq
from cirq_google.api import v2


@cirq.value_equality
class GoogleDevice(cirq.Device):
"""Device object representing Google devices.
For end users, instances of this class are typically accessed via
`Engine.get_processor('processor_name').get_device()`.
This class is compliant with the core `cirq.Device` abstraction. In particular:
* Device information is captured in the `metadata` property.
* An instance of `GoogleDevice` can be used to validate circuits, moments, and operations.
Example use cases:
* Get an instance of a Google device.
>>> device = cirq_google.get_engine().get_processor('weber').get_device()
* Print the grid layout of the device.
>>> print(device)
* Determine whether a circuit can be run on the device.
>>> device.validate_circuit(circuit) # Raises an exception if the circuit is invalid.
* Determine whether an operation can be run on the device.
>>> device.validate_operation(operation) # Raises an exception if the operation is invalid.
* Get the `cirq.Gateset` containing valid gates for the device, and inspect the full list
of valid gates.
>>> gateset = device.metadata.gateset
>>> print(gateset)
* Determine whether a gate is available on the device.
>>> gate in device.metadata.gateset
* Get a collection of valid qubits on the device.
>>> device.metadata.qubit_set
* Get a collection of valid qubit pairs for two-qubit gates.
>>> device.metadata.qubit_pairs
* Get a collection of isolated qubits, i.e. qubits which are not part of any qubit pair.
>>> device.metadata.isolated_qubits
* Get a collection of approximate durations of performing each gate supported by the device.
>>> device.metadata.gate_durations
TODO(#5050) Add compilation_target_gatesets example.
Notes for cirq_google internal implementation:
For Google devices, the
[DeviceSpecification proto](
https://github.com/quantumlib/Cirq/blob/3969c2d3964cea56df33b329f036ba6810683882/cirq-google/cirq_google/api/v2/device.proto#L13
)
is the main specification for device information surfaced by the Quantum Computing Service.
Thus, this class is should be instantiated using a `DeviceSpecification` proto via the
`from_proto()` class method.
"""

def __init__(self, metadata: cirq.GridDeviceMetadata):
"""Creates a GoogleDevice object.
This constructor typically should not be used directly. Use `from_proto()` instead.
"""
self._metadata = metadata

@classmethod
def from_proto(cls, proto: v2.device_pb2.DeviceSpecification) -> 'GoogleDevice':
"""Create a `GoogleDevice` from a DeviceSpecification proto.
This class only supports `cirq.GridQubit`s and `cirq.NamedQubit`s. If a
`DeviceSpecification.valid_qubits` string is in the form `<int>_<int>`, it is parsed as a
GridQubit. Otherwise it is parsed as a NamedQubit.
Args:
proto: The `DeviceSpecification` proto describing a Google device.
Raises:
ValueError: If the given `DeviceSpecification` is invalid.
"""

# Create qubit set
all_qubits = [_qid_from_str(q) for q in proto.valid_qubits]

# Create qubit pair set
#
# While the `GateSpecification` proto message contains qubit target references, they are
# ignored here because the following assumptions make them unnecessary currently:
# * All valid qubit pairs work for all two-qubit gates.
# * All valid qubits work for all single-qubit gates.
# * Measurement gate can always be applied to all subset of qubits.
#
# TODO(#5169) Consider adding the reversed pair, depending on the issue's solution.
qubit_pairs = [
(_qid_from_str(target.ids[0]), _qid_from_str(target.ids[1]))
for ts in proto.valid_targets
for target in ts.targets
if len(target.ids) == 2 and ts.target_ordering == v2.device_pb2.TargetSet.SYMMETRIC
]

# TODO(#5050) implement gate durations
try:
metadata = cirq.GridDeviceMetadata(
qubit_pairs=qubit_pairs,
gateset=cirq.Gateset(), # TODO(#5050) implement
all_qubits=all_qubits,
)
except ValueError as ve:
raise ValueError("DeviceSpecification is invalid.") from ve

return GoogleDevice(metadata)

@property
def metadata(self):
"""Get metadata information for the device."""
return self._metadata

def validate_operation(self, operation: cirq.Operation) -> None:
"""Raises an exception if an operation is not valid.
An operation is valid if
* The operation is in the device gateset.
* The operation targets a valid qubit
* The operation targets a valid qubit pair, if it is a two-qubit operation.
Args:
operation: The operation to validate.
Raises:
ValueError: The operation isn't valid for this device.
"""
# TODO(#5050) uncomment once gateset logic is implemented
# if operation not in self._metadata.gateset:
# raise ValueError(f'Operation {operation} is not a supported gate')

for q in operation.qubits:
if q not in self._metadata.qubit_set:
raise ValueError(f'Qubit not on device: {q!r}')

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

def __str__(self) -> str:
# If all qubits are grid qubits, render an appropriate text diagram.
if all(isinstance(q, cirq.GridQubit) for q in self._metadata.qubit_set):
diagram = cirq.TextDiagramDrawer()

qubits = cast(Set[cirq.GridQubit], self._metadata.qubit_set)

# Don't print out extras newlines if the row/col doesn't start at 0
min_col = min(q.col for q in qubits)
min_row = min(q.row for q in qubits)

for q in qubits:
diagram.write(q.col - min_col, q.row - min_row, str(q))

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

# Draw lines between connected pairs. Limit to horizontal/vertical
# lines since that is all the diagram drawer can handle.
for q1, q2 in pairs:
if q1.row == q2.row or q1.col == q2.col:
diagram.grid_line(
q1.col - min_col, q1.row - min_row, q2.col - min_col, q2.row - min_row
)

return diagram.render(
horizontal_spacing=3, vertical_spacing=2, use_unicode_characters=True
)

return super().__str__()

def _repr_pretty_(self, p: Any, cycle: bool) -> None:
"""Creates ASCII diagram for Jupyter, IPython, etc."""
# There should never be a cycle, but just in case use the default repr.
p.text(repr(self) if cycle else str(self))

def __repr__(self) -> str:
return f'cirq_google.GoogleDevice({repr(self._metadata)})'

def _json_dict_(self):
return {
'metadata': self._metadata,
}

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

def _value_equality_values_(self):
return self._metadata


def _qid_from_str(id_str: str) -> cirq.Qid:
"""Translates a qubit id string info cirq.Qid objects.
Tries to translate to GridQubit if possible (e.g. '4_3'), otherwise
falls back to using NamedQubit.
"""
try:
return v2.grid_qubit_from_proto_id(id_str)
except ValueError:
return v2.named_qubit_from_proto_id(id_str)
Loading

0 comments on commit 3cd7c1b

Please sign in to comment.