diff --git a/cirq-google/cirq_google/__init__.py b/cirq-google/cirq_google/__init__.py index 8000a2214626..d39a35ea1d46 100644 --- a/cirq-google/cirq_google/__init__.py +++ b/cirq-google/cirq_google/__init__.py @@ -57,8 +57,8 @@ from cirq_google.devices import ( Bristlecone, Foxtail, - GoogleDevice, GoogleNoiseProperties, + GridDevice, NoiseModelFromGoogleNoiseProperties, SerializableDevice, Sycamore, diff --git a/cirq-google/cirq_google/devices/__init__.py b/cirq-google/cirq_google/devices/__init__.py index a2d453c9d480..f1040c6626d0 100644 --- a/cirq-google/cirq_google/devices/__init__.py +++ b/cirq-google/cirq_google/devices/__init__.py @@ -19,7 +19,7 @@ from cirq_google.devices.known_devices import Bristlecone, Foxtail, Sycamore, Sycamore23 -from cirq_google.devices.google_device import GoogleDevice +from cirq_google.devices.grid_device import GridDevice from cirq_google.devices.serializable_device import SerializableDevice diff --git a/cirq-google/cirq_google/devices/google_device.py b/cirq-google/cirq_google/devices/google_device.py deleted file mode 100644 index c9d526086ccc..000000000000 --- a/cirq-google/cirq_google/devices/google_device.py +++ /dev/null @@ -1,226 +0,0 @@ -# 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 `_`, 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) diff --git a/cirq-google/cirq_google/devices/google_device_test.py b/cirq-google/cirq_google/devices/google_device_test.py deleted file mode 100644 index 8fd9cb863e13..000000000000 --- a/cirq-google/cirq_google/devices/google_device_test.py +++ /dev/null @@ -1,207 +0,0 @@ -# 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. - -import unittest.mock as mock - -import pytest - -import cirq -import cirq_google -from cirq_google.api import v2 - - -GRID_HEIGHT = 5 - - -def _create_device_spec_with_horizontal_couplings(): - # Qubit layout: - # x -- x - # x -- x - # x -- x - # x -- x - # x -- x - - grid_qubits = [cirq.GridQubit(i, j) for i in range(GRID_HEIGHT) for j in range(2)] - spec = v2.device_pb2.DeviceSpecification() - spec.valid_qubits.extend([v2.qubit_to_proto_id(q) for q in grid_qubits]) - grid_targets = spec.valid_targets.add() - grid_targets.name = '2_qubit_targets' - grid_targets.target_ordering = v2.device_pb2.TargetSet.SYMMETRIC - for row in range(GRID_HEIGHT): - new_target = grid_targets.targets.add() - new_target.ids.extend([v2.qubit_to_proto_id(cirq.GridQubit(row, j)) for j in range(2)]) - gate = spec.valid_gates.add() - gate.syc.SetInParent() - gate.gate_duration_picos = 12000 - gate.valid_targets.extend(['2_qubit_targets']) - - return grid_qubits, spec - - -def _create_device_spec_with_all_couplings(): - # Qubit layout: - # x -- x - # | | - # x -- x - # | | - # x -- x - # | | - # x -- x - # | | - # x -- x - - grid_qubits, spec = _create_device_spec_with_horizontal_couplings() - for row in range(GRID_HEIGHT - 1): - for col in range(2): - new_target = spec.valid_targets[0].targets.add() - new_target.ids.extend( - [ - v2.qubit_to_proto_id(cirq.GridQubit(row, col)), - v2.qubit_to_proto_id(cirq.GridQubit(row + 1, col)), - ] - ) - return grid_qubits, spec - - -def _create_device_spec_with_qubit_pair_self_loops() -> v2.device_pb2.DeviceSpecification: - q_proto_id = v2.qubit_to_proto_id(cirq.NamedQubit('q')) - - spec = v2.device_pb2.DeviceSpecification() - spec.valid_qubits.extend([q_proto_id]) - targets = spec.valid_targets.add() - targets.name = 'test_targets' - targets.target_ordering = v2.device_pb2.TargetSet.SYMMETRIC - new_target = targets.targets.add() - new_target.ids.extend([q_proto_id, q_proto_id]) - - return spec - - -def _create_device_spec_with_invalid_qubit_in_qubit_pair() -> v2.device_pb2.DeviceSpecification: - q_proto_ids = [v2.qubit_to_proto_id(cirq.GridQubit(0, i)) for i in range(2)] - - spec = v2.device_pb2.DeviceSpecification() - spec.valid_qubits.extend([q_proto_ids[0]]) - targets = spec.valid_targets.add() - targets.name = 'test_targets' - targets.target_ordering = v2.device_pb2.TargetSet.SYMMETRIC - new_target = targets.targets.add() - new_target.ids.extend([q_proto_ids[0], q_proto_ids[1]]) - - return spec - - -def test_google_device_from_proto_and_validation(): - grid_qubits, spec = _create_device_spec_with_horizontal_couplings() - - device = cirq_google.GoogleDevice.from_proto(spec) - - assert len(device.metadata.qubit_set) == len(grid_qubits) - assert device.metadata.qubit_set == frozenset(grid_qubits) - assert all( - (cirq.GridQubit(row, 0), cirq.GridQubit(row, 1)) in device.metadata.qubit_pairs - for row in range(GRID_HEIGHT) - ) - - -def test_google_device_validate_operations_positive(): - grid_qubits, spec = _create_device_spec_with_horizontal_couplings() - device = cirq_google.GoogleDevice.from_proto(spec) - - for q in grid_qubits: - device.validate_operation(cirq.X(q)) - - # horizontal qubit pairs - for i in range(GRID_HEIGHT): - device.validate_operation(cirq.CZ(grid_qubits[2 * i], grid_qubits[2 * i + 1])) - - # TODO(#5050) verify validate_operations gateset support - - -def test_google_device_validate_operations_negative(): - grid_qubits, spec = _create_device_spec_with_horizontal_couplings() - device = cirq_google.GoogleDevice.from_proto(spec) - - q = cirq.GridQubit(10, 10) - with pytest.raises(ValueError, match='Qubit not on device'): - device.validate_operation(cirq.X(q)) - - # vertical qubit pair - q00, q10 = grid_qubits[0], grid_qubits[2] # (0, 0), (1, 0) - with pytest.raises(ValueError, match='Qubit pair is not valid'): - device.validate_operation(cirq.CZ(q00, q10)) - - # TODO(#5050) verify validate_operations gateset errors - - -@pytest.mark.parametrize( - 'spec', - [ - # TODO(#5050) implement once gateset support is implemented - # _create_device_spec_with_missing_gate_durations(), - _create_device_spec_with_qubit_pair_self_loops(), - _create_device_spec_with_invalid_qubit_in_qubit_pair(), - ], -) -def test_google_device_invalid_device_spec(spec): - with pytest.raises(ValueError, match='DeviceSpecification is invalid'): - cirq_google.GoogleDevice.from_proto(spec) - - -def test_google_device_repr_json(): - _, spec = _create_device_spec_with_horizontal_couplings() - device = cirq_google.GoogleDevice.from_proto(spec) - - assert eval(repr(device)) == device - assert cirq.read_json(json_text=cirq.to_json(device)) == device - - -def test_google_device_str_grid_qubits(): - _, spec = _create_device_spec_with_all_couplings() - device = cirq_google.GoogleDevice.from_proto(spec) - - assert ( - str(device) - == """\ -(0, 0)───(0, 1) -│ │ -│ │ -(1, 0)───(1, 1) -│ │ -│ │ -(2, 0)───(2, 1) -│ │ -│ │ -(3, 0)───(3, 1) -│ │ -│ │ -(4, 0)───(4, 1)""" - ) - - -@pytest.mark.parametrize('cycle,func', [(False, str), (True, repr)]) -def test_google_device_repr_pretty(cycle, func): - _, spec = _create_device_spec_with_all_couplings() - device = cirq_google.GoogleDevice.from_proto(spec) - printer = mock.Mock() - device._repr_pretty_(printer, cycle) - printer.text.assert_called_once_with(func(device)) - - -def test_serializable_device_str_named_qubits(): - q_proto_id = v2.qubit_to_proto_id(cirq.NamedQubit('q')) - spec = v2.device_pb2.DeviceSpecification() - spec.valid_qubits.extend([q_proto_id]) - device = cirq_google.GoogleDevice.from_proto(spec) - assert device.__class__.__name__ in str(device) diff --git a/cirq-google/cirq_google/json_resolver_cache.py b/cirq-google/cirq_google/json_resolver_cache.py index f85549192af4..913590f33569 100644 --- a/cirq-google/cirq_google/json_resolver_cache.py +++ b/cirq-google/cirq_google/json_resolver_cache.py @@ -33,7 +33,7 @@ def _class_resolver_dictionary() -> Dict[str, ObjectFactory]: 'GoogleNoiseProperties': cirq_google.GoogleNoiseProperties, 'SycamoreGate': cirq_google.SycamoreGate, 'GateTabulation': cirq_google.GateTabulation, - 'GoogleDevice': cirq_google.GoogleDevice, + 'GridDevice': cirq_google.GridDevice, 'PhysicalZTag': cirq_google.PhysicalZTag, 'FSimGateFamily': cirq_google.FSimGateFamily, 'FloquetPhasedFSimCalibrationOptions': cirq_google.FloquetPhasedFSimCalibrationOptions, diff --git a/cirq-google/cirq_google/json_test_data/GoogleDevice.json b/cirq-google/cirq_google/json_test_data/GoogleDevice.json deleted file mode 100644 index 890ce5c3648e..000000000000 --- a/cirq-google/cirq_google/json_test_data/GoogleDevice.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "cirq_type": "GoogleDevice", - "metadata": { - "cirq_type": "GridDeviceMetadata", - "qubit_pairs": [ - [ - { - "cirq_type": "GridQubit", - "row": 0, - "col": 0 - }, - { - "cirq_type": "GridQubit", - "row": 0, - "col": 1 - } - ], - [ - { - "cirq_type": "GridQubit", - "row": 0, - "col": 0 - }, - { - "cirq_type": "GridQubit", - "row": 1, - "col": 0 - } - ], - [ - { - "cirq_type": "GridQubit", - "row": 0, - "col": 1 - }, - { - "cirq_type": "GridQubit", - "row": 0, - "col": 2 - } - ], - [ - { - "cirq_type": "GridQubit", - "row": 0, - "col": 1 - }, - { - "cirq_type": "GridQubit", - "row": 1, - "col": 1 - } - ], - [ - { - "cirq_type": "GridQubit", - "row": 0, - "col": 2 - }, - { - "cirq_type": "GridQubit", - "row": 1, - "col": 2 - } - ], - [ - { - "cirq_type": "GridQubit", - "row": 1, - "col": 0 - }, - { - "cirq_type": "GridQubit", - "row": 1, - "col": 1 - } - ], - [ - { - "cirq_type": "GridQubit", - "row": 1, - "col": 1 - }, - { - "cirq_type": "GridQubit", - "row": 1, - "col": 2 - } - ] - ], - "gateset": { - "cirq_type": "Gateset", - "gates": [ - { - "cirq_type": "GateFamily", - "gate": "XPowGate", - "name": "Type GateFamily: cirq.ops.common_gates.XPowGate", - "description": "Accepts `cirq.Gate` instances `g` s.t. `isinstance(g, cirq.ops.common_gates.XPowGate)`", - "ignore_global_phase": true - }, - { - "cirq_type": "GateFamily", - "gate": "YPowGate", - "name": "Type GateFamily: cirq.ops.common_gates.YPowGate", - "description": "Accepts `cirq.Gate` instances `g` s.t. `isinstance(g, cirq.ops.common_gates.YPowGate)`", - "ignore_global_phase": true - }, - { - "cirq_type": "GateFamily", - "gate": "ZPowGate", - "name": "Type GateFamily: cirq.ops.common_gates.ZPowGate", - "description": "Accepts `cirq.Gate` instances `g` s.t. `isinstance(g, cirq.ops.common_gates.ZPowGate)`", - "ignore_global_phase": true - } - ], - "name": null, - "unroll_circuit_op": true, - "accept_global_phase_op": true - }, - "gate_durations": [ - [ - { - "cirq_type": "GateFamily", - "gate": "XPowGate", - "name": "Type GateFamily: cirq.ops.common_gates.XPowGate", - "description": "Accepts `cirq.Gate` instances `g` s.t. `isinstance(g, cirq.ops.common_gates.XPowGate)`", - "ignore_global_phase": true - }, - { - "cirq_type": "Duration", - "picos": 1000 - } - ], - [ - { - "cirq_type": "GateFamily", - "gate": "YPowGate", - "name": "Type GateFamily: cirq.ops.common_gates.YPowGate", - "description": "Accepts `cirq.Gate` instances `g` s.t. `isinstance(g, cirq.ops.common_gates.YPowGate)`", - "ignore_global_phase": true - }, - { - "cirq_type": "Duration", - "picos": 1 - } - ], - [ - { - "cirq_type": "GateFamily", - "gate": "ZPowGate", - "name": "Type GateFamily: cirq.ops.common_gates.ZPowGate", - "description": "Accepts `cirq.Gate` instances `g` s.t. `isinstance(g, cirq.ops.common_gates.ZPowGate)`", - "ignore_global_phase": true - }, - { - "cirq_type": "Duration", - "picos": 1 - } - ] - ], - "all_qubits": [ - { - "cirq_type": "GridQubit", - "row": 0, - "col": 0 - }, - { - "cirq_type": "GridQubit", - "row": 0, - "col": 1 - }, - { - "cirq_type": "GridQubit", - "row": 0, - "col": 2 - }, - { - "cirq_type": "GridQubit", - "row": 1, - "col": 0 - }, - { - "cirq_type": "GridQubit", - "row": 1, - "col": 1 - }, - { - "cirq_type": "GridQubit", - "row": 1, - "col": 2 - }, - { - "cirq_type": "GridQubit", - "row": 9, - "col": 9 - }, - { - "cirq_type": "GridQubit", - "row": 10, - "col": 10 - } - ] - } -} \ No newline at end of file diff --git a/cirq-google/cirq_google/json_test_data/GoogleDevice.repr b/cirq-google/cirq_google/json_test_data/GoogleDevice.repr deleted file mode 100644 index 6b7b096bacac..000000000000 --- a/cirq-google/cirq_google/json_test_data/GoogleDevice.repr +++ /dev/null @@ -1 +0,0 @@ -cirq_google.GoogleDevice(cirq.GridDeviceMetadata(frozenset({(cirq.GridQubit(1, 1), cirq.GridQubit(1, 2)), (cirq.GridQubit(0, 2), cirq.GridQubit(1, 2)), (cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)), (cirq.GridQubit(0, 1), cirq.GridQubit(0, 2)), (cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)), (cirq.GridQubit(0, 1), cirq.GridQubit(1, 1)), (cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))}), cirq.Gateset(cirq.ops.common_gates.XPowGate, cirq.ops.common_gates.YPowGate, cirq.ops.common_gates.ZPowGate, unroll_circuit_op = True,accept_global_phase_op = True), {cirq.GateFamily(gate=cirq.ops.common_gates.XPowGate, ignore_global_phase=True): cirq.Duration(nanos=1), cirq.GateFamily(gate=cirq.ops.common_gates.YPowGate, ignore_global_phase=True): cirq.Duration(picos=1), cirq.GateFamily(gate=cirq.ops.common_gates.ZPowGate, ignore_global_phase=True): cirq.Duration(picos=1)}, frozenset({cirq.GridQubit(0, 0), cirq.GridQubit(1, 2), cirq.GridQubit(1, 0), cirq.GridQubit(0, 2), cirq.GridQubit(10, 10), cirq.GridQubit(0, 1), cirq.GridQubit(1, 1), cirq.GridQubit(9, 9)}))) \ No newline at end of file