From f8ec32144e38612b811e4f51b3abdf1f79403b87 Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Wed, 12 Jan 2022 16:59:58 -0800 Subject: [PATCH 1/4] Add DeviceMetaData class. --- cirq-core/cirq/__init__.py | 1 + cirq-core/cirq/devices/__init__.py | 1 + cirq-core/cirq/devices/device.py | 77 ++++++++++++++++++- cirq-core/cirq/devices/device_test.py | 31 ++++++++ cirq-core/cirq/json_resolver_cache.py | 1 + .../cirq/protocols/json_serialization_test.py | 9 +-- .../json_test_data/DeviceMetaData.json | 54 +++++++++++++ .../json_test_data/DeviceMetaData.repr | 1 + 8 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json create mode 100644 cirq-core/cirq/protocols/json_test_data/DeviceMetaData.repr diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 137ca460eda..b0f2d060f45 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -81,6 +81,7 @@ from cirq.devices import ( ConstantQubitNoiseModel, Device, + DeviceMetaData, GridQid, GridQubit, LineQid, diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index b1480a9513d..3e9c08cb347 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -15,6 +15,7 @@ """Types for devices, device-specific qubits, and noise models.""" from cirq.devices.device import ( Device, + DeviceMetaData, SymmetricalQidPair, ) diff --git a/cirq-core/cirq/devices/device.py b/cirq-core/cirq/devices/device.py index 05da94b0ca0..eca9b5d981e 100644 --- a/cirq-core/cirq/devices/device.py +++ b/cirq-core/cirq/devices/device.py @@ -13,8 +13,9 @@ # limitations under the License. import abc -from typing import TYPE_CHECKING, Optional, AbstractSet, cast, FrozenSet, Iterator +from typing import TYPE_CHECKING, Optional, AbstractSet, cast, FrozenSet, Iterator, Iterable +import networkx as nx from cirq import value from cirq.devices.grid_qubit import _BaseGridQid from cirq.devices.line_qubit import _BaseLineQid @@ -178,3 +179,77 @@ def __iter__(self) -> Iterator['cirq.Qid']: def __contains__(self, item: 'cirq.Qid') -> bool: return item in self.qids + + +@value.value_equality +class DeviceMetaData: + """Parent type for all device specific metadata classes.""" + + def qubit_set(self) -> Optional[FrozenSet['cirq.Qid']]: + """Returns a set of qubits on the device, if possible. + + Returns: + Frozenset of qubits on device if specified, otherwise None. + """ + return cast(Optional[FrozenSet['cirq.Qid']], self._qubits_set) + + def nx_graph(self) -> Optional['nx.Graph']: + """Returns a nx.Graph where nodes are qubits are couple-able qubits. + + Returns: + `nx.Graph` of device connectivity if specified, otherwise None. + """ + return self._nx_graph + + def _value_equality_values_(self): + graph_equality = None + if self._nx_graph is not None: + graph_equality = (sorted(self._nx_graph.nodes()), sorted(self._nx_graph.edges())) + + qubit_equality = None + if self._qubits_set is not None: + qubit_equality = sorted(list(self._qubits_set)) + + return qubit_equality, graph_equality + + def _json_dict_(self): + graph_payload = '' + if self._nx_graph is not None: + graph_payload = nx.readwrite.json_graph.node_link_data(self._nx_graph) + + qubits_payload = '' + if self._qubits_set is not None: + qubits_payload = sorted(list(self._qubits_set)) + return {'qubits': qubits_payload, 'nx_graph': graph_payload} + + @classmethod + def _from_json_dict_(cls, qubits, nx_graph, **kwargs): + if qubits == '': + qubits = None + graph_obj = None + if nx_graph != '': + graph_obj = nx.readwrite.json_graph.node_link_graph(nx_graph) + return cls(qubits, graph_obj, **kwargs) + + def __init__( + self, + qubits: Optional[Iterable['cirq.Qid']] = None, + nx_graph: Optional['nx.graph'] = None, + **kwargs, + ): + """Construct a DeviceMetaData object. + + Args: + qubits: Optional iterable of `cirq.Qid`s that exist on the device. + nx_graph: Optional `nx.Graph` describing qubit connectivity + on a device. Nodes represent qubits, directed edges indicate + directional coupling, undirected edges indicate bi-directional + coupling. + **kwargs: Additional miscellanous properties to attach to metadata. + """ + self._qubits_set = qubits + if self._qubits_set is not None: + self._qubits_set = frozenset(self._qubits_set) + + self._nx_graph = nx_graph + self.__dict__.update(kwargs) diff --git a/cirq-core/cirq/devices/device_test.py b/cirq-core/cirq/devices/device_test.py index f730492b5e3..bfc99bb0c59 100644 --- a/cirq-core/cirq/devices/device_test.py +++ b/cirq-core/cirq/devices/device_test.py @@ -1,5 +1,6 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice import pytest +import networkx as nx import cirq @@ -75,3 +76,33 @@ def test_qid_pair(): with pytest.raises(ValueError, match='A QidPair cannot have identical qids.'): cirq.SymmetricalQidPair(q0, q0) + + +def test_metadata(): + qubits = cirq.LineQubit.range(4) + graph = nx.star_graph(3) + metadata = cirq.DeviceMetaData(qubits, graph) + assert metadata.qubit_set() == frozenset(qubits) + assert metadata.nx_graph() == graph + + metadata = cirq.DeviceMetaData() + assert metadata.qubit_set() is None + assert metadata.nx_graph() is None + + metadata = cirq.DeviceMetaData(None, None, additional_property=5) + assert metadata.additional_property == 5 + + +def test_metadata_json_load_logic(): + qubits = cirq.LineQubit.range(4) + graph = nx.star_graph(3) + metadata = cirq.DeviceMetaData(qubits, graph) + str_rep = cirq.to_json(metadata) + assert metadata == cirq.read_json(json_text=str_rep) + + qubits = None + graph = None + other_arg = 5 + metadata = cirq.DeviceMetaData(qubits, graph, other_arg=other_arg) + str_rep = cirq.to_json(metadata) + assert metadata == cirq.read_json(json_text=str_rep) diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index c60a8cf66d1..d6c27170937 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -77,6 +77,7 @@ def _parallel_gate_op(gate, qubits): 'CZPowGate': cirq.CZPowGate, 'DensePauliString': cirq.DensePauliString, 'DepolarizingChannel': cirq.DepolarizingChannel, + 'DeviceMetaData': cirq.DeviceMetaData, 'Duration': cirq.Duration, 'FrozenCircuit': cirq.FrozenCircuit, 'FSimGate': cirq.FSimGate, diff --git a/cirq-core/cirq/protocols/json_serialization_test.py b/cirq-core/cirq/protocols/json_serialization_test.py index 51244fd22c1..c69a0a017d0 100644 --- a/cirq-core/cirq/protocols/json_serialization_test.py +++ b/cirq-core/cirq/protocols/json_serialization_test.py @@ -24,6 +24,7 @@ from typing import ClassVar, Dict, List, Optional, Tuple, Type from unittest import mock +import networkx as nx import numpy as np import pandas as pd import pytest @@ -726,13 +727,7 @@ def _eval_repr_data_file(path: pathlib.Path, deprecation_deadline: Optional[str] if deprecation is not None and deprecation.old_name in content: ctx_managers.append(deprecation.deprecation_assertion) - imports = { - 'cirq': cirq, - 'pd': pd, - 'sympy': sympy, - 'np': np, - 'datetime': datetime, - } + imports = {'cirq': cirq, 'pd': pd, 'sympy': sympy, 'np': np, 'datetime': datetime, 'nx': nx} for m in TESTED_MODULES.keys(): try: diff --git a/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json b/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json new file mode 100644 index 00000000000..833daccd540 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json @@ -0,0 +1,54 @@ +{ + "cirq_type": "DeviceMetaData", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + }, + { + "cirq_type": "LineQubit", + "x": 2 + }, + { + "cirq_type": "LineQubit", + "x": 3 + } + ], + "nx_graph": { + "directed": false, + "multigraph": false, + "graph": {}, + "nodes": [ + { + "id": 0 + }, + { + "id": 1 + }, + { + "id": 2 + }, + { + "id": 3 + } + ], + "links": [ + { + "source": 0, + "target": 1 + }, + { + "source": 0, + "target": 2 + }, + { + "source": 0, + "target": 3 + } + ] + } +} diff --git a/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.repr b/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.repr new file mode 100644 index 00000000000..8998306cec0 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.repr @@ -0,0 +1 @@ +cirq.DeviceMetaData(cirq.LineQubit.range(4), nx.star_graph(3)) \ No newline at end of file From 64ddad96a035ffd9b4f4e0e7c93b900c62fa3fdd Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Wed, 12 Jan 2022 18:55:12 -0800 Subject: [PATCH 2/4] added obj_kwargs to json serialization. --- cirq-core/cirq/devices/device.py | 14 ++++++++++---- cirq-core/cirq/devices/device_test.py | 4 +++- .../protocols/json_test_data/DeviceMetaData.json | 3 ++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cirq-core/cirq/devices/device.py b/cirq-core/cirq/devices/device.py index eca9b5d981e..07070af045a 100644 --- a/cirq-core/cirq/devices/device.py +++ b/cirq-core/cirq/devices/device.py @@ -210,7 +210,9 @@ def _value_equality_values_(self): if self._qubits_set is not None: qubit_equality = sorted(list(self._qubits_set)) - return qubit_equality, graph_equality + kwarg_equality = [(k, v) for k, v in vars(self).items() if k in self._kwargs_names] + kwarg_equality = sorted(kwarg_equality) + return qubit_equality, graph_equality, kwarg_equality def _json_dict_(self): graph_payload = '' @@ -220,16 +222,19 @@ def _json_dict_(self): qubits_payload = '' if self._qubits_set is not None: qubits_payload = sorted(list(self._qubits_set)) - return {'qubits': qubits_payload, 'nx_graph': graph_payload} + + kwargs_payload = {k: v for k, v in vars(self).items() if k in self._kwargs_names} + + return {'qubits': qubits_payload, 'nx_graph': graph_payload, 'obj_kwargs': kwargs_payload} @classmethod - def _from_json_dict_(cls, qubits, nx_graph, **kwargs): + def _from_json_dict_(cls, qubits, nx_graph, obj_kwargs, **kwargs): if qubits == '': qubits = None graph_obj = None if nx_graph != '': graph_obj = nx.readwrite.json_graph.node_link_graph(nx_graph) - return cls(qubits, graph_obj, **kwargs) + return cls(qubits, graph_obj, **obj_kwargs) def __init__( self, @@ -252,4 +257,5 @@ def __init__( self._qubits_set = frozenset(self._qubits_set) self._nx_graph = nx_graph + self._kwargs_names = kwargs.keys() self.__dict__.update(kwargs) diff --git a/cirq-core/cirq/devices/device_test.py b/cirq-core/cirq/devices/device_test.py index bfc99bb0c59..1a24c46e835 100644 --- a/cirq-core/cirq/devices/device_test.py +++ b/cirq-core/cirq/devices/device_test.py @@ -105,4 +105,6 @@ def test_metadata_json_load_logic(): other_arg = 5 metadata = cirq.DeviceMetaData(qubits, graph, other_arg=other_arg) str_rep = cirq.to_json(metadata) - assert metadata == cirq.read_json(json_text=str_rep) + output = cirq.read_json(json_text=str_rep) + assert output.other_arg == 5 + assert metadata == output diff --git a/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json b/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json index 833daccd540..f4d9c966578 100644 --- a/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json +++ b/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json @@ -50,5 +50,6 @@ "target": 3 } ] - } + }, + "obj_kwargs": {} } From 18b5fa73b173cdab414fe43cb350beb6b0e1f9e8 Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Thu, 13 Jan 2022 13:28:49 -0800 Subject: [PATCH 3/4] Orion feedback. --- cirq-core/cirq/__init__.py | 2 +- cirq-core/cirq/devices/__init__.py | 2 +- cirq-core/cirq/devices/device.py | 64 +++++++++---------- cirq-core/cirq/devices/device_test.py | 13 ++-- cirq-core/cirq/json_resolver_cache.py | 2 +- .../json_test_data/DeviceMetaData.repr | 1 - ...eviceMetaData.json => DeviceMetadata.json} | 5 +- .../json_test_data/DeviceMetadata.repr | 1 + 8 files changed, 39 insertions(+), 51 deletions(-) delete mode 100644 cirq-core/cirq/protocols/json_test_data/DeviceMetaData.repr rename cirq-core/cirq/protocols/json_test_data/{DeviceMetaData.json => DeviceMetadata.json} (92%) create mode 100644 cirq-core/cirq/protocols/json_test_data/DeviceMetadata.repr diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index b0f2d060f45..5afab01a07d 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -81,7 +81,7 @@ from cirq.devices import ( ConstantQubitNoiseModel, Device, - DeviceMetaData, + DeviceMetadata, GridQid, GridQubit, LineQid, diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index 3e9c08cb347..18a7100a035 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -15,7 +15,7 @@ """Types for devices, device-specific qubits, and noise models.""" from cirq.devices.device import ( Device, - DeviceMetaData, + DeviceMetadata, SymmetricalQidPair, ) diff --git a/cirq-core/cirq/devices/device.py b/cirq-core/cirq/devices/device.py index 07070af045a..7c4ea8f22e4 100644 --- a/cirq-core/cirq/devices/device.py +++ b/cirq-core/cirq/devices/device.py @@ -182,19 +182,41 @@ def __contains__(self, item: 'cirq.Qid') -> bool: @value.value_equality -class DeviceMetaData: +class DeviceMetadata: """Parent type for all device specific metadata classes.""" + def __init__( + self, + qubits: Optional[Iterable['cirq.Qid']] = None, + nx_graph: Optional['nx.graph'] = None, + ): + """Construct a DeviceMetadata object. + + Args: + qubits: Optional iterable of `cirq.Qid`s that exist on the device. + nx_graph: Optional `nx.Graph` describing qubit connectivity + on a device. Nodes represent qubits, directed edges indicate + directional coupling, undirected edges indicate bi-directional + coupling. + """ + if qubits is not None: + qubits = frozenset(qubits) + self._qubits_set: Optional[FrozenSet['cirq.Qid']] = cast( + Optional[FrozenSet['cirq.Qid']], qubits + ) + + self._nx_graph = nx_graph + def qubit_set(self) -> Optional[FrozenSet['cirq.Qid']]: """Returns a set of qubits on the device, if possible. Returns: Frozenset of qubits on device if specified, otherwise None. """ - return cast(Optional[FrozenSet['cirq.Qid']], self._qubits_set) + return self._qubits_set def nx_graph(self) -> Optional['nx.Graph']: - """Returns a nx.Graph where nodes are qubits are couple-able qubits. + """Returns a nx.Graph where nodes are qubits and edges are couple-able qubits. Returns: `nx.Graph` of device connectivity if specified, otherwise None. @@ -210,9 +232,7 @@ def _value_equality_values_(self): if self._qubits_set is not None: qubit_equality = sorted(list(self._qubits_set)) - kwarg_equality = [(k, v) for k, v in vars(self).items() if k in self._kwargs_names] - kwarg_equality = sorted(kwarg_equality) - return qubit_equality, graph_equality, kwarg_equality + return qubit_equality, graph_equality def _json_dict_(self): graph_payload = '' @@ -223,39 +243,13 @@ def _json_dict_(self): if self._qubits_set is not None: qubits_payload = sorted(list(self._qubits_set)) - kwargs_payload = {k: v for k, v in vars(self).items() if k in self._kwargs_names} - - return {'qubits': qubits_payload, 'nx_graph': graph_payload, 'obj_kwargs': kwargs_payload} + return {'qubits': qubits_payload, 'nx_graph': graph_payload} @classmethod - def _from_json_dict_(cls, qubits, nx_graph, obj_kwargs, **kwargs): + def _from_json_dict_(cls, qubits, nx_graph, **kwargs): if qubits == '': qubits = None graph_obj = None if nx_graph != '': graph_obj = nx.readwrite.json_graph.node_link_graph(nx_graph) - return cls(qubits, graph_obj, **obj_kwargs) - - def __init__( - self, - qubits: Optional[Iterable['cirq.Qid']] = None, - nx_graph: Optional['nx.graph'] = None, - **kwargs, - ): - """Construct a DeviceMetaData object. - - Args: - qubits: Optional iterable of `cirq.Qid`s that exist on the device. - nx_graph: Optional `nx.Graph` describing qubit connectivity - on a device. Nodes represent qubits, directed edges indicate - directional coupling, undirected edges indicate bi-directional - coupling. - **kwargs: Additional miscellanous properties to attach to metadata. - """ - self._qubits_set = qubits - if self._qubits_set is not None: - self._qubits_set = frozenset(self._qubits_set) - - self._nx_graph = nx_graph - self._kwargs_names = kwargs.keys() - self.__dict__.update(kwargs) + return cls(qubits, graph_obj) diff --git a/cirq-core/cirq/devices/device_test.py b/cirq-core/cirq/devices/device_test.py index 1a24c46e835..502c357925e 100644 --- a/cirq-core/cirq/devices/device_test.py +++ b/cirq-core/cirq/devices/device_test.py @@ -81,30 +81,25 @@ def test_qid_pair(): def test_metadata(): qubits = cirq.LineQubit.range(4) graph = nx.star_graph(3) - metadata = cirq.DeviceMetaData(qubits, graph) + metadata = cirq.DeviceMetadata(qubits, graph) assert metadata.qubit_set() == frozenset(qubits) assert metadata.nx_graph() == graph - metadata = cirq.DeviceMetaData() + metadata = cirq.DeviceMetadata() assert metadata.qubit_set() is None assert metadata.nx_graph() is None - metadata = cirq.DeviceMetaData(None, None, additional_property=5) - assert metadata.additional_property == 5 - def test_metadata_json_load_logic(): qubits = cirq.LineQubit.range(4) graph = nx.star_graph(3) - metadata = cirq.DeviceMetaData(qubits, graph) + metadata = cirq.DeviceMetadata(qubits, graph) str_rep = cirq.to_json(metadata) assert metadata == cirq.read_json(json_text=str_rep) qubits = None graph = None - other_arg = 5 - metadata = cirq.DeviceMetaData(qubits, graph, other_arg=other_arg) + metadata = cirq.DeviceMetadata(qubits, graph) str_rep = cirq.to_json(metadata) output = cirq.read_json(json_text=str_rep) - assert output.other_arg == 5 assert metadata == output diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index d6c27170937..2e7cc71797f 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -77,7 +77,7 @@ def _parallel_gate_op(gate, qubits): 'CZPowGate': cirq.CZPowGate, 'DensePauliString': cirq.DensePauliString, 'DepolarizingChannel': cirq.DepolarizingChannel, - 'DeviceMetaData': cirq.DeviceMetaData, + 'DeviceMetadata': cirq.DeviceMetadata, 'Duration': cirq.Duration, 'FrozenCircuit': cirq.FrozenCircuit, 'FSimGate': cirq.FSimGate, diff --git a/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.repr b/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.repr deleted file mode 100644 index 8998306cec0..00000000000 --- a/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.repr +++ /dev/null @@ -1 +0,0 @@ -cirq.DeviceMetaData(cirq.LineQubit.range(4), nx.star_graph(3)) \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json b/cirq-core/cirq/protocols/json_test_data/DeviceMetadata.json similarity index 92% rename from cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json rename to cirq-core/cirq/protocols/json_test_data/DeviceMetadata.json index f4d9c966578..f02295ae929 100644 --- a/cirq-core/cirq/protocols/json_test_data/DeviceMetaData.json +++ b/cirq-core/cirq/protocols/json_test_data/DeviceMetadata.json @@ -1,5 +1,5 @@ { - "cirq_type": "DeviceMetaData", + "cirq_type": "DeviceMetadata", "qubits": [ { "cirq_type": "LineQubit", @@ -50,6 +50,5 @@ "target": 3 } ] - }, - "obj_kwargs": {} + } } diff --git a/cirq-core/cirq/protocols/json_test_data/DeviceMetadata.repr b/cirq-core/cirq/protocols/json_test_data/DeviceMetadata.repr new file mode 100644 index 00000000000..866722f0248 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/DeviceMetadata.repr @@ -0,0 +1 @@ +cirq.DeviceMetadata(cirq.LineQubit.range(4), nx.star_graph(3)) \ No newline at end of file From 7a6c673b9521a7aa859450f9c0c04b2634c6af9a Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Thu, 13 Jan 2022 15:01:13 -0800 Subject: [PATCH 4/4] Orion feedback 2. --- cirq-core/cirq/devices/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/devices/device.py b/cirq-core/cirq/devices/device.py index 7c4ea8f22e4..701136d13d9 100644 --- a/cirq-core/cirq/devices/device.py +++ b/cirq-core/cirq/devices/device.py @@ -201,8 +201,8 @@ def __init__( """ if qubits is not None: qubits = frozenset(qubits) - self._qubits_set: Optional[FrozenSet['cirq.Qid']] = cast( - Optional[FrozenSet['cirq.Qid']], qubits + self._qubits_set: Optional[FrozenSet['cirq.Qid']] = ( + None if qubits is None else frozenset(qubits) ) self._nx_graph = nx_graph