Skip to content

Commit 3517306

Browse files
Add isolated qubit functionality for gridmetadata. (#5001)
Adds isolated qubit property to GridDeviceMetadata. Also adds error checking for self loops. cc: @verult .
1 parent 6c2b376 commit 3517306

File tree

4 files changed

+129
-21
lines changed

4 files changed

+129
-21
lines changed

cirq-core/cirq/devices/grid_device_metadata.py

+49-13
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ def __init__(
3939
qubit_pairs: Iterable[Tuple['cirq.Qid', 'cirq.Qid']],
4040
gateset: 'cirq.Gateset',
4141
gate_durations: Optional[Dict['cirq.GateFamily', 'cirq.Duration']] = None,
42+
all_qubits: Optional[Iterable['cirq.Qid']] = None,
4243
):
4344
"""Create a GridDeviceMetadata object.
4445
4546
Create a GridDevice which has a well defined set of couplable
4647
qubit pairs that have the same two qubit gates available in
47-
both coupling directions.
48+
both coupling directions. Gate times (if provided) are expected
49+
to be uniform across all qubits on the device.
4850
4951
Args:
5052
qubit_pairs: Iterable of pairs of `cirq.Qid`s representing
@@ -55,25 +57,51 @@ def __init__(
5557
instances mapping to `cirq.Duration` instances for
5658
gate timing metadata information. If provided,
5759
must match all entries in gateset.
60+
all_qubits: Optional iterable specifying all qubits
61+
found on the device. If None, all_qubits will
62+
be inferred from the entries in qubit_pairs.
5863
5964
Raises:
6065
ValueError: if the union of GateFamily keys in gate_durations
6166
is not identical to set of gate families in gateset.
67+
ValueError: If qubit_pairs contains a self loop.
68+
ValueError: if all_qubits is provided and is not a superset
69+
of all the qubits found in qubit_pairs.
6270
"""
63-
qubit_pairs = list(qubit_pairs)
64-
flat_pairs = [q for pair in qubit_pairs for q in pair]
71+
sorted_pairs = sorted(list(qubit_pairs))
72+
for a, b in sorted_pairs:
73+
if a == b:
74+
raise ValueError(f"Self loop encountered in qubit {a}")
75+
6576
# Keep lexigraphically smaller tuples for undirected edges.
66-
sorted_pairs = sorted(qubit_pairs)
67-
pair_set = set()
77+
edge_set = set()
78+
node_set = set()
6879
for a, b in sorted_pairs:
69-
if (b, a) not in pair_set:
70-
pair_set.add((a, b))
80+
node_set.add(a)
81+
node_set.add(b)
82+
if (b, a) not in edge_set:
83+
edge_set.add((a, b))
84+
85+
if all_qubits is None:
86+
all_qubits = node_set
87+
88+
all_qubits = frozenset(all_qubits)
89+
for q in node_set:
90+
if q not in all_qubits:
91+
raise ValueError(
92+
f"Qubit {q} found in node_set and not in"
93+
" all_qubits. all_qubits must contain at least"
94+
" all the qubits found in all_qubits."
95+
)
7196

7297
connectivity = nx.Graph()
73-
connectivity.add_edges_from(sorted(pair_set), directed=False)
74-
super().__init__(flat_pairs, connectivity)
75-
self._qubit_pairs = frozenset(pair_set)
98+
connectivity.add_nodes_from(sorted(all_qubits))
99+
connectivity.add_edges_from(sorted(edge_set), directed=False)
100+
super().__init__(all_qubits, connectivity)
101+
102+
self._qubit_pairs = frozenset(edge_set)
76103
self._gateset = gateset
104+
self._isolated_qubits = all_qubits.difference(node_set)
77105

78106
if gate_durations is not None:
79107
working_gatefamilies = frozenset(gate_durations.keys())
@@ -93,6 +121,11 @@ def qubit_pairs(self) -> FrozenSet[Tuple['cirq.Qid', 'cirq.Qid']]:
93121
"""Returns the set of all couple-able qubits on the device."""
94122
return self._qubit_pairs
95123

124+
@property
125+
def isolated_qubits(self) -> FrozenSet['cirq.Qid']:
126+
"""Returns the set of all isolated qubits on the device (if appliable)."""
127+
return self._isolated_qubits
128+
96129
@property
97130
def gateset(self) -> 'cirq.Gateset':
98131
"""Returns the `cirq.Gateset` of supported gates on this device."""
@@ -112,12 +145,14 @@ def _value_equality_values_(self):
112145
tuple(sorted(self._qubit_pairs)),
113146
self._gateset,
114147
tuple(duration_equality),
148+
tuple(sorted(self.qubit_set)),
115149
)
116150

117151
def __repr__(self) -> str:
118152
return (
119153
f'cirq.GridDeviceMetadata({repr(self._qubit_pairs)},'
120-
f' {repr(self._gateset)}, {repr(self._gate_durations)})'
154+
f' {repr(self._gateset)}, {repr(self._gate_durations)},'
155+
f' {repr(self.qubit_set)})'
121156
)
122157

123158
def _json_dict_(self):
@@ -129,8 +164,9 @@ def _json_dict_(self):
129164
'qubit_pairs': sorted(list(self._qubit_pairs)),
130165
'gateset': self._gateset,
131166
'gate_durations': duration_payload,
167+
'all_qubits': sorted(list(self.qubit_set)),
132168
}
133169

134170
@classmethod
135-
def _from_json_dict_(cls, qubit_pairs, gateset, gate_durations, **kwargs):
136-
return cls(qubit_pairs, gateset, dict(gate_durations))
171+
def _from_json_dict_(cls, qubit_pairs, gateset, gate_durations, all_qubits, **kwargs):
172+
return cls(qubit_pairs, gateset, dict(gate_durations), all_qubits)

cirq-core/cirq/devices/grid_device_metadata_test.py

+36-6
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@
2121
def test_griddevice_metadata():
2222
qubits = cirq.GridQubit.rect(2, 3)
2323
qubit_pairs = [(a, b) for a in qubits for b in qubits if a != b and a.is_adjacent(b)]
24-
24+
isolated_qubits = [cirq.GridQubit(9, 9), cirq.GridQubit(10, 10)]
2525
gateset = cirq.Gateset(cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZ)
26-
metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset)
27-
26+
metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, all_qubits=qubits + isolated_qubits)
2827
expected_pairings = frozenset(
2928
{
3029
(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)),
@@ -36,14 +35,16 @@ def test_griddevice_metadata():
3635
(cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)),
3736
}
3837
)
39-
assert metadata.qubit_set == frozenset(qubits)
38+
assert metadata.qubit_set == frozenset(qubits + isolated_qubits)
4039
assert metadata.qubit_pairs == expected_pairings
4140
assert metadata.gateset == gateset
4241
expected_graph = nx.Graph()
42+
expected_graph.add_nodes_from(sorted(list(qubits + isolated_qubits)))
4343
expected_graph.add_edges_from(sorted(list(expected_pairings)), directed=False)
4444
assert metadata.nx_graph.edges() == expected_graph.edges()
4545
assert metadata.nx_graph.nodes() == expected_graph.nodes()
4646
assert metadata.gate_durations is None
47+
assert metadata.isolated_qubits == frozenset(isolated_qubits)
4748

4849

4950
def test_griddevice_metadata_bad_durations():
@@ -58,6 +59,24 @@ def test_griddevice_metadata_bad_durations():
5859
cirq.GridDeviceMetadata([qubits], gateset, gate_durations=invalid_duration)
5960

6061

62+
def test_griddevice_metadata_bad_isolated():
63+
qubits = cirq.GridQubit.rect(2, 3)
64+
qubit_pairs = [(a, b) for a in qubits for b in qubits if a != b and a.is_adjacent(b)]
65+
fewer_qubits = [cirq.GridQubit(0, 0)]
66+
gateset = cirq.Gateset(cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZ)
67+
with pytest.raises(ValueError, match='node_set'):
68+
_ = cirq.GridDeviceMetadata(qubit_pairs, gateset, all_qubits=fewer_qubits)
69+
70+
71+
def test_griddevice_self_loop():
72+
bad_pairs = [
73+
(cirq.GridQubit(0, 0), cirq.GridQubit(0, 0)),
74+
(cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)),
75+
]
76+
with pytest.raises(ValueError, match='Self loop'):
77+
_ = cirq.GridDeviceMetadata(bad_pairs, cirq.Gateset(cirq.XPowGate))
78+
79+
6180
def test_griddevice_json_load():
6281
qubits = cirq.GridQubit.rect(2, 3)
6382
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():
6786
cirq.GateFamily(cirq.YPowGate): cirq.Duration(picos=2),
6887
cirq.GateFamily(cirq.ZPowGate): cirq.Duration(picos=3),
6988
}
70-
metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration)
89+
isolated_qubits = [cirq.GridQubit(9, 9), cirq.GridQubit(10, 10)]
90+
metadata = cirq.GridDeviceMetadata(
91+
qubit_pairs, gateset, gate_durations=duration, all_qubits=qubits + isolated_qubits
92+
)
7193
rep_str = cirq.to_json(metadata)
7294
assert metadata == cirq.read_json(json_text=rep_str)
7395

@@ -86,17 +108,22 @@ def test_griddevice_metadata_equality():
86108
cirq.GateFamily(cirq.YPowGate): cirq.Duration(picos=13),
87109
cirq.GateFamily(cirq.ZPowGate): cirq.Duration(picos=12),
88110
}
111+
isolated_qubits = [cirq.GridQubit(9, 9)]
89112
metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration)
90113
metadata2 = cirq.GridDeviceMetadata(qubit_pairs[:2], gateset, gate_durations=duration)
91114
metadata3 = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=None)
92115
metadata4 = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration2)
93116
metadata5 = cirq.GridDeviceMetadata(reversed(qubit_pairs), gateset, gate_durations=duration)
117+
metadata6 = cirq.GridDeviceMetadata(
118+
qubit_pairs, gateset, gate_durations=duration, all_qubits=qubits + isolated_qubits
119+
)
94120

95121
eq = cirq.testing.EqualsTester()
96122
eq.add_equality_group(metadata)
97123
eq.add_equality_group(metadata2)
98124
eq.add_equality_group(metadata3)
99125
eq.add_equality_group(metadata4)
126+
eq.add_equality_group(metadata6)
100127

101128
assert metadata == metadata5
102129

@@ -110,5 +137,8 @@ def test_repr():
110137
cirq.GateFamily(cirq.YPowGate): cirq.Duration(picos=3),
111138
cirq.GateFamily(cirq.ZPowGate): cirq.Duration(picos=2),
112139
}
113-
metadata = cirq.GridDeviceMetadata(qubit_pairs, gateset, gate_durations=duration)
140+
isolated_qubits = [cirq.GridQubit(9, 9)]
141+
metadata = cirq.GridDeviceMetadata(
142+
qubit_pairs, gateset, gate_durations=duration, all_qubits=qubits + isolated_qubits
143+
)
114144
cirq.testing.assert_equivalent_repr(metadata)

cirq-core/cirq/protocols/json_test_data/GridDeviceMetadata.json

+43-1
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,47 @@
155155
"picos": 1
156156
}
157157
]
158+
],
159+
"all_qubits": [
160+
{
161+
"cirq_type": "GridQubit",
162+
"row": 0,
163+
"col": 0
164+
},
165+
{
166+
"cirq_type": "GridQubit",
167+
"row": 0,
168+
"col": 1
169+
},
170+
{
171+
"cirq_type": "GridQubit",
172+
"row": 0,
173+
"col": 2
174+
},
175+
{
176+
"cirq_type": "GridQubit",
177+
"row": 1,
178+
"col": 0
179+
},
180+
{
181+
"cirq_type": "GridQubit",
182+
"row": 1,
183+
"col": 1
184+
},
185+
{
186+
"cirq_type": "GridQubit",
187+
"row": 1,
188+
"col": 2
189+
},
190+
{
191+
"cirq_type": "GridQubit",
192+
"row": 9,
193+
"col": 9
194+
},
195+
{
196+
"cirq_type": "GridQubit",
197+
"row": 10,
198+
"col": 10
199+
}
158200
]
159-
}
201+
}
Original file line numberDiff line numberDiff line change
@@ -1 +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)})
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)}, cirq.GridQubit.rect(2, 3) + [cirq.GridQubit(9, 9), cirq.GridQubit(10, 10)])

0 commit comments

Comments
 (0)