diff --git a/cirq-core/cirq/devices/grid_device_metadata.py b/cirq-core/cirq/devices/grid_device_metadata.py index afb6bce1000..e0ccd2d55f6 100644 --- a/cirq-core/cirq/devices/grid_device_metadata.py +++ b/cirq-core/cirq/devices/grid_device_metadata.py @@ -39,12 +39,14 @@ def __init__( qubit_pairs: Iterable[Tuple['cirq.Qid', 'cirq.Qid']], gateset: 'cirq.Gateset', gate_durations: Optional[Dict['cirq.GateFamily', 'cirq.Duration']] = None, + all_qubits: Optional[Iterable['cirq.Qid']] = 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. + both coupling directions. Gate times (if provided) are expected + to be uniform across all qubits on the device. Args: qubit_pairs: Iterable of pairs of `cirq.Qid`s representing @@ -55,25 +57,51 @@ def __init__( instances mapping to `cirq.Duration` instances for gate timing metadata information. If provided, must match all entries in gateset. + all_qubits: Optional iterable specifying all qubits + found on the device. If None, all_qubits will + be inferred from the entries in qubit_pairs. Raises: ValueError: if the union of GateFamily keys in gate_durations is not identical to set of gate families in gateset. + ValueError: If qubit_pairs contains a self loop. + ValueError: if all_qubits is provided and is not a superset + of all the qubits found in qubit_pairs. """ - qubit_pairs = list(qubit_pairs) - flat_pairs = [q for pair in qubit_pairs for q in pair] + sorted_pairs = sorted(list(qubit_pairs)) + for a, b in sorted_pairs: + if a == b: + raise ValueError(f"Self loop encountered in qubit {a}") + # Keep lexigraphically smaller tuples for undirected edges. - sorted_pairs = sorted(qubit_pairs) - pair_set = set() + edge_set = set() + node_set = set() for a, b in sorted_pairs: - if (b, a) not in pair_set: - pair_set.add((a, b)) + node_set.add(a) + node_set.add(b) + if (b, a) not in edge_set: + edge_set.add((a, b)) + + if all_qubits is None: + all_qubits = node_set + + all_qubits = frozenset(all_qubits) + for q in node_set: + if q not in all_qubits: + raise ValueError( + f"Qubit {q} found in node_set and not in" + " all_qubits. all_qubits must contain at least" + " all the qubits found in all_qubits." + ) connectivity = nx.Graph() - connectivity.add_edges_from(sorted(pair_set), directed=False) - super().__init__(flat_pairs, connectivity) - self._qubit_pairs = frozenset(pair_set) + connectivity.add_nodes_from(sorted(all_qubits)) + connectivity.add_edges_from(sorted(edge_set), directed=False) + super().__init__(all_qubits, connectivity) + + self._qubit_pairs = frozenset(edge_set) self._gateset = gateset + self._isolated_qubits = all_qubits.difference(node_set) if gate_durations is not None: working_gatefamilies = frozenset(gate_durations.keys()) @@ -93,6 +121,11 @@ 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 isolated_qubits(self) -> FrozenSet['cirq.Qid']: + """Returns the set of all isolated qubits on the device (if appliable).""" + return self._isolated_qubits + @property def gateset(self) -> 'cirq.Gateset': """Returns the `cirq.Gateset` of supported gates on this device.""" @@ -112,12 +145,14 @@ def _value_equality_values_(self): tuple(sorted(self._qubit_pairs)), self._gateset, tuple(duration_equality), + tuple(sorted(self.qubit_set)), ) def __repr__(self) -> str: return ( f'cirq.GridDeviceMetadata({repr(self._qubit_pairs)},' - f' {repr(self._gateset)}, {repr(self._gate_durations)})' + f' {repr(self._gateset)}, {repr(self._gate_durations)},' + f' {repr(self.qubit_set)})' ) def _json_dict_(self): @@ -129,8 +164,9 @@ def _json_dict_(self): 'qubit_pairs': sorted(list(self._qubit_pairs)), 'gateset': self._gateset, 'gate_durations': duration_payload, + 'all_qubits': sorted(list(self.qubit_set)), } @classmethod - def _from_json_dict_(cls, qubit_pairs, gateset, gate_durations, **kwargs): - return cls(qubit_pairs, gateset, dict(gate_durations)) + def _from_json_dict_(cls, qubit_pairs, gateset, gate_durations, all_qubits, **kwargs): + return cls(qubit_pairs, gateset, dict(gate_durations), all_qubits) diff --git a/cirq-core/cirq/devices/grid_device_metadata_test.py b/cirq-core/cirq/devices/grid_device_metadata_test.py index 08c810e1258..2f1bcd76193 100644 --- a/cirq-core/cirq/devices/grid_device_metadata_test.py +++ b/cirq-core/cirq/devices/grid_device_metadata_test.py @@ -21,10 +21,9 @@ 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)] - + isolated_qubits = [cirq.GridQubit(9, 9), cirq.GridQubit(10, 10)] gateset = cirq.Gateset(cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZ) - metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset) - + metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, all_qubits=qubits + isolated_qubits) expected_pairings = frozenset( { (cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)), @@ -36,14 +35,16 @@ def test_griddevice_metadata(): (cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)), } ) - assert metadata.qubit_set == frozenset(qubits) + assert metadata.qubit_set == frozenset(qubits + isolated_qubits) assert metadata.qubit_pairs == expected_pairings assert metadata.gateset == gateset expected_graph = nx.Graph() + expected_graph.add_nodes_from(sorted(list(qubits + isolated_qubits))) 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 + assert metadata.isolated_qubits == frozenset(isolated_qubits) def test_griddevice_metadata_bad_durations(): @@ -58,6 +59,24 @@ def test_griddevice_metadata_bad_durations(): cirq.GridDeviceMetadata([qubits], gateset, gate_durations=invalid_duration) +def test_griddevice_metadata_bad_isolated(): + 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)] + fewer_qubits = [cirq.GridQubit(0, 0)] + gateset = cirq.Gateset(cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZ) + with pytest.raises(ValueError, match='node_set'): + _ = cirq.GridDeviceMetadata(qubit_pairs, gateset, all_qubits=fewer_qubits) + + +def test_griddevice_self_loop(): + bad_pairs = [ + (cirq.GridQubit(0, 0), cirq.GridQubit(0, 0)), + (cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)), + ] + with pytest.raises(ValueError, match='Self loop'): + _ = cirq.GridDeviceMetadata(bad_pairs, cirq.Gateset(cirq.XPowGate)) + + 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)] @@ -67,7 +86,10 @@ def test_griddevice_json_load(): cirq.GateFamily(cirq.YPowGate): cirq.Duration(picos=2), cirq.GateFamily(cirq.ZPowGate): cirq.Duration(picos=3), } - metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration) + isolated_qubits = [cirq.GridQubit(9, 9), cirq.GridQubit(10, 10)] + metadata = cirq.GridDeviceMetadata( + qubit_pairs, gateset, gate_durations=duration, all_qubits=qubits + isolated_qubits + ) rep_str = cirq.to_json(metadata) assert metadata == cirq.read_json(json_text=rep_str) @@ -86,17 +108,22 @@ def test_griddevice_metadata_equality(): cirq.GateFamily(cirq.YPowGate): cirq.Duration(picos=13), cirq.GateFamily(cirq.ZPowGate): cirq.Duration(picos=12), } + isolated_qubits = [cirq.GridQubit(9, 9)] 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) + metadata6 = cirq.GridDeviceMetadata( + qubit_pairs, gateset, gate_durations=duration, all_qubits=qubits + isolated_qubits + ) eq = cirq.testing.EqualsTester() eq.add_equality_group(metadata) eq.add_equality_group(metadata2) eq.add_equality_group(metadata3) eq.add_equality_group(metadata4) + eq.add_equality_group(metadata6) assert metadata == metadata5 @@ -110,5 +137,8 @@ def test_repr(): cirq.GateFamily(cirq.YPowGate): cirq.Duration(picos=3), cirq.GateFamily(cirq.ZPowGate): cirq.Duration(picos=2), } - metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration) + isolated_qubits = [cirq.GridQubit(9, 9)] + metadata = cirq.GridDeviceMetadata( + qubit_pairs, gateset, gate_durations=duration, all_qubits=qubits + isolated_qubits + ) cirq.testing.assert_equivalent_repr(metadata) diff --git a/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.json b/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.json index 00ad3fdaea3..ec46060bf59 100644 --- a/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.json +++ b/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.json @@ -155,5 +155,47 @@ "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-core/cirq/protocols/json_test_data/GridDeviceMetadata.repr b/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.repr index 70c88bb01c4..51cc6a2cb1c 100644 --- a/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.repr +++ b/cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.repr @@ -1 +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), {cirq.GateFamily(cirq.ops.common_gates.XPowGate): cirq.Duration(nanos=1), cirq.GateFamily(cirq.ops.common_gates.YPowGate): cirq.Duration(picos=1), cirq.GateFamily(cirq.ops.common_gates.ZPowGate): cirq.Duration(picos=1)}) \ No newline at end of file +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), {cirq.GateFamily(cirq.ops.common_gates.XPowGate): cirq.Duration(nanos=1), cirq.GateFamily(cirq.ops.common_gates.YPowGate): cirq.Duration(picos=1), cirq.GateFamily(cirq.ops.common_gates.ZPowGate): cirq.Duration(picos=1)}, cirq.GridQubit.rect(2, 3) + [cirq.GridQubit(9, 9), cirq.GridQubit(10, 10)]) \ No newline at end of file