cirq-core 1.7.0.dev20251015163856__py3-none-any.whl → 1.7.0.dev20251017194258__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cirq-core might be problematic. Click here for more details.
- cirq/_version.py +1 -1
- cirq/_version_test.py +1 -1
- cirq/transformers/_connected_component.py +230 -0
- cirq/transformers/_connected_component_test.py +200 -0
- cirq/transformers/transformer_primitives.py +269 -144
- cirq/transformers/transformer_primitives_test.py +184 -0
- {cirq_core-1.7.0.dev20251015163856.dist-info → cirq_core-1.7.0.dev20251017194258.dist-info}/METADATA +1 -1
- {cirq_core-1.7.0.dev20251015163856.dist-info → cirq_core-1.7.0.dev20251017194258.dist-info}/RECORD +11 -9
- {cirq_core-1.7.0.dev20251015163856.dist-info → cirq_core-1.7.0.dev20251017194258.dist-info}/WHEEL +0 -0
- {cirq_core-1.7.0.dev20251015163856.dist-info → cirq_core-1.7.0.dev20251017194258.dist-info}/licenses/LICENSE +0 -0
- {cirq_core-1.7.0.dev20251015163856.dist-info → cirq_core-1.7.0.dev20251017194258.dist-info}/top_level.txt +0 -0
cirq/_version.py
CHANGED
cirq/_version_test.py
CHANGED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Copyright 2025 The Cirq Developers
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Defines a connected component of operations, to be used in merge transformers."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Callable, cast, Sequence, TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from scipy.cluster.hierarchy import DisjointSet
|
|
22
|
+
|
|
23
|
+
from cirq import ops, protocols
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
import cirq
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Component:
|
|
30
|
+
"""Internal representation for a connected component of operations."""
|
|
31
|
+
|
|
32
|
+
# Circuit moment containing the component
|
|
33
|
+
moment_id: int
|
|
34
|
+
# Union of all op qubits in the component
|
|
35
|
+
qubits: frozenset[cirq.Qid]
|
|
36
|
+
# Union of all measurement keys in the component
|
|
37
|
+
mkeys: frozenset[cirq.MeasurementKey]
|
|
38
|
+
# Union of all control keys in the component
|
|
39
|
+
ckeys: frozenset[cirq.MeasurementKey]
|
|
40
|
+
# Initial operation in the component
|
|
41
|
+
op: cirq.Operation
|
|
42
|
+
|
|
43
|
+
# True if the component can be merged with other components
|
|
44
|
+
is_mergeable: bool
|
|
45
|
+
|
|
46
|
+
def __init__(self, op: cirq.Operation, moment_id: int, is_mergeable=True):
|
|
47
|
+
"""Initializes a singleton component."""
|
|
48
|
+
self.op = op
|
|
49
|
+
self.is_mergeable = is_mergeable
|
|
50
|
+
self.moment_id = moment_id
|
|
51
|
+
self.qubits = frozenset(op.qubits)
|
|
52
|
+
self.mkeys = protocols.measurement_key_objs(op)
|
|
53
|
+
self.ckeys = protocols.control_keys(op)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ComponentWithOps(Component):
|
|
57
|
+
"""Component that keeps track of operations."""
|
|
58
|
+
|
|
59
|
+
# List of all operations in the component
|
|
60
|
+
ops: list[cirq.Operation]
|
|
61
|
+
|
|
62
|
+
def __init__(self, op: cirq.Operation, moment_id: int, is_mergeable=True):
|
|
63
|
+
super().__init__(op, moment_id, is_mergeable)
|
|
64
|
+
self.ops = [op]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ComponentWithCircuitOp(Component):
|
|
68
|
+
"""Component that keeps track of operations as a CircuitOperation."""
|
|
69
|
+
|
|
70
|
+
# CircuitOperation containing all the operations in the component,
|
|
71
|
+
# or a single Operation if the component is a singleton
|
|
72
|
+
circuit_op: cirq.Operation
|
|
73
|
+
|
|
74
|
+
def __init__(self, op: cirq.Operation, moment_id: int, is_mergeable=True):
|
|
75
|
+
super().__init__(op, moment_id, is_mergeable)
|
|
76
|
+
self.circuit_op = op
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ComponentSet:
|
|
80
|
+
"""Represents a set of mergeable components of operations."""
|
|
81
|
+
|
|
82
|
+
_comp_type: type[Component]
|
|
83
|
+
|
|
84
|
+
_disjoint_set: DisjointSet
|
|
85
|
+
|
|
86
|
+
# Callable to decide if a component is mergeable
|
|
87
|
+
_is_mergeable: Callable[[cirq.Operation], bool]
|
|
88
|
+
|
|
89
|
+
# List of components in creation order
|
|
90
|
+
_components: list[Component]
|
|
91
|
+
|
|
92
|
+
def __init__(self, is_mergeable: Callable[[cirq.Operation], bool]):
|
|
93
|
+
self._is_mergeable = is_mergeable
|
|
94
|
+
self._disjoint_set = DisjointSet()
|
|
95
|
+
self._components = []
|
|
96
|
+
self._comp_type = Component
|
|
97
|
+
|
|
98
|
+
def new_component(self, op: cirq.Operation, moment_id: int, is_mergeable=True) -> Component:
|
|
99
|
+
"""Creates a new component and adds it to the set."""
|
|
100
|
+
c = self._comp_type(op, moment_id, is_mergeable and self._is_mergeable(op))
|
|
101
|
+
self._disjoint_set.add(c)
|
|
102
|
+
self._components.append(c)
|
|
103
|
+
return c
|
|
104
|
+
|
|
105
|
+
def components(self) -> list[Component]:
|
|
106
|
+
"""Returns the initial components in creation order."""
|
|
107
|
+
return self._components
|
|
108
|
+
|
|
109
|
+
def find(self, x: Component) -> Component:
|
|
110
|
+
"""Finds the representative for a merged component."""
|
|
111
|
+
return self._disjoint_set[x]
|
|
112
|
+
|
|
113
|
+
def merge(self, x: Component, y: Component, merge_left=True) -> Component | None:
|
|
114
|
+
"""Attempts to merge two components.
|
|
115
|
+
|
|
116
|
+
If merge_left is True, y is merged into x, and the representative will keep
|
|
117
|
+
y's moment. If merge_left is False, x is merged into y, and the representative
|
|
118
|
+
will keep y's moment.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
x: First component to merge.
|
|
122
|
+
y: Second component to merge.
|
|
123
|
+
merge_left: True to keep x's moment for the merged component, False to
|
|
124
|
+
keep y's moment for the merged component.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
None, if the components can't be merged.
|
|
128
|
+
Otherwise the new component representative.
|
|
129
|
+
"""
|
|
130
|
+
x = self._disjoint_set[x]
|
|
131
|
+
y = self._disjoint_set[y]
|
|
132
|
+
|
|
133
|
+
if not x.is_mergeable or not y.is_mergeable:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
if not self._disjoint_set.merge(x, y):
|
|
137
|
+
return x
|
|
138
|
+
|
|
139
|
+
root = self._disjoint_set[x]
|
|
140
|
+
root.moment_id = x.moment_id if merge_left else y.moment_id
|
|
141
|
+
root.qubits = x.qubits.union(y.qubits)
|
|
142
|
+
root.mkeys = x.mkeys.union(y.mkeys)
|
|
143
|
+
root.ckeys = x.ckeys.union(y.ckeys)
|
|
144
|
+
|
|
145
|
+
return root
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ComponentWithOpsSet(ComponentSet):
|
|
149
|
+
"""Represents a set of mergeable components, where each component tracks operations."""
|
|
150
|
+
|
|
151
|
+
# Callable that returns if two components can be merged based on their operations
|
|
152
|
+
_can_merge: Callable[[Sequence[cirq.Operation], Sequence[cirq.Operation]], bool]
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
is_mergeable: Callable[[cirq.Operation], bool],
|
|
157
|
+
can_merge: Callable[[Sequence[cirq.Operation], Sequence[cirq.Operation]], bool],
|
|
158
|
+
):
|
|
159
|
+
super().__init__(is_mergeable)
|
|
160
|
+
self._can_merge = can_merge
|
|
161
|
+
self._comp_type = ComponentWithOps
|
|
162
|
+
|
|
163
|
+
def merge(self, x: Component, y: Component, merge_left=True) -> Component | None:
|
|
164
|
+
"""Attempts to merge two components.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
None if can_merge is False or the merge doesn't succeed, otherwise the
|
|
168
|
+
new representative. The representative will have ops = x.ops + y.ops.
|
|
169
|
+
"""
|
|
170
|
+
x = cast(ComponentWithOps, self._disjoint_set[x])
|
|
171
|
+
y = cast(ComponentWithOps, self._disjoint_set[y])
|
|
172
|
+
|
|
173
|
+
if x is y:
|
|
174
|
+
return x
|
|
175
|
+
|
|
176
|
+
if not x.is_mergeable or not y.is_mergeable or not self._can_merge(x.ops, y.ops):
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
root = cast(ComponentWithOps, super().merge(x, y, merge_left))
|
|
180
|
+
root.ops = x.ops + y.ops
|
|
181
|
+
# Clear the ops list in the non-representative component to avoid duplication
|
|
182
|
+
other = y if x is root else x
|
|
183
|
+
other.ops = []
|
|
184
|
+
return root
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class ComponentWithCircuitOpSet(ComponentSet):
|
|
188
|
+
"""Represents a set of mergeable components, with operations as a CircuitOperation."""
|
|
189
|
+
|
|
190
|
+
# Callable that merges CircuitOperations from two components
|
|
191
|
+
_merge_func: Callable[[ops.Operation, ops.Operation], ops.Operation | None]
|
|
192
|
+
|
|
193
|
+
def __init__(
|
|
194
|
+
self,
|
|
195
|
+
is_mergeable: Callable[[cirq.Operation], bool],
|
|
196
|
+
merge_func: Callable[[ops.Operation, ops.Operation], ops.Operation | None],
|
|
197
|
+
):
|
|
198
|
+
super().__init__(is_mergeable)
|
|
199
|
+
self._merge_func = merge_func
|
|
200
|
+
self._comp_type = ComponentWithCircuitOp
|
|
201
|
+
|
|
202
|
+
def merge(self, x: Component, y: Component, merge_left=True) -> Component | None:
|
|
203
|
+
"""Attempts to merge two components.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
None if merge_func returns None or the merge doesn't succeed,
|
|
207
|
+
otherwise the new representative.
|
|
208
|
+
"""
|
|
209
|
+
x = cast(ComponentWithCircuitOp, self._disjoint_set[x])
|
|
210
|
+
y = cast(ComponentWithCircuitOp, self._disjoint_set[y])
|
|
211
|
+
|
|
212
|
+
if x is y:
|
|
213
|
+
return x
|
|
214
|
+
|
|
215
|
+
if not x.is_mergeable or not y.is_mergeable:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
new_op = self._merge_func(x.circuit_op, y.circuit_op)
|
|
219
|
+
if not new_op:
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
root = cast(ComponentWithCircuitOp, super().merge(x, y, merge_left))
|
|
223
|
+
|
|
224
|
+
root.circuit_op = new_op
|
|
225
|
+
# The merge_func can be arbitrary, so we need to recompute the component properties
|
|
226
|
+
root.qubits = frozenset(new_op.qubits)
|
|
227
|
+
root.mkeys = protocols.measurement_key_objs(new_op)
|
|
228
|
+
root.ckeys = protocols.control_keys(new_op)
|
|
229
|
+
|
|
230
|
+
return root
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Copyright 2025 The Cirq Developers
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
import cirq
|
|
20
|
+
from cirq.transformers._connected_component import (
|
|
21
|
+
ComponentSet,
|
|
22
|
+
ComponentWithCircuitOpSet,
|
|
23
|
+
ComponentWithOpsSet,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _always_mergeable(_: cirq.Operation) -> bool:
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _never_mergeable(_: cirq.Operation) -> bool:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _always_can_merge(_ops1: list[cirq.Operation], _ops2: list[cirq.Operation]) -> bool:
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _never_can_merge(_ops1: list[cirq.Operation], _ops2: list[cirq.Operation]) -> bool:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _merge_as_first_operation(op1: cirq.Operation, _op2: cirq.Operation) -> cirq.Operation:
|
|
44
|
+
return op1
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _merge_never_successful(op1: cirq.Operation, _op2: cirq.Operation) -> None:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_find_returns_itself_for_singleton():
|
|
52
|
+
cset = ComponentSet(_always_mergeable)
|
|
53
|
+
|
|
54
|
+
q = cirq.NamedQubit('x')
|
|
55
|
+
c = cset.new_component(op=cirq.X(q), moment_id=0)
|
|
56
|
+
assert cset.find(c) is c
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_merge_components():
|
|
60
|
+
cset = ComponentSet(_always_mergeable)
|
|
61
|
+
|
|
62
|
+
q = cirq.NamedQubit('x')
|
|
63
|
+
c = [cset.new_component(op=cirq.X(q), moment_id=i) for i in range(5)]
|
|
64
|
+
cset.merge(c[1], c[0])
|
|
65
|
+
cset.merge(c[2], c[1])
|
|
66
|
+
cset.merge(c[4], c[3])
|
|
67
|
+
cset.merge(c[3], c[0])
|
|
68
|
+
|
|
69
|
+
for i in range(5):
|
|
70
|
+
assert cset.find(c[i]) is cset.find(c[0])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_merge_same_component():
|
|
74
|
+
cset = ComponentSet(_always_mergeable)
|
|
75
|
+
|
|
76
|
+
q = cirq.NamedQubit('x')
|
|
77
|
+
c = [cset.new_component(op=cirq.X(q), moment_id=i) for i in range(3)]
|
|
78
|
+
cset.merge(c[1], c[0])
|
|
79
|
+
cset.merge(c[2], c[1])
|
|
80
|
+
|
|
81
|
+
root = cset.find(c[0])
|
|
82
|
+
|
|
83
|
+
assert cset.merge(c[0], c[2]) is root
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_merge_returns_None_if_one_component_is_not_mergeable():
|
|
87
|
+
cset = ComponentSet(_always_mergeable)
|
|
88
|
+
|
|
89
|
+
q = cirq.NamedQubit('x')
|
|
90
|
+
c0 = cset.new_component(op=cirq.X(q), moment_id=0, is_mergeable=True)
|
|
91
|
+
c1 = cset.new_component(op=cirq.X(q), moment_id=1, is_mergeable=False)
|
|
92
|
+
assert cset.merge(c0, c1) is None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_cset_merge_returns_None_if_is_mergeable_is_false():
|
|
96
|
+
q = cirq.NamedQubit('x')
|
|
97
|
+
cset = ComponentSet(is_mergeable=_never_mergeable)
|
|
98
|
+
|
|
99
|
+
c0 = cset.new_component(op=cirq.X(q), moment_id=0, is_mergeable=True)
|
|
100
|
+
c1 = cset.new_component(op=cirq.X(q), moment_id=1, is_mergeable=True)
|
|
101
|
+
assert cset.merge(c0, c1) is None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.parametrize("merge_left,expected_moment_id", [(True, 0), (False, 1)])
|
|
105
|
+
def test_merge_qubits_with_merge_left(merge_left: bool, expected_moment_id: int) -> None:
|
|
106
|
+
cset = ComponentSet(_always_mergeable)
|
|
107
|
+
|
|
108
|
+
q0 = cirq.NamedQubit('x')
|
|
109
|
+
q1 = cirq.NamedQubit('y')
|
|
110
|
+
c0 = cset.new_component(op=cirq.X(q0), moment_id=0)
|
|
111
|
+
c1 = cset.new_component(op=cirq.X(q1), moment_id=1)
|
|
112
|
+
c2 = cset.new_component(op=cirq.X(q1), moment_id=2)
|
|
113
|
+
cset.merge(c1, c2)
|
|
114
|
+
cset.merge(c0, c1, merge_left=merge_left)
|
|
115
|
+
assert cset.find(c1).qubits == frozenset([q0, q1])
|
|
116
|
+
assert cset.find(c1).moment_id == expected_moment_id
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_component_with_ops_merge():
|
|
120
|
+
cset = ComponentWithOpsSet(_always_mergeable, _always_can_merge)
|
|
121
|
+
|
|
122
|
+
q = cirq.LineQubit.range(3)
|
|
123
|
+
ops = [cirq.X(q[i]) for i in range(3)]
|
|
124
|
+
c = [cset.new_component(op=ops[i], moment_id=i) for i in range(3)]
|
|
125
|
+
cset.merge(c[0], c[1])
|
|
126
|
+
cset.merge(c[1], c[2])
|
|
127
|
+
assert cset.find(c[0]).ops == ops
|
|
128
|
+
# check merge of indirectly merged components does not make a difference
|
|
129
|
+
assert cset.merge(c[0], c[2]).ops == ops
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_component_with_ops_merge_when_merge_fails():
|
|
133
|
+
cset = ComponentWithOpsSet(_always_mergeable, _never_can_merge)
|
|
134
|
+
|
|
135
|
+
q = cirq.LineQubit.range(3)
|
|
136
|
+
ops = [cirq.X(q[i]) for i in range(3)]
|
|
137
|
+
c = [cset.new_component(op=ops[i], moment_id=i) for i in range(3)]
|
|
138
|
+
|
|
139
|
+
cset.merge(c[0], c[1])
|
|
140
|
+
cset.merge(c[1], c[2])
|
|
141
|
+
# No merge happened
|
|
142
|
+
for i in range(3):
|
|
143
|
+
assert cset.find(c[i]) is c[i]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_component_with_ops_merge_when_is_mergeable_is_false():
|
|
147
|
+
cset = ComponentWithOpsSet(_never_mergeable, _always_can_merge)
|
|
148
|
+
|
|
149
|
+
q = cirq.LineQubit.range(3)
|
|
150
|
+
ops = [cirq.X(q[i]) for i in range(3)]
|
|
151
|
+
c = [cset.new_component(op=ops[i], moment_id=i) for i in range(3)]
|
|
152
|
+
|
|
153
|
+
cset.merge(c[0], c[1])
|
|
154
|
+
cset.merge(c[1], c[2])
|
|
155
|
+
# No merge happened
|
|
156
|
+
for i in range(3):
|
|
157
|
+
assert cset.find(c[i]) is c[i]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_component_with_circuit_op_merge():
|
|
161
|
+
cset = ComponentWithCircuitOpSet(_always_mergeable, _merge_as_first_operation)
|
|
162
|
+
|
|
163
|
+
q = cirq.LineQubit.range(3)
|
|
164
|
+
ops = [cirq.X(q[i]) for i in range(3)]
|
|
165
|
+
c = [cset.new_component(op=ops[i], moment_id=i) for i in range(3)]
|
|
166
|
+
|
|
167
|
+
cset.merge(c[0], c[1])
|
|
168
|
+
cset.merge(c[1], c[2])
|
|
169
|
+
for i in range(3):
|
|
170
|
+
assert cset.find(c[i]).circuit_op == ops[0]
|
|
171
|
+
# check merge of indirectly merged components does not make a difference
|
|
172
|
+
assert cset.merge(c[0], c[2]) is cset.find(c[1])
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_component_with_circuit_op_merge_func_is_none():
|
|
176
|
+
cset = ComponentWithCircuitOpSet(_always_mergeable, _merge_never_successful)
|
|
177
|
+
|
|
178
|
+
q = cirq.LineQubit.range(3)
|
|
179
|
+
ops = [cirq.X(q[i]) for i in range(3)]
|
|
180
|
+
c = [cset.new_component(op=ops[i], moment_id=i) for i in range(3)]
|
|
181
|
+
|
|
182
|
+
cset.merge(c[0], c[1])
|
|
183
|
+
cset.merge(c[1], c[2])
|
|
184
|
+
# No merge happened
|
|
185
|
+
for i in range(3):
|
|
186
|
+
assert cset.find(c[i]) is c[i]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_component_with_circuit_op_merge_when_is_mergeable_is_false():
|
|
190
|
+
cset = ComponentWithCircuitOpSet(_never_mergeable, _merge_as_first_operation)
|
|
191
|
+
|
|
192
|
+
q = cirq.LineQubit.range(3)
|
|
193
|
+
ops = [cirq.X(q[i]) for i in range(3)]
|
|
194
|
+
c = [cset.new_component(op=ops[i], moment_id=i) for i in range(3)]
|
|
195
|
+
|
|
196
|
+
cset.merge(c[0], c[1])
|
|
197
|
+
cset.merge(c[1], c[2])
|
|
198
|
+
# No merge happened
|
|
199
|
+
for i in range(3):
|
|
200
|
+
assert cset.find(c[i]) is c[i]
|
|
@@ -17,12 +17,21 @@
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
import bisect
|
|
20
|
+
import copy
|
|
20
21
|
import dataclasses
|
|
22
|
+
import itertools
|
|
21
23
|
from collections import defaultdict
|
|
22
24
|
from typing import Callable, cast, Hashable, Sequence, TYPE_CHECKING
|
|
23
25
|
|
|
24
26
|
from cirq import circuits, ops, protocols
|
|
25
27
|
from cirq.circuits.circuit import CIRCUIT_TYPE
|
|
28
|
+
from cirq.transformers._connected_component import (
|
|
29
|
+
Component,
|
|
30
|
+
ComponentSet,
|
|
31
|
+
ComponentWithCircuitOp,
|
|
32
|
+
ComponentWithCircuitOpSet,
|
|
33
|
+
ComponentWithOpsSet,
|
|
34
|
+
)
|
|
26
35
|
|
|
27
36
|
if TYPE_CHECKING:
|
|
28
37
|
import cirq
|
|
@@ -49,6 +58,33 @@ def _create_target_circuit_type(ops: ops.OP_TREE, target_circuit: CIRCUIT_TYPE)
|
|
|
49
58
|
return cast(CIRCUIT_TYPE, circuits.Circuit(ops))
|
|
50
59
|
|
|
51
60
|
|
|
61
|
+
def _insort_last(indices: list[int], value: int) -> None:
|
|
62
|
+
"""Insert value to a sorted list of indices.
|
|
63
|
+
|
|
64
|
+
Optimized for the majority case when the value is the last in the list.
|
|
65
|
+
"""
|
|
66
|
+
if indices and value < indices[-1]:
|
|
67
|
+
bisect.insort(indices, value)
|
|
68
|
+
else:
|
|
69
|
+
indices.append(value)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _remove_last(indices: list[int], value: int) -> None:
|
|
73
|
+
"""Remove value from a sorted list of indices.
|
|
74
|
+
|
|
75
|
+
Optimized for the majority case when the value is the last in the list.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ValueError: If the value is not in the list.
|
|
79
|
+
"""
|
|
80
|
+
if indices and (
|
|
81
|
+
indices[pos := -1] == value or indices[pos := bisect.bisect_left(indices, value)] == value
|
|
82
|
+
):
|
|
83
|
+
indices.pop(pos)
|
|
84
|
+
else:
|
|
85
|
+
raise ValueError("The value is not in the list of indices") # pragma: no cover
|
|
86
|
+
|
|
87
|
+
|
|
52
88
|
def map_moments(
|
|
53
89
|
circuit: CIRCUIT_TYPE,
|
|
54
90
|
map_func: Callable[[circuits.Moment, int], circuits.Moment | Sequence[circuits.Moment]],
|
|
@@ -282,17 +318,23 @@ def map_operations_and_unroll(
|
|
|
282
318
|
|
|
283
319
|
@dataclasses.dataclass
|
|
284
320
|
class _MergedCircuit:
|
|
285
|
-
"""An optimized internal representation of a circuit, tailored for
|
|
321
|
+
"""An optimized internal representation of a circuit, tailored for merge operations.
|
|
322
|
+
|
|
323
|
+
Operations are represented as mergeable components.
|
|
324
|
+
Each component has a moment id, a set of qubits, a set of measurement keys, and a set of
|
|
325
|
+
control keys. The moment id is the index of the moment that contains the component.
|
|
286
326
|
|
|
287
327
|
Attributes:
|
|
288
|
-
qubit_indexes: Mapping from qubits to (sorted) list of
|
|
289
|
-
acting on the qubit.
|
|
290
|
-
mkey_indexes: Mapping from measurement keys to (sorted) list of
|
|
291
|
-
measurement operations with the same key.
|
|
292
|
-
ckey_indexes: Mapping from measurement keys to (sorted) list of
|
|
293
|
-
classically controlled operations controlled on the same key.
|
|
294
|
-
|
|
295
|
-
|
|
328
|
+
qubit_indexes: Mapping from qubits to (sorted) list of component moments containing
|
|
329
|
+
operations acting on the qubit.
|
|
330
|
+
mkey_indexes: Mapping from measurement keys to (sorted) list of component moments
|
|
331
|
+
containing measurement operations with the same key.
|
|
332
|
+
ckey_indexes: Mapping from measurement keys to (sorted) list of component moments
|
|
333
|
+
containing classically controlled operations controlled on the same key.
|
|
334
|
+
components_by_index: List of components indexed by moment id.
|
|
335
|
+
For a moment id, we use a dictionary to keep track of the
|
|
336
|
+
components in the moment. The dictionary instead of a set is used to preserve
|
|
337
|
+
insertion order and the dictionary's values are intentionally unused.
|
|
296
338
|
"""
|
|
297
339
|
|
|
298
340
|
qubit_indexes: dict[cirq.Qid, list[int]] = dataclasses.field(
|
|
@@ -304,54 +346,203 @@ class _MergedCircuit:
|
|
|
304
346
|
ckey_indexes: dict[cirq.MeasurementKey, list[int]] = dataclasses.field(
|
|
305
347
|
default_factory=lambda: defaultdict(lambda: [-1])
|
|
306
348
|
)
|
|
307
|
-
|
|
349
|
+
components_by_index: list[dict[Component, None]] = dataclasses.field(default_factory=list)
|
|
308
350
|
|
|
309
351
|
def append_empty_moment(self) -> None:
|
|
310
|
-
self.
|
|
311
|
-
|
|
312
|
-
def
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
for
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
for
|
|
332
|
-
self.
|
|
333
|
-
for
|
|
334
|
-
self.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
352
|
+
self.components_by_index.append({})
|
|
353
|
+
|
|
354
|
+
def add_component(self, c: Component) -> None:
|
|
355
|
+
"""Adds a new components to merged circuit."""
|
|
356
|
+
self.components_by_index[c.moment_id][c] = None
|
|
357
|
+
for q in c.qubits:
|
|
358
|
+
_insort_last(self.qubit_indexes[q], c.moment_id)
|
|
359
|
+
for mkey in c.mkeys:
|
|
360
|
+
_insort_last(self.mkey_indexes[mkey], c.moment_id)
|
|
361
|
+
for ckey in c.ckeys:
|
|
362
|
+
_insort_last(self.ckey_indexes[ckey], c.moment_id)
|
|
363
|
+
|
|
364
|
+
def remove_component(self, c: Component, c_data: Component) -> None:
|
|
365
|
+
"""Removes a component from the merged circuit.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
c: Reference to the component to be removed.
|
|
369
|
+
c_data: Copy of the data in c before any component merges involving c
|
|
370
|
+
(this is necessary as component merges alter the component data).
|
|
371
|
+
"""
|
|
372
|
+
self.components_by_index[c_data.moment_id].pop(c)
|
|
373
|
+
for q in c_data.qubits:
|
|
374
|
+
_remove_last(self.qubit_indexes[q], c_data.moment_id)
|
|
375
|
+
for mkey in c_data.mkeys:
|
|
376
|
+
_remove_last(self.mkey_indexes[mkey], c_data.moment_id)
|
|
377
|
+
for ckey in c_data.ckeys:
|
|
378
|
+
_remove_last(self.ckey_indexes[ckey], c_data.moment_id)
|
|
379
|
+
|
|
380
|
+
def get_mergeable_components(self, c: Component, c_qs: set[cirq.Qid]) -> list[Component]:
|
|
381
|
+
"""Finds all components that can be merged with c.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
c: Component to be merged with existing components.
|
|
385
|
+
c_qs: Subset of c.qubits used to decide which components are mergeable.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
List of mergeable components.
|
|
389
|
+
"""
|
|
390
|
+
# Find the index of previous moment which can be merged with `c`.
|
|
342
391
|
idx = max(
|
|
343
|
-
|
|
392
|
+
itertools.chain(
|
|
393
|
+
(self.qubit_indexes[q][-1] for q in c_qs),
|
|
394
|
+
(self.mkey_indexes[ckey][-1] for ckey in c.ckeys),
|
|
395
|
+
(self.ckey_indexes[mkey][-1] for mkey in c.mkeys),
|
|
396
|
+
),
|
|
397
|
+
default=-1,
|
|
344
398
|
)
|
|
345
|
-
# Return the set of overlapping
|
|
399
|
+
# Return the set of overlapping components in moment with index `idx`.
|
|
346
400
|
if idx == -1:
|
|
347
|
-
return
|
|
401
|
+
return []
|
|
402
|
+
|
|
403
|
+
return [c for c in self.components_by_index[idx] if not c_qs.isdisjoint(c.qubits)]
|
|
404
|
+
|
|
405
|
+
def get_cirq_circuit(self, cset: ComponentSet, merged_circuit_op_tag: str) -> cirq.Circuit:
|
|
406
|
+
"""Returns the merged circuit.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
cset: Disjoint set data structure containing the components.
|
|
410
|
+
merged_circuit_op_tag: Tag to use for CircuitOperations.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
The circuit with merged components as a CircuitOperation.
|
|
414
|
+
"""
|
|
415
|
+
component_ops: dict[Component, list[cirq.Operation]] = defaultdict(list)
|
|
416
|
+
|
|
417
|
+
# Traverse the components in creation order and collect operations
|
|
418
|
+
for c in cset.components():
|
|
419
|
+
root = cset.find(c)
|
|
420
|
+
component_ops[root].append(c.op)
|
|
421
|
+
|
|
422
|
+
moments = []
|
|
423
|
+
for m in self.components_by_index:
|
|
424
|
+
ops = []
|
|
425
|
+
for c in m.keys():
|
|
426
|
+
if isinstance(c, ComponentWithCircuitOp):
|
|
427
|
+
ops.append(c.circuit_op)
|
|
428
|
+
continue
|
|
429
|
+
if len(component_ops[c]) == 1:
|
|
430
|
+
ops.append(component_ops[c][0])
|
|
431
|
+
else:
|
|
432
|
+
ops.append(
|
|
433
|
+
circuits.CircuitOperation(
|
|
434
|
+
circuits.FrozenCircuit(component_ops[c])
|
|
435
|
+
).with_tags(merged_circuit_op_tag)
|
|
436
|
+
)
|
|
437
|
+
moments.append(circuits.Moment(ops))
|
|
438
|
+
return circuits.Circuit(moments)
|
|
348
439
|
|
|
349
|
-
return idx, [
|
|
350
|
-
left_op for left_op in self.ops_by_index[idx] if not op_qs.isdisjoint(left_op.qubits)
|
|
351
|
-
]
|
|
352
440
|
|
|
353
|
-
|
|
354
|
-
|
|
441
|
+
def _merge_operations_impl(
|
|
442
|
+
circuit: CIRCUIT_TYPE,
|
|
443
|
+
cset: ComponentSet,
|
|
444
|
+
*,
|
|
445
|
+
merged_circuit_op_tag: str = "Merged connected component",
|
|
446
|
+
tags_to_ignore: Sequence[Hashable] = (),
|
|
447
|
+
deep: bool = False,
|
|
448
|
+
) -> CIRCUIT_TYPE:
|
|
449
|
+
"""Merges operations in a circuit.
|
|
450
|
+
|
|
451
|
+
Two operations op1 and op2 are merge-able if
|
|
452
|
+
- There is no other operations between op1 and op2 in the circuit
|
|
453
|
+
- is_subset(op1.qubits, op2.qubits) or is_subset(op2.qubits, op1.qubits)
|
|
454
|
+
|
|
455
|
+
The method iterates on the input circuit moment-by-moment from left to right and attempts
|
|
456
|
+
to repeatedly merge each operation in the latest moment with all the corresponding merge-able
|
|
457
|
+
operations to its left.
|
|
458
|
+
|
|
459
|
+
Operations are wrapped in a component and then cset.merge() is called to merge two
|
|
460
|
+
components.
|
|
461
|
+
|
|
462
|
+
If op1 and op2 are merged, both op1 and op2 are deleted from the circuit and
|
|
463
|
+
the merged component is inserted at the index corresponding to the larger
|
|
464
|
+
of op1/op2. If both op1 and op2 act on the same number of qubits, the merged component is
|
|
465
|
+
inserted in the smaller moment index to minimize circuit depth.
|
|
466
|
+
|
|
467
|
+
At the end every component with more than one operation is replaced by a CircuitOperation.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
circuit: Input circuit to apply the transformations on. The input circuit is not mutated.
|
|
471
|
+
cset: Disjoint set data structure that is used to create and merge components.
|
|
472
|
+
merged_circuit_op_tag: Tag used for CircuitOperations created from merged components.
|
|
473
|
+
tags_to_ignore: Sequence of tags which should be ignored during the merge: operations with
|
|
474
|
+
these tags will not be merged.
|
|
475
|
+
deep: If true, the transformer primitive will be recursively applied to all circuits
|
|
476
|
+
wrapped inside circuit operations.
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
Copy of input circuit with merged operations.
|
|
481
|
+
"""
|
|
482
|
+
tags_to_ignore_set = set(tags_to_ignore)
|
|
483
|
+
|
|
484
|
+
merged_circuit = _MergedCircuit()
|
|
485
|
+
for moment_idx, current_moment in enumerate(circuit):
|
|
486
|
+
merged_circuit.append_empty_moment()
|
|
487
|
+
for op in sorted(current_moment.operations, key=lambda op: op.qubits):
|
|
488
|
+
if (
|
|
489
|
+
deep
|
|
490
|
+
and isinstance(op.untagged, circuits.CircuitOperation)
|
|
491
|
+
and tags_to_ignore_set.isdisjoint(op.tags)
|
|
492
|
+
):
|
|
493
|
+
op_untagged = op.untagged
|
|
494
|
+
merged_op = op_untagged.replace(
|
|
495
|
+
circuit=_merge_operations_impl(
|
|
496
|
+
op_untagged.circuit,
|
|
497
|
+
cset,
|
|
498
|
+
merged_circuit_op_tag=merged_circuit_op_tag,
|
|
499
|
+
tags_to_ignore=tags_to_ignore,
|
|
500
|
+
deep=True,
|
|
501
|
+
)
|
|
502
|
+
).with_tags(*op.tags)
|
|
503
|
+
c = cset.new_component(merged_op, moment_idx, is_mergeable=False)
|
|
504
|
+
merged_circuit.add_component(c)
|
|
505
|
+
continue
|
|
506
|
+
|
|
507
|
+
c = cset.new_component(
|
|
508
|
+
op, moment_idx, is_mergeable=tags_to_ignore_set.isdisjoint(op.tags)
|
|
509
|
+
)
|
|
510
|
+
if not c.is_mergeable:
|
|
511
|
+
merged_circuit.add_component(c)
|
|
512
|
+
continue
|
|
513
|
+
|
|
514
|
+
c_qs = set(c.qubits)
|
|
515
|
+
left_comp = merged_circuit.get_mergeable_components(c, c_qs)
|
|
516
|
+
if len(left_comp) == 1 and c_qs.issubset(left_comp[0].qubits):
|
|
517
|
+
# Make a shallow copy of the left component data before merge
|
|
518
|
+
left_c_data = copy.copy(left_comp[0])
|
|
519
|
+
# Case-1: Try to merge c with the larger component on the left.
|
|
520
|
+
new_comp = cset.merge(left_comp[0], c, merge_left=True)
|
|
521
|
+
if new_comp is not None:
|
|
522
|
+
merged_circuit.remove_component(left_comp[0], left_c_data)
|
|
523
|
+
merged_circuit.add_component(new_comp)
|
|
524
|
+
else:
|
|
525
|
+
merged_circuit.add_component(c)
|
|
526
|
+
continue
|
|
527
|
+
|
|
528
|
+
while left_comp and c_qs:
|
|
529
|
+
# Case-2: left_c will merge right into `c` whenever possible.
|
|
530
|
+
for left_c in left_comp:
|
|
531
|
+
is_merged = False
|
|
532
|
+
if c_qs.issuperset(left_c.qubits):
|
|
533
|
+
# Make a shallow copy of the left component data before merge
|
|
534
|
+
left_c_data = copy.copy(left_c)
|
|
535
|
+
# Try to merge left_c into c
|
|
536
|
+
new_comp = cset.merge(left_c, c, merge_left=False)
|
|
537
|
+
if new_comp is not None:
|
|
538
|
+
merged_circuit.remove_component(left_c, left_c_data)
|
|
539
|
+
c, is_merged = new_comp, True
|
|
540
|
+
if not is_merged:
|
|
541
|
+
c_qs -= left_c.qubits
|
|
542
|
+
left_comp = merged_circuit.get_mergeable_components(c, c_qs)
|
|
543
|
+
merged_circuit.add_component(c)
|
|
544
|
+
ret_circuit = merged_circuit.get_cirq_circuit(cset, merged_circuit_op_tag)
|
|
545
|
+
return _to_target_circuit_type(ret_circuit, circuit)
|
|
355
546
|
|
|
356
547
|
|
|
357
548
|
def merge_operations(
|
|
@@ -407,12 +598,8 @@ def merge_operations(
|
|
|
407
598
|
ValueError if the merged operation acts on new qubits outside the set of qubits
|
|
408
599
|
corresponding to the original operations to be merged.
|
|
409
600
|
"""
|
|
410
|
-
_circuit_op_tag = "_internal_tag_to_mark_circuit_ops_in_circuit"
|
|
411
|
-
tags_to_ignore_set = set(tags_to_ignore) | {_circuit_op_tag}
|
|
412
601
|
|
|
413
602
|
def apply_merge_func(op1: ops.Operation, op2: ops.Operation) -> ops.Operation | None:
|
|
414
|
-
if not all(tags_to_ignore_set.isdisjoint(op.tags) for op in [op1, op2]):
|
|
415
|
-
return None
|
|
416
603
|
new_op = merge_func(op1, op2)
|
|
417
604
|
qubit_set = frozenset(op1.qubits + op2.qubits)
|
|
418
605
|
if new_op is not None and not qubit_set.issuperset(new_op.qubits):
|
|
@@ -422,63 +609,15 @@ def merge_operations(
|
|
|
422
609
|
)
|
|
423
610
|
return new_op
|
|
424
611
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
merged_circuit.append_empty_moment()
|
|
428
|
-
for op in sorted(current_moment.operations, key=lambda op: op.qubits):
|
|
429
|
-
if (
|
|
430
|
-
deep
|
|
431
|
-
and isinstance(op.untagged, circuits.CircuitOperation)
|
|
432
|
-
and tags_to_ignore_set.isdisjoint(op.tags)
|
|
433
|
-
):
|
|
434
|
-
op_untagged = op.untagged
|
|
435
|
-
merged_circuit.add_op_to_moment(
|
|
436
|
-
moment_idx,
|
|
437
|
-
op_untagged.replace(
|
|
438
|
-
circuit=merge_operations(
|
|
439
|
-
op_untagged.circuit,
|
|
440
|
-
merge_func,
|
|
441
|
-
tags_to_ignore=tags_to_ignore,
|
|
442
|
-
deep=True,
|
|
443
|
-
)
|
|
444
|
-
).with_tags(*op.tags, _circuit_op_tag),
|
|
445
|
-
)
|
|
446
|
-
continue
|
|
447
|
-
|
|
448
|
-
op_qs = set(op.qubits)
|
|
449
|
-
left_idx, left_ops = merged_circuit.get_mergeable_ops(op, op_qs)
|
|
450
|
-
if len(left_ops) == 1 and op_qs.issubset(left_ops[0].qubits):
|
|
451
|
-
# Case-1: Try to merge op with the larger operation on the left.
|
|
452
|
-
new_op = apply_merge_func(left_ops[0], op)
|
|
453
|
-
if new_op is not None:
|
|
454
|
-
merged_circuit.remove_op_from_moment(left_idx, left_ops[0])
|
|
455
|
-
merged_circuit.add_op_to_moment(left_idx, new_op)
|
|
456
|
-
else:
|
|
457
|
-
merged_circuit.add_op_to_moment(moment_idx, op)
|
|
458
|
-
continue
|
|
612
|
+
def is_mergeable(_: cirq.Operation):
|
|
613
|
+
return True
|
|
459
614
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
new_op = apply_merge_func(left_op, op)
|
|
467
|
-
if new_op is not None:
|
|
468
|
-
merged_circuit.remove_op_from_moment(left_idx, left_op)
|
|
469
|
-
op, is_merged = new_op, True
|
|
470
|
-
if not is_merged:
|
|
471
|
-
op_qs -= frozenset(left_op.qubits)
|
|
472
|
-
left_idx, left_ops = merged_circuit.get_mergeable_ops(op, op_qs)
|
|
473
|
-
merged_circuit.add_op_to_moment(moment_idx, op)
|
|
474
|
-
ret_circuit = merged_circuit.get_cirq_circuit()
|
|
475
|
-
if deep:
|
|
476
|
-
ret_circuit = map_operations(
|
|
477
|
-
ret_circuit,
|
|
478
|
-
lambda o, _: o.untagged.with_tags(*(set(o.tags) - {_circuit_op_tag})),
|
|
479
|
-
deep=True,
|
|
480
|
-
)
|
|
481
|
-
return _to_target_circuit_type(ret_circuit, circuit)
|
|
615
|
+
return _merge_operations_impl(
|
|
616
|
+
circuit,
|
|
617
|
+
ComponentWithCircuitOpSet(is_mergeable, apply_merge_func),
|
|
618
|
+
tags_to_ignore=tags_to_ignore,
|
|
619
|
+
deep=deep,
|
|
620
|
+
)
|
|
482
621
|
|
|
483
622
|
|
|
484
623
|
def merge_operations_to_circuit_op(
|
|
@@ -491,10 +630,9 @@ def merge_operations_to_circuit_op(
|
|
|
491
630
|
) -> CIRCUIT_TYPE:
|
|
492
631
|
"""Merges connected components of operations and wraps each component into a circuit operation.
|
|
493
632
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
strategy.
|
|
633
|
+
Moment structure is preserved for operations that do not participate in merging.
|
|
634
|
+
For merged operations, the newly created circuit operations are constructed by inserting
|
|
635
|
+
operations using EARLIEST strategy.
|
|
498
636
|
If you need more control on moment structure of newly created circuit operations, consider
|
|
499
637
|
using `cirq.merge_operations` directly with a custom `merge_func`.
|
|
500
638
|
|
|
@@ -514,24 +652,16 @@ def merge_operations_to_circuit_op(
|
|
|
514
652
|
Copy of input circuit with valid connected components wrapped in tagged circuit operations.
|
|
515
653
|
"""
|
|
516
654
|
|
|
517
|
-
def
|
|
518
|
-
|
|
519
|
-
op_untagged = op.untagged
|
|
520
|
-
return (
|
|
521
|
-
[*op_untagged.circuit.all_operations()]
|
|
522
|
-
if isinstance(op_untagged, circuits.CircuitOperation)
|
|
523
|
-
and merged_circuit_op_tag in op.tags
|
|
524
|
-
else [op]
|
|
525
|
-
)
|
|
655
|
+
def is_mergeable(_: cirq.Operation):
|
|
656
|
+
return True
|
|
526
657
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return merge_operations(circuit, merge_func, tags_to_ignore=tags_to_ignore, deep=deep)
|
|
658
|
+
return _merge_operations_impl(
|
|
659
|
+
circuit,
|
|
660
|
+
ComponentWithOpsSet(is_mergeable, can_merge),
|
|
661
|
+
merged_circuit_op_tag=merged_circuit_op_tag,
|
|
662
|
+
tags_to_ignore=tags_to_ignore,
|
|
663
|
+
deep=deep,
|
|
664
|
+
)
|
|
535
665
|
|
|
536
666
|
|
|
537
667
|
def merge_k_qubit_unitaries_to_circuit_op(
|
|
@@ -544,10 +674,9 @@ def merge_k_qubit_unitaries_to_circuit_op(
|
|
|
544
674
|
) -> CIRCUIT_TYPE:
|
|
545
675
|
"""Merges connected components of operations, acting on <= k qubits, into circuit operations.
|
|
546
676
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
are constructed by inserting operations using EARLIEST strategy.
|
|
677
|
+
Moment structure is preserved for operations that do not participate in merging.
|
|
678
|
+
For merged operations, the newly created circuit operations are constructed by inserting
|
|
679
|
+
operations using EARLIEST strategy.
|
|
551
680
|
|
|
552
681
|
Args:
|
|
553
682
|
circuit: Input circuit to apply the transformations on. The input circuit is not mutated.
|
|
@@ -563,18 +692,14 @@ def merge_k_qubit_unitaries_to_circuit_op(
|
|
|
563
692
|
Copy of input circuit with valid connected components wrapped in tagged circuit operations.
|
|
564
693
|
"""
|
|
565
694
|
|
|
566
|
-
def
|
|
567
|
-
return
|
|
568
|
-
protocols.num_qubits(op) <= k and protocols.has_unitary(op)
|
|
569
|
-
for op_list in [ops1, ops2]
|
|
570
|
-
for op in op_list
|
|
571
|
-
)
|
|
695
|
+
def is_mergeable(op: cirq.Operation):
|
|
696
|
+
return protocols.num_qubits(op) <= k and protocols.has_unitary(op)
|
|
572
697
|
|
|
573
|
-
return
|
|
698
|
+
return _merge_operations_impl(
|
|
574
699
|
circuit,
|
|
575
|
-
|
|
576
|
-
tags_to_ignore=tags_to_ignore,
|
|
700
|
+
ComponentSet(is_mergeable),
|
|
577
701
|
merged_circuit_op_tag=merged_circuit_op_tag or f"Merged {k}q unitary connected component.",
|
|
702
|
+
tags_to_ignore=tags_to_ignore,
|
|
578
703
|
deep=deep,
|
|
579
704
|
)
|
|
580
705
|
|
|
@@ -877,3 +877,187 @@ def test_merge_operations_does_not_merge_measurements_behind_ccos():
|
|
|
877
877
|
cirq.testing.assert_same_circuits(
|
|
878
878
|
cirq.align_left(cirq.merge_operations(circuit, merge_func)), expected_circuit
|
|
879
879
|
)
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def test_merge_3q_unitaries_to_circuit_op_3q_gate_absorbs_overlapping_2q_gates():
|
|
883
|
+
q = cirq.LineQubit.range(3)
|
|
884
|
+
c_orig = cirq.Circuit(
|
|
885
|
+
cirq.Moment(
|
|
886
|
+
cirq.H(q[0]).with_tags("ignore"),
|
|
887
|
+
cirq.H(q[1]).with_tags("ignore"),
|
|
888
|
+
cirq.H(q[2]).with_tags("ignore"),
|
|
889
|
+
),
|
|
890
|
+
cirq.Moment(cirq.CNOT(q[0], q[2]), cirq.X(q[1]).with_tags("ignore")),
|
|
891
|
+
cirq.CNOT(q[0], q[1]),
|
|
892
|
+
cirq.CNOT(q[1], q[2]),
|
|
893
|
+
cirq.CCZ(*q),
|
|
894
|
+
strategy=cirq.InsertStrategy.NEW,
|
|
895
|
+
)
|
|
896
|
+
cirq.testing.assert_has_diagram(
|
|
897
|
+
c_orig,
|
|
898
|
+
'''
|
|
899
|
+
┌──────────┐
|
|
900
|
+
0: ───H[ignore]────@─────────────@───────@───
|
|
901
|
+
│ │ │
|
|
902
|
+
1: ───H[ignore]────┼X[ignore]────X───@───@───
|
|
903
|
+
│ │ │
|
|
904
|
+
2: ───H[ignore]────X─────────────────X───@───
|
|
905
|
+
└──────────┘
|
|
906
|
+
''',
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
c_new = cirq.merge_k_qubit_unitaries_to_circuit_op(
|
|
910
|
+
c_orig, k=3, merged_circuit_op_tag="merged", tags_to_ignore=["ignore"]
|
|
911
|
+
)
|
|
912
|
+
cirq.testing.assert_has_diagram(
|
|
913
|
+
cirq.drop_empty_moments(c_new),
|
|
914
|
+
'''
|
|
915
|
+
[ 0: ───@───@───────@─── ]
|
|
916
|
+
[ │ │ │ ]
|
|
917
|
+
0: ───H[ignore]───────────────[ 1: ───┼───X───@───@─── ]───────────
|
|
918
|
+
[ │ │ │ ]
|
|
919
|
+
[ 2: ───X───────X───@─── ][merged]
|
|
920
|
+
│
|
|
921
|
+
1: ───H[ignore]───X[ignore]───#2───────────────────────────────────
|
|
922
|
+
│
|
|
923
|
+
2: ───H[ignore]───────────────#3───────────────────────────────────
|
|
924
|
+
''',
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def test_merge_3q_unitaries_to_circuit_op_3q_gate_absorbs_disjoint_gates():
|
|
929
|
+
q = cirq.LineQubit.range(3)
|
|
930
|
+
c_orig = cirq.Circuit(
|
|
931
|
+
cirq.Moment(cirq.CNOT(q[0], q[1]), cirq.X(q[2])),
|
|
932
|
+
cirq.CCZ(*q),
|
|
933
|
+
strategy=cirq.InsertStrategy.NEW,
|
|
934
|
+
)
|
|
935
|
+
cirq.testing.assert_has_diagram(
|
|
936
|
+
c_orig,
|
|
937
|
+
'''
|
|
938
|
+
0: ───@───@───
|
|
939
|
+
│ │
|
|
940
|
+
1: ───X───@───
|
|
941
|
+
│
|
|
942
|
+
2: ───X───@───
|
|
943
|
+
''',
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
c_new = cirq.merge_k_qubit_unitaries_to_circuit_op(
|
|
947
|
+
c_orig, k=3, merged_circuit_op_tag="merged", tags_to_ignore=["ignore"]
|
|
948
|
+
)
|
|
949
|
+
cirq.testing.assert_has_diagram(
|
|
950
|
+
cirq.drop_empty_moments(c_new),
|
|
951
|
+
'''
|
|
952
|
+
[ 0: ───@───@─── ]
|
|
953
|
+
[ │ │ ]
|
|
954
|
+
0: ───[ 1: ───X───@─── ]───────────
|
|
955
|
+
[ │ ]
|
|
956
|
+
[ 2: ───X───@─── ][merged]
|
|
957
|
+
│
|
|
958
|
+
1: ───#2───────────────────────────
|
|
959
|
+
│
|
|
960
|
+
2: ───#3───────────────────────────
|
|
961
|
+
''',
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
def test_merge_3q_unitaries_to_circuit_op_3q_gate_doesnt_absorb_unmergeable_gate():
|
|
966
|
+
q = cirq.LineQubit.range(3)
|
|
967
|
+
c_orig = cirq.Circuit(
|
|
968
|
+
cirq.CCZ(*q),
|
|
969
|
+
cirq.Moment(cirq.CNOT(q[0], q[1]), cirq.X(q[2]).with_tags("ignore")),
|
|
970
|
+
cirq.CCZ(*q),
|
|
971
|
+
strategy=cirq.InsertStrategy.NEW,
|
|
972
|
+
)
|
|
973
|
+
cirq.testing.assert_has_diagram(
|
|
974
|
+
c_orig,
|
|
975
|
+
'''
|
|
976
|
+
0: ───@───@───────────@───
|
|
977
|
+
│ │ │
|
|
978
|
+
1: ───@───X───────────@───
|
|
979
|
+
│ │
|
|
980
|
+
2: ───@───X[ignore]───@───
|
|
981
|
+
''',
|
|
982
|
+
)
|
|
983
|
+
|
|
984
|
+
c_new = cirq.merge_k_qubit_unitaries_to_circuit_op(
|
|
985
|
+
c_orig, k=3, merged_circuit_op_tag="merged", tags_to_ignore=["ignore"]
|
|
986
|
+
)
|
|
987
|
+
cirq.testing.assert_has_diagram(
|
|
988
|
+
cirq.drop_empty_moments(c_new),
|
|
989
|
+
'''
|
|
990
|
+
[ 0: ───@───@─── ]
|
|
991
|
+
[ │ │ ]
|
|
992
|
+
0: ───[ 1: ───@───X─── ]───────────────────────@───
|
|
993
|
+
[ │ ] │
|
|
994
|
+
[ 2: ───@─────── ][merged] │
|
|
995
|
+
│ │
|
|
996
|
+
1: ───#2───────────────────────────────────────@───
|
|
997
|
+
│ │
|
|
998
|
+
2: ───#3───────────────────────────X[ignore]───@───
|
|
999
|
+
''',
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def test_merge_3q_unitaries_to_circuit_op_prefer_to_merge_into_earlier_op():
|
|
1004
|
+
q = cirq.LineQubit.range(6)
|
|
1005
|
+
c_orig = cirq.Circuit(
|
|
1006
|
+
cirq.Moment(
|
|
1007
|
+
cirq.CCZ(*q[0:3]), cirq.X(q[3]), cirq.H(q[4]), cirq.H(q[5]).with_tags("ignore")
|
|
1008
|
+
),
|
|
1009
|
+
cirq.Moment(cirq.CNOT(q[0], q[1]), cirq.X(q[2]).with_tags("ignore"), cirq.CCZ(*q[3:6])),
|
|
1010
|
+
cirq.Moment(
|
|
1011
|
+
cirq.X(q[0]),
|
|
1012
|
+
cirq.X(q[1]),
|
|
1013
|
+
cirq.X(q[2]),
|
|
1014
|
+
cirq.X(q[3]).with_tags("ignore"),
|
|
1015
|
+
cirq.CNOT(*q[4:6]),
|
|
1016
|
+
),
|
|
1017
|
+
cirq.Moment(cirq.CCZ(*q[0:3]), cirq.CCZ(*q[3:6])),
|
|
1018
|
+
strategy=cirq.InsertStrategy.NEW,
|
|
1019
|
+
)
|
|
1020
|
+
cirq.testing.assert_has_diagram(
|
|
1021
|
+
c_orig,
|
|
1022
|
+
'''
|
|
1023
|
+
0: ───@───────────@───────────X───────────@───
|
|
1024
|
+
│ │ │
|
|
1025
|
+
1: ───@───────────X───────────X───────────@───
|
|
1026
|
+
│ │
|
|
1027
|
+
2: ───@───────────X[ignore]───X───────────@───
|
|
1028
|
+
|
|
1029
|
+
3: ───X───────────@───────────X[ignore]───@───
|
|
1030
|
+
│ │
|
|
1031
|
+
4: ───H───────────@───────────@───────────@───
|
|
1032
|
+
│ │ │
|
|
1033
|
+
5: ───H[ignore]───@───────────X───────────@───
|
|
1034
|
+
''',
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
c_new = cirq.merge_k_qubit_unitaries_to_circuit_op(
|
|
1038
|
+
c_orig, k=3, merged_circuit_op_tag="merged", tags_to_ignore=["ignore"]
|
|
1039
|
+
)
|
|
1040
|
+
cirq.testing.assert_has_diagram(
|
|
1041
|
+
cirq.drop_empty_moments(c_new),
|
|
1042
|
+
'''
|
|
1043
|
+
[ 0: ───@───@───X─── ] [ 0: ───────@─── ]
|
|
1044
|
+
[ │ │ ] [ │ ]
|
|
1045
|
+
0: ───[ 1: ───@───X───X─── ]────────────────────────────────────────────────────────[ 1: ───────@─── ]───────────
|
|
1046
|
+
[ │ ] [ │ ]
|
|
1047
|
+
[ 2: ───@─────────── ][merged] [ 2: ───X───@─── ][merged]
|
|
1048
|
+
│ │
|
|
1049
|
+
1: ───#2────────────────────────────────────────────────────────────────────────────#2───────────────────────────
|
|
1050
|
+
│ │
|
|
1051
|
+
2: ───#3───────────────────────────────X[ignore]────────────────────────────────────#3───────────────────────────
|
|
1052
|
+
|
|
1053
|
+
[ 3: ───X───@─────── ]
|
|
1054
|
+
[ │ ]
|
|
1055
|
+
3: ────────────────────────────────────[ 4: ───H───@───@─── ]───────────X[ignore]───@────────────────────────────
|
|
1056
|
+
[ │ │ ] │
|
|
1057
|
+
[ 5: ───────@───X─── ][merged] │
|
|
1058
|
+
│ │
|
|
1059
|
+
4: ────────────────────────────────────#2───────────────────────────────────────────@────────────────────────────
|
|
1060
|
+
│ │
|
|
1061
|
+
5: ───H[ignore]────────────────────────#3───────────────────────────────────────────@────────────────────────────
|
|
1062
|
+
''', # noqa: E501
|
|
1063
|
+
)
|
{cirq_core-1.7.0.dev20251015163856.dist-info → cirq_core-1.7.0.dev20251017194258.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cirq-core
|
|
3
|
-
Version: 1.7.0.
|
|
3
|
+
Version: 1.7.0.dev20251017194258
|
|
4
4
|
Summary: A framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits.
|
|
5
5
|
Home-page: http://github.com/quantumlib/cirq
|
|
6
6
|
Author: The Cirq Developers
|
{cirq_core-1.7.0.dev20251015163856.dist-info → cirq_core-1.7.0.dev20251017194258.dist-info}/RECORD
RENAMED
|
@@ -4,8 +4,8 @@ cirq/_compat_test.py,sha256=emXpdD5ZvwLRlFAoQB8YatmZyU3b4e9jg6FppMTUhkU,33900
|
|
|
4
4
|
cirq/_doc.py,sha256=28ZskY9ZtZ_4GS1oXPUgklKnJqmAE-rkUfzcsJ0--nA,2941
|
|
5
5
|
cirq/_import.py,sha256=ixBu4EyGl46Ram2cP3p5eZVEFDW5L2DS-VyTjz4N9iw,8429
|
|
6
6
|
cirq/_import_test.py,sha256=oF4izzOVZLc7NZ0aZHFcGv-r01eiFFt_JORx_x7_D4s,1089
|
|
7
|
-
cirq/_version.py,sha256=
|
|
8
|
-
cirq/_version_test.py,sha256
|
|
7
|
+
cirq/_version.py,sha256=IMZibsH2TjYICrU180KQdB1dmupy9Va1_pKNqcvjn4o,1206
|
|
8
|
+
cirq/_version_test.py,sha256=-O7DI69zPqWXjQmHgCno_e5fNyp_wnL-rEYQaiu7l4A,155
|
|
9
9
|
cirq/conftest.py,sha256=wSDKNdIQRDfLnXvOCWD3erheOw8JHRhdfQ53EyTUIXg,1239
|
|
10
10
|
cirq/json_resolver_cache.py,sha256=A5DIgFAY1hUNt9vai_C3-gGBv24116CJMzQxMcXOax4,13726
|
|
11
11
|
cirq/py.typed,sha256=VFSlmh_lNwnaXzwY-ZuW-C2Ws5PkuDoVgBdNCs0jXJE,63
|
|
@@ -1070,6 +1070,8 @@ cirq/testing/test_data/test_module_missing_json_test_data/__init__.py,sha256=47D
|
|
|
1070
1070
|
cirq/testing/test_data/test_module_missing_testspec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1071
1071
|
cirq/testing/test_data/test_module_missing_testspec/json_test_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1072
1072
|
cirq/transformers/__init__.py,sha256=6noA2FZMgzwYorHZuyi1aMB8mQzohE2FIdNxbNxMLsM,7343
|
|
1073
|
+
cirq/transformers/_connected_component.py,sha256=Qn8rTKCgNzpbB-fiAKG8MHQYiFPb2v_JSZRavNJETVM,8072
|
|
1074
|
+
cirq/transformers/_connected_component_test.py,sha256=BNpoDshQWHw9uvHQpPkkM6yAfQe9s0DR5D25jz8kiUM,6204
|
|
1073
1075
|
cirq/transformers/align.py,sha256=95iI2sIil7V02hRPaXB-wG_eiMb5yF6ffUZU3fHmPP4,3341
|
|
1074
1076
|
cirq/transformers/align_test.py,sha256=blaroKF825RMDCdt403u98OoGck1AXSRCyw1KuWnqB0,7791
|
|
1075
1077
|
cirq/transformers/drop_empty_moments.py,sha256=uZJG9FpUNyA1Mi0xLDuVuhj_siZhPZ1_s08Ry9xQ-1M,1535
|
|
@@ -1110,8 +1112,8 @@ cirq/transformers/tag_transformers.py,sha256=xGTEe9_H857Zd-GJ_g1tlCz_zH3kWKGBq38
|
|
|
1110
1112
|
cirq/transformers/tag_transformers_test.py,sha256=PqIcYFgiLU7VgC1EHkFYhxNCf0D9zKDCZ_Gwtnykkt4,3439
|
|
1111
1113
|
cirq/transformers/transformer_api.py,sha256=woyAJu8NIbo7IP9jgIZNSOVIIj0x4wpFyzpYlto1wCg,16794
|
|
1112
1114
|
cirq/transformers/transformer_api_test.py,sha256=vz_zTDPJIfjfqORGKCxeAs3U1F3X2dFNbe50o79uY-4,13273
|
|
1113
|
-
cirq/transformers/transformer_primitives.py,sha256=
|
|
1114
|
-
cirq/transformers/transformer_primitives_test.py,sha256=
|
|
1115
|
+
cirq/transformers/transformer_primitives.py,sha256=UEGmKYZf3_vzfU_RkIcchmhEpmryW1H7KiU4LlMzC84,40832
|
|
1116
|
+
cirq/transformers/transformer_primitives_test.py,sha256=qQL95Rp_SUmJDSuMVauhVmLeKUBoIGPH3Tzomc3CFAc,51645
|
|
1115
1117
|
cirq/transformers/analytical_decompositions/__init__.py,sha256=Rw7X6hPh14k-cDTcdWI7fQu8v5oU9d1vHuwulUBv-8o,3694
|
|
1116
1118
|
cirq/transformers/analytical_decompositions/clifford_decomposition.py,sha256=_Wikc4KpE3hRH5WWtkYXvTzPxXnJr5-ObHtnpLnKV2c,6781
|
|
1117
1119
|
cirq/transformers/analytical_decompositions/clifford_decomposition_test.py,sha256=DPVqB0rlDsA0BZJOin5QTkVSEfNTVNuf8seGCp49ttg,7178
|
|
@@ -1244,8 +1246,8 @@ cirq/work/sampler.py,sha256=rxbMWvrhu3gfNSBjZKozw28lLKVvBAS_1EGyPdYe8Xg,19041
|
|
|
1244
1246
|
cirq/work/sampler_test.py,sha256=SsMrRvLDYELyOAWLKISjkdEfrBwLYWRsT6D8WrsLM3Q,13533
|
|
1245
1247
|
cirq/work/zeros_sampler.py,sha256=Fs2JWwq0n9zv7_G5Rm-9vPeHUag7uctcMOHg0JTkZpc,2371
|
|
1246
1248
|
cirq/work/zeros_sampler_test.py,sha256=lQLgQDGBLtfImryys2HzQ2jOSGxHgc7-koVBUhv8qYk,3345
|
|
1247
|
-
cirq_core-1.7.0.
|
|
1248
|
-
cirq_core-1.7.0.
|
|
1249
|
-
cirq_core-1.7.0.
|
|
1250
|
-
cirq_core-1.7.0.
|
|
1251
|
-
cirq_core-1.7.0.
|
|
1249
|
+
cirq_core-1.7.0.dev20251017194258.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
|
1250
|
+
cirq_core-1.7.0.dev20251017194258.dist-info/METADATA,sha256=qzy3lCdh84WWUo752Q79w6Kob6Ye011keqXeJjMxwoA,4757
|
|
1251
|
+
cirq_core-1.7.0.dev20251017194258.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
1252
|
+
cirq_core-1.7.0.dev20251017194258.dist-info/top_level.txt,sha256=Sz9iOxHU0IEMLSFGwiwOCaN2e9K-jFbBbtpPN1hB73g,5
|
|
1253
|
+
cirq_core-1.7.0.dev20251017194258.dist-info/RECORD,,
|
{cirq_core-1.7.0.dev20251015163856.dist-info → cirq_core-1.7.0.dev20251017194258.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|