13
13
# limitations under the License.
14
14
15
15
import abc
16
- from typing import Dict , Iterator , List , Sequence , Tuple
16
+ from typing import Callable , Dict , Iterator , List , Sequence , Tuple
17
17
from numpy .typing import NDArray
18
18
19
19
import cirq
@@ -34,6 +34,7 @@ def _unary_iteration_segtree(
34
34
r : int ,
35
35
l_iter : int ,
36
36
r_iter : int ,
37
+ break_early : Callable [[int , int ], bool ],
37
38
) -> Iterator [Tuple [cirq .OP_TREE , cirq .Qid , int ]]:
38
39
"""Constructs a unary iteration circuit by iterating over nodes of an implicit Segment Tree.
39
40
@@ -53,6 +54,11 @@ def _unary_iteration_segtree(
53
54
r: Right index of the range represented by current node of the segment tree.
54
55
l_iter: Left index of iteration range over which the segment tree should be constructed.
55
56
r_iter: Right index of iteration range over which the segment tree should be constructed.
57
+ break_early: For each internal node of the segment tree, `break_early(l, r)` is called to
58
+ evaluate whether the unary iteration should terminate early and not recurse in the
59
+ subtree of the node representing range `[l, r)`. If True, the internal node is
60
+ considered equivalent to a leaf node and the method yields only one tuple
61
+ `(OP_TREE, control_qubit, l)` for all integers in the range `[l, r)`.
56
62
57
63
Yields:
58
64
One `Tuple[cirq.OP_TREE, cirq.Qid, int]` for each leaf node in the segment tree. The i'th
@@ -68,8 +74,8 @@ def _unary_iteration_segtree(
68
74
if l >= r_iter or l_iter >= r :
69
75
# Range corresponding to this node is completely outside of iteration range.
70
76
return
71
- if l == (r - 1 ):
72
- # Reached a leaf node; yield the operations.
77
+ if l_iter <= l < r <= r_iter and ( l == (r - 1 ) or break_early ( l , r ) ):
78
+ # Reached a leaf node or a "special" internal node ; yield the operations.
73
79
yield tuple (ops ), control , l
74
80
ops .clear ()
75
81
return
@@ -78,20 +84,24 @@ def _unary_iteration_segtree(
78
84
if r_iter <= m :
79
85
# Yield only left sub-tree.
80
86
yield from _unary_iteration_segtree (
81
- ops , control , selection , ancilla , sl + 1 , l , m , l_iter , r_iter
87
+ ops , control , selection , ancilla , sl + 1 , l , m , l_iter , r_iter , break_early
82
88
)
83
89
return
84
90
if l_iter >= m :
85
91
# Yield only right sub-tree
86
92
yield from _unary_iteration_segtree (
87
- ops , control , selection , ancilla , sl + 1 , m , r , l_iter , r_iter
93
+ ops , control , selection , ancilla , sl + 1 , m , r , l_iter , r_iter , break_early
88
94
)
89
95
return
90
96
anc , sq = ancilla [sl ], selection [sl ]
91
97
ops .append (and_gate .And ((1 , 0 )).on (control , sq , anc ))
92
- yield from _unary_iteration_segtree (ops , anc , selection , ancilla , sl + 1 , l , m , l_iter , r_iter )
98
+ yield from _unary_iteration_segtree (
99
+ ops , anc , selection , ancilla , sl + 1 , l , m , l_iter , r_iter , break_early
100
+ )
93
101
ops .append (cirq .CNOT (control , anc ))
94
- yield from _unary_iteration_segtree (ops , anc , selection , ancilla , sl + 1 , m , r , l_iter , r_iter )
102
+ yield from _unary_iteration_segtree (
103
+ ops , anc , selection , ancilla , sl + 1 , m , r , l_iter , r_iter , break_early
104
+ )
95
105
ops .append (and_gate .And (adjoint = True ).on (control , sq , anc ))
96
106
97
107
@@ -101,16 +111,17 @@ def _unary_iteration_zero_control(
101
111
ancilla : Sequence [cirq .Qid ],
102
112
l_iter : int ,
103
113
r_iter : int ,
114
+ break_early : Callable [[int , int ], bool ],
104
115
) -> Iterator [Tuple [cirq .OP_TREE , cirq .Qid , int ]]:
105
116
sl , l , r = 0 , 0 , 2 ** len (selection )
106
117
m = (l + r ) >> 1
107
118
ops .append (cirq .X (selection [0 ]))
108
119
yield from _unary_iteration_segtree (
109
- ops , selection [0 ], selection [1 :], ancilla , sl , l , m , l_iter , r_iter
120
+ ops , selection [0 ], selection [1 :], ancilla , sl , l , m , l_iter , r_iter , break_early
110
121
)
111
122
ops .append (cirq .X (selection [0 ]))
112
123
yield from _unary_iteration_segtree (
113
- ops , selection [0 ], selection [1 :], ancilla , sl , m , r , l_iter , r_iter
124
+ ops , selection [0 ], selection [1 :], ancilla , sl , m , r , l_iter , r_iter , break_early
114
125
)
115
126
116
127
@@ -121,9 +132,12 @@ def _unary_iteration_single_control(
121
132
ancilla : Sequence [cirq .Qid ],
122
133
l_iter : int ,
123
134
r_iter : int ,
135
+ break_early : Callable [[int , int ], bool ],
124
136
) -> Iterator [Tuple [cirq .OP_TREE , cirq .Qid , int ]]:
125
137
sl , l , r = 0 , 0 , 2 ** len (selection )
126
- yield from _unary_iteration_segtree (ops , control , selection , ancilla , sl , l , r , l_iter , r_iter )
138
+ yield from _unary_iteration_segtree (
139
+ ops , control , selection , ancilla , sl , l , r , l_iter , r_iter , break_early
140
+ )
127
141
128
142
129
143
def _unary_iteration_multi_controls (
@@ -133,6 +147,7 @@ def _unary_iteration_multi_controls(
133
147
ancilla : Sequence [cirq .Qid ],
134
148
l_iter : int ,
135
149
r_iter : int ,
150
+ break_early : Callable [[int , int ], bool ],
136
151
) -> Iterator [Tuple [cirq .OP_TREE , cirq .Qid , int ]]:
137
152
num_controls = len (controls )
138
153
and_ancilla = ancilla [: num_controls - 2 ]
@@ -142,7 +157,7 @@ def _unary_iteration_multi_controls(
142
157
)
143
158
ops .append (multi_controlled_and )
144
159
yield from _unary_iteration_single_control (
145
- ops , and_target , selection , ancilla [num_controls - 1 :], l_iter , r_iter
160
+ ops , and_target , selection , ancilla [num_controls - 1 :], l_iter , r_iter , break_early
146
161
)
147
162
ops .append (cirq .inverse (multi_controlled_and ))
148
163
@@ -154,6 +169,7 @@ def unary_iteration(
154
169
controls : Sequence [cirq .Qid ],
155
170
selection : Sequence [cirq .Qid ],
156
171
qubit_manager : cirq .QubitManager ,
172
+ break_early : Callable [[int , int ], bool ] = lambda l , r : False ,
157
173
) -> Iterator [Tuple [cirq .OP_TREE , cirq .Qid , int ]]:
158
174
"""The method performs unary iteration on `selection` integer in `range(l_iter, r_iter)`.
159
175
@@ -181,6 +197,9 @@ def unary_iteration(
181
197
... circuit.append(j_ops)
182
198
>>> circuit.append(i_ops)
183
199
200
+ Note: Unary iteration circuits assume that the selection register stores integers only in the
201
+ range `[l, r)` for which the corresponding unary iteration circuit should be built.
202
+
184
203
Args:
185
204
l_iter: Starting index of the iteration range.
186
205
r_iter: Ending index of the iteration range.
@@ -192,6 +211,11 @@ def unary_iteration(
192
211
controls: Control register of qubits.
193
212
selection: Selection register of qubits.
194
213
qubit_manager: A `cirq.QubitManager` to allocate new qubits.
214
+ break_early: For each internal node of the segment tree, `break_early(l, r)` is called to
215
+ evaluate whether the unary iteration should terminate early and not recurse in the
216
+ subtree of the node representing range `[l, r)`. If True, the internal node is
217
+ considered equivalent to a leaf node and the method yields only one tuple
218
+ `(OP_TREE, control_qubit, l)` for all integers in the range `[l, r)`.
195
219
196
220
Yields:
197
221
(r_iter - l_iter) different tuples, each corresponding to an integer in range
@@ -207,14 +231,16 @@ def unary_iteration(
207
231
assert len (selection ) > 0
208
232
ancilla = qubit_manager .qalloc (max (0 , len (controls ) + len (selection ) - 1 ))
209
233
if len (controls ) == 0 :
210
- yield from _unary_iteration_zero_control (flanking_ops , selection , ancilla , l_iter , r_iter )
234
+ yield from _unary_iteration_zero_control (
235
+ flanking_ops , selection , ancilla , l_iter , r_iter , break_early
236
+ )
211
237
elif len (controls ) == 1 :
212
238
yield from _unary_iteration_single_control (
213
- flanking_ops , controls [0 ], selection , ancilla , l_iter , r_iter
239
+ flanking_ops , controls [0 ], selection , ancilla , l_iter , r_iter , break_early
214
240
)
215
241
else :
216
242
yield from _unary_iteration_multi_controls (
217
- flanking_ops , controls , selection , ancilla , l_iter , r_iter
243
+ flanking_ops , controls , selection , ancilla , l_iter , r_iter , break_early
218
244
)
219
245
qubit_manager .qfree (ancilla )
220
246
@@ -231,6 +257,9 @@ class UnaryIterationGate(infra.GateWithRegisters):
231
257
indexed operations on a target register depending on the index value stored in a selection
232
258
register.
233
259
260
+ Note: Unary iteration circuits assume that the selection register stores integers only in the
261
+ range `[l, r)` for which the corresponding unary iteration circuit should be built.
262
+
234
263
References:
235
264
[Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity]
236
265
(https://arxiv.org/abs/1805.03662).
@@ -308,10 +337,38 @@ def decompose_zero_selection(
308
337
"""
309
338
raise NotImplementedError ("Selection register must not be empty." )
310
339
340
+ def _break_early (self , selection_index_prefix : Tuple [int , ...], l : int , r : int ) -> bool :
341
+ """Derived classes should override this method to specify an early termination condition.
342
+
343
+ For each internal node of the unary iteration segment tree, `break_early(l, r)` is called
344
+ to evaluate whether the unary iteration should not recurse in the subtree of the node
345
+ representing range `[l, r)`. If True, the internal node is considered equivalent to a leaf
346
+ node and thus, `self.nth_operation` will be called for only integer `l` in the range [l, r).
347
+
348
+ When the `UnaryIteration` class is constructed using multiple selection registers, i.e. we
349
+ wish to perform nested coherent for-loops, a unary iteration segment tree is constructed
350
+ corresponding to each nested coherent for-loop. For every such unary iteration segment tree,
351
+ the `_break_early` condition is checked by passing the `selection_index_prefix` tuple.
352
+
353
+ Args:
354
+ selection_index_prefix: To evaluate the early breaking condition for the i'th nested
355
+ for-loop, the `selection_index_prefix` contains `i-1` integers corresponding to
356
+ the loop variable values for the first `i-1` nested loops.
357
+ l: Beginning of range `[l, r)` for internal node of unary iteration segment tree.
358
+ r: End (exclusive) of range `[l, r)` for internal node of unary iteration segment tree.
359
+
360
+ Returns:
361
+ True of the `len(selection_index_prefix)`'th unary iteration should terminate early for
362
+ the given parameters.
363
+ """
364
+ return False
365
+
311
366
def decompose_from_registers (
312
367
self , * , context : cirq .DecompositionContext , ** quregs : NDArray [cirq .Qid ]
313
368
) -> cirq .OP_TREE :
314
- if self .selection_registers .total_bits () == 0 :
369
+ if self .selection_registers .total_bits () == 0 or self ._break_early (
370
+ (), 0 , self .selection_registers [0 ].iteration_length
371
+ ):
315
372
return self .decompose_zero_selection (context = context , ** quregs )
316
373
317
374
num_loops = len (self .selection_registers )
@@ -354,20 +411,23 @@ def unary_iteration_loops(
354
411
return
355
412
# Use recursion to write `num_loops` nested loops using unary_iteration().
356
413
ops : List [cirq .Operation ] = []
414
+ selection_index_prefix = tuple (selection_reg_name_to_val .values ())
357
415
ith_for_loop = unary_iteration (
358
416
l_iter = 0 ,
359
417
r_iter = self .selection_registers [nested_depth ].iteration_length ,
360
418
flanking_ops = ops ,
361
419
controls = controls ,
362
420
selection = [* quregs [self .selection_registers [nested_depth ].name ]],
363
421
qubit_manager = context .qubit_manager ,
422
+ break_early = lambda l , r : self ._break_early (selection_index_prefix , l , r ),
364
423
)
365
424
for op_tree , control_qid , n in ith_for_loop :
366
425
yield op_tree
367
426
selection_reg_name_to_val [self .selection_registers [nested_depth ].name ] = n
368
427
yield from unary_iteration_loops (
369
428
nested_depth + 1 , selection_reg_name_to_val , (control_qid ,)
370
429
)
430
+ selection_reg_name_to_val .pop (self .selection_registers [nested_depth ].name )
371
431
yield ops
372
432
373
433
return unary_iteration_loops (0 , {}, self .control_registers .merge_qubits (** quregs ))
0 commit comments