diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 5afab01a07d..2e9d2f54cf0 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -82,6 +82,7 @@ ConstantQubitNoiseModel, Device, DeviceMetadata, + GridDeviceMetadata, GridQid, GridQubit, LineQid, diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index 18a7100a035..926c72c5cb0 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -19,6 +19,10 @@ SymmetricalQidPair, ) +from cirq.devices.grid_device_metadata import ( + GridDeviceMetadata, +) + from cirq.devices.grid_qubit import ( GridQid, GridQubit, diff --git a/cirq-core/cirq/devices/device.py b/cirq-core/cirq/devices/device.py index 2986205572f..e6a4b8ae96f 100644 --- a/cirq-core/cirq/devices/device.py +++ b/cirq-core/cirq/devices/device.py @@ -13,7 +13,15 @@ # limitations under the License. import abc -from typing import TYPE_CHECKING, Optional, AbstractSet, cast, FrozenSet, Iterator, Iterable +from typing import ( + TYPE_CHECKING, + Optional, + AbstractSet, + cast, + FrozenSet, + Iterator, + Iterable, +) import networkx as nx from cirq import value diff --git a/cirq-core/cirq/devices/grid_device_metadata.py b/cirq-core/cirq/devices/grid_device_metadata.py new file mode 100644 index 00000000000..18e809c399b --- /dev/null +++ b/cirq-core/cirq/devices/grid_device_metadata.py @@ -0,0 +1,137 @@ +# 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. +"""Metadata subtype for 2D Homogenous devices.""" + +from typing import ( + TYPE_CHECKING, + Optional, + FrozenSet, + Iterable, + Tuple, + Dict, +) + +import networkx as nx +from cirq import value +from cirq.devices import device + +if TYPE_CHECKING: + import cirq + + +@value.value_equality +class GridDeviceMetadata(device.DeviceMetadata): + """Hardware metadata for homogenous 2d symmetric grid devices.""" + + def __init__( + self, + qubit_pairs: Iterable[Tuple['cirq.Qid', 'cirq.Qid']], + supported_gates: 'cirq.Gateset', + gate_durations: Optional[Dict['cirq.Gateset', 'cirq.Duration']] = None, + ): + """Create a GridDeviceMetadata object. + + Create a GridDevice which has a well defined set of couplable + qubit pairs that have the same two qubit gates available in + both coupling directions. + + Args: + qubit_pairs: Iterable of pairs of `cirq.Qid`s representing + bi-directional couplings. + supported_gates: `cirq.Gateset` indicating gates supported + everywhere on the device. + gate_durations: Optional dictionary of `cirq.Gateset` + instances mapping to `cirq.Duration` instances for + gate timing metadata information. If provided, + must match all entries in supported_gates. + + Raises: + ValueError: if the union of gateset keys in gate_durations, + do not represent an identical gateset to supported_gates. + """ + qubit_pairs = list(qubit_pairs) + flat_pairs = [q for pair in qubit_pairs for q in pair] + # Keep lexigraphically smaller tuples for undirected edges. + sorted_pairs = sorted(qubit_pairs) + pair_set = set() + for a, b in sorted_pairs: + if (b, a) not in pair_set: + pair_set.add((a, b)) + + connectivity = nx.Graph() + connectivity.add_edges_from(sorted(pair_set), directed=False) + super().__init__(flat_pairs, connectivity) + self._qubit_pairs = frozenset(pair_set) + self._supported_gates = supported_gates + + if gate_durations is not None: + working_gatefamilies = frozenset( + g for gset in gate_durations.keys() for g in gset.gates + ) + if working_gatefamilies != supported_gates.gates: + missing_items = working_gatefamilies.difference(supported_gates.gates) + raise ValueError( + "Supplied gate_durations contains gates not present" + f" in supported_gates. {missing_items} in supported_gates" + " is False." + ) + + self._gate_durations = gate_durations + + @property + def qubit_pairs(self) -> FrozenSet[Tuple['cirq.Qid', 'cirq.Qid']]: + """Returns the set of all couple-able qubits on the device.""" + return self._qubit_pairs + + @property + def gateset(self) -> 'cirq.Gateset': + """Returns the `cirq.Gateset` of supported gates on this device.""" + return self._supported_gates + + @property + def gate_durations(self) -> Optional[Dict['cirq.Gateset', 'cirq.Duration']]: + """Get a dictionary mapping from gateset to duration for gates.""" + return self._gate_durations + + def _value_equality_values_(self): + duration_equality = '' + if self._gate_durations is not None: + duration_equality = sorted(self._gate_durations.items(), key=lambda x: repr(x[0])) + + return ( + tuple(sorted(self._qubit_pairs)), + self._supported_gates, + tuple(duration_equality), + ) + + def __repr__(self) -> str: + return ( + f'cirq.GridDeviceMetadata({repr(self._qubit_pairs)},' + f' {repr(self._supported_gates)}, {repr(self._gate_durations)})' + ) + + def _json_dict_(self): + duration_payload = None + if self._gate_durations is not None: + duration_payload = sorted(self._gate_durations.items(), key=lambda x: repr(x[0])) + + return { + 'qubit_pairs': sorted(list(self._qubit_pairs)), + 'supported_gates': self._supported_gates, + 'gate_durations': duration_payload, + } + + @classmethod + def _from_json_dict_(cls, qubit_pairs, supported_gates, gate_durations, **kwargs): + return cls(qubit_pairs, supported_gates, dict(gate_durations)) diff --git a/cirq-core/cirq/devices/grid_device_metadata_test.py b/cirq-core/cirq/devices/grid_device_metadata_test.py new file mode 100644 index 00000000000..7febcc95844 --- /dev/null +++ b/cirq-core/cirq/devices/grid_device_metadata_test.py @@ -0,0 +1,114 @@ +# 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. +"""Tests for GridDevicemetadata.""" + +import pytest +import cirq +import networkx as nx + + +def test_griddevice_metadata(): + qubits = cirq.GridQubit.rect(2, 3) + qubit_pairs = [(a, b) for a in qubits for b in qubits if a != b and a.is_adjacent(b)] + + gateset = cirq.Gateset(cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZ) + metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset) + + expected_pairings = frozenset( + { + (cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)), + (cirq.GridQubit(0, 1), cirq.GridQubit(0, 2)), + (cirq.GridQubit(0, 1), cirq.GridQubit(1, 1)), + (cirq.GridQubit(0, 2), cirq.GridQubit(1, 2)), + (cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)), + (cirq.GridQubit(1, 1), cirq.GridQubit(1, 2)), + (cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)), + } + ) + assert metadata.qubit_set == frozenset(qubits) + assert metadata.qubit_pairs == expected_pairings + assert metadata.gateset == gateset + expected_graph = nx.Graph() + expected_graph.add_edges_from(sorted(list(expected_pairings)), directed=False) + assert metadata.nx_graph.edges() == expected_graph.edges() + assert metadata.nx_graph.nodes() == expected_graph.nodes() + assert metadata.gate_durations is None + + +def test_griddevice_metadata_bad_durations(): + qubits = tuple(cirq.GridQubit.rect(1, 2)) + + gateset = cirq.Gateset(cirq.XPowGate, cirq.YPowGate) + invalid_duration = { + cirq.Gateset(cirq.XPowGate): cirq.Duration(nanos=1), + cirq.Gateset(cirq.ZPowGate): cirq.Duration(picos=1), + } + with pytest.raises(ValueError, match="ZPowGate"): + cirq.GridDeviceMetadata([qubits], gateset, gate_durations=invalid_duration) + + +def test_griddevice_json_load(): + qubits = cirq.GridQubit.rect(2, 3) + qubit_pairs = [(a, b) for a in qubits for b in qubits if a != b and a.is_adjacent(b)] + gateset = cirq.Gateset(cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate) + duration = { + cirq.Gateset(cirq.XPowGate): cirq.Duration(nanos=1), + cirq.Gateset(cirq.YPowGate): cirq.Duration(picos=2), + cirq.Gateset(cirq.ZPowGate): cirq.Duration(picos=3), + } + metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration) + rep_str = cirq.to_json(metadata) + assert metadata == cirq.read_json(json_text=rep_str) + + +def test_griddevice_metadata_equality(): + qubits = cirq.GridQubit.rect(2, 3) + qubit_pairs = [(a, b) for a in qubits for b in qubits if a != b and a.is_adjacent(b)] + gateset = cirq.Gateset(cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate) + duration = { + cirq.Gateset(cirq.XPowGate): cirq.Duration(nanos=1), + cirq.Gateset(cirq.YPowGate): cirq.Duration(picos=3), + cirq.Gateset(cirq.ZPowGate): cirq.Duration(picos=2), + } + duration2 = { + cirq.Gateset(cirq.XPowGate): cirq.Duration(nanos=10), + cirq.Gateset(cirq.YPowGate): cirq.Duration(picos=13), + cirq.Gateset(cirq.ZPowGate): cirq.Duration(picos=12), + } + metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration) + metadata2 = cirq.GridDeviceMetadata(qubit_pairs[:2], gateset, gate_durations=duration) + metadata3 = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=None) + metadata4 = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration2) + metadata5 = cirq.GridDeviceMetadata(reversed(qubit_pairs), gateset, gate_durations=duration) + + eq = cirq.testing.EqualsTester() + eq.add_equality_group(metadata) + eq.add_equality_group(metadata2) + eq.add_equality_group(metadata3) + eq.add_equality_group(metadata4) + + assert metadata == metadata5 + + +def test_repr(): + qubits = cirq.GridQubit.rect(2, 3) + qubit_pairs = [(a, b) for a in qubits for b in qubits if a != b and a.is_adjacent(b)] + gateset = cirq.Gateset(cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate) + duration = { + cirq.Gateset(cirq.XPowGate): cirq.Duration(nanos=1), + cirq.Gateset(cirq.YPowGate): cirq.Duration(picos=3), + cirq.Gateset(cirq.ZPowGate): cirq.Duration(picos=2), + } + metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration) + cirq.testing.assert_equivalent_repr(metadata) diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 2e7cc71797f..2eb74bd732e 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -87,6 +87,7 @@ def _parallel_gate_op(gate, qubits): 'GeneralizedAmplitudeDampingChannel': cirq.GeneralizedAmplitudeDampingChannel, 'GlobalPhaseGate': cirq.GlobalPhaseGate, 'GlobalPhaseOperation': cirq.GlobalPhaseOperation, + 'GridDeviceMetadata': cirq.GridDeviceMetadata, 'GridInteractionLayer': GridInteractionLayer, 'GridParallelXEBMetadata': GridParallelXEBMetadata, 'GridQid': cirq.GridQid, diff --git a/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.json b/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.json new file mode 100644 index 00000000000..c2379c7ffa1 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.json @@ -0,0 +1,183 @@ +{ + "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 + } + ] + ], + "supported_gates": { + "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": "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 + } + ], + "name": null, + "unroll_circuit_op": true, + "accept_global_phase_op": true + }, + { + "cirq_type": "Duration", + "picos": 1000 + } + ], + [ + { + "cirq_type": "Gateset", + "gates": [ + { + "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 + } + ], + "name": null, + "unroll_circuit_op": true, + "accept_global_phase_op": true + }, + { + "cirq_type": "Duration", + "picos": 1 + } + ], + [ + { + "cirq_type": "Gateset", + "gates": [ + { + "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 + }, + { + "cirq_type": "Duration", + "picos": 1 + } + ] + ] +} diff --git a/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.repr b/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.repr new file mode 100644 index 00000000000..7730e8a3219 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.repr @@ -0,0 +1 @@ +cirq.GridDeviceMetadata(frozenset({(cirq.GridQubit(0, 2), cirq.GridQubit(1, 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.GridQubit(0, 1), cirq.GridQubit(0, 2)), (cirq.GridQubit(1, 1), cirq.GridQubit(1, 2)), (cirq.GridQubit(1, 0), cirq.GridQubit(1, 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.Gateset(cirq.ops.common_gates.XPowGate, unroll_circuit_op = True,accept_global_phase_op = True): cirq.Duration(nanos=1), cirq.Gateset(cirq.ops.common_gates.YPowGate, unroll_circuit_op = True,accept_global_phase_op = True): cirq.Duration(picos=1), cirq.Gateset(cirq.ops.common_gates.ZPowGate, unroll_circuit_op = True,accept_global_phase_op = True): cirq.Duration(picos=1)}) \ No newline at end of file