-
Notifications
You must be signed in to change notification settings - Fork 1k
/
circuit.py
2755 lines (2288 loc) · 107 KB
/
circuit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2018 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The circuit data structure.
Circuits consist of a list of Moments, each Moment made up of a set of
Operations. Each Operation is a Gate that acts on some Qubits, for a given
Moment the Operations must all act on distinct Qubits.
"""
import abc
import enum
import html
import itertools
import math
from collections import defaultdict
from typing import (
AbstractSet,
Any,
Callable,
Mapping,
MutableSequence,
cast,
Dict,
FrozenSet,
Iterable,
Iterator,
List,
Optional,
overload,
Sequence,
Set,
Tuple,
Type,
TYPE_CHECKING,
TypeVar,
Union,
)
import networkx
import numpy as np
import cirq._version
from cirq import _compat, devices, ops, protocols, qis
from cirq._doc import document
from cirq.circuits._bucket_priority_queue import BucketPriorityQueue
from cirq.circuits.circuit_operation import CircuitOperation
from cirq.circuits.insert_strategy import InsertStrategy
from cirq.circuits.qasm_output import QasmOutput
from cirq.circuits.text_diagram_drawer import TextDiagramDrawer
from cirq.circuits.moment import Moment
from cirq.protocols import circuit_diagram_info_protocol
from cirq.type_workarounds import NotImplementedType
if TYPE_CHECKING:
import cirq
_TGate = TypeVar('_TGate', bound='cirq.Gate')
CIRCUIT_TYPE = TypeVar('CIRCUIT_TYPE', bound='AbstractCircuit')
document(
CIRCUIT_TYPE,
"""Type variable for an AbstractCircuit.
This can be used when writing generic functions that operate on circuits.
For example, suppose we define the following function:
def foo(circuit: CIRCUIT_TYPE) -> CIRCUIT_TYPE:
...
This lets the typechecker know that this function takes any kind of circuit
and returns the same type, that is, if passed a `cirq.Circuit` it will return
`cirq.Circuit`, and similarly if passed `cirq.FrozenCircuit` it will return
`cirq.FrozenCircuit`. This is particularly useful for things like the
transformer API, since it can preserve more type information than if we typed
the function as taking and returning `cirq.AbstractCircuit`.
""",
)
_INT_TYPE = Union[int, np.integer]
class Alignment(enum.Enum):
"""Alignment option for combining/zipping two circuits together.
Args:
LEFT: Stop when left ends are lined up.
RIGHT: Stop when right ends are lined up.
FIRST: Stop the first time left ends are lined up or right ends are lined up.
"""
LEFT = 1
RIGHT = 2
FIRST = 3
def __repr__(self) -> str:
return f'cirq.Alignment.{self.name}'
class AbstractCircuit(abc.ABC):
"""The base class for Circuit-like objects.
A circuit-like object must have a list of moments (which can be empty).
These methods return information about the circuit, and can be called on
either Circuit or FrozenCircuit objects:
* next_moment_operating_on
* prev_moment_operating_on
* next_moments_operating_on
* operation_at
* all_qubits
* all_operations
* findall_operations
* findall_operations_between
* findall_operations_until_blocked
* findall_operations_with_gate_type
* reachable_frontier_from
* has_measurements
* are_all_matches_terminal
* are_all_measurements_terminal
* unitary
* final_state_vector
* to_text_diagram
* to_text_diagram_drawer
* qid_shape
* all_measurement_key_names
* to_quil
* to_qasm
* save_qasm
* get_independent_qubit_sets
"""
@classmethod
def from_moments(cls: Type[CIRCUIT_TYPE], *moments: 'cirq.OP_TREE') -> CIRCUIT_TYPE:
"""Create a circuit from moment op trees.
Args:
*moments: Op tree for each moment.
"""
return cls._from_moments(
moment if isinstance(moment, Moment) else Moment(moment) for moment in moments
)
@classmethod
@abc.abstractmethod
def _from_moments(cls: Type[CIRCUIT_TYPE], moments: Iterable['cirq.Moment']) -> CIRCUIT_TYPE:
"""Create a circuit from moments.
This must be implemented by subclasses. It provides a more efficient way
to construct a circuit instance since we already have the moments and so
can skip the analysis required to implement various insert strategies.
Args:
moments: Moments of the circuit.
"""
@property
@abc.abstractmethod
def moments(self) -> Sequence['cirq.Moment']:
pass
def freeze(self) -> 'cirq.FrozenCircuit':
"""Creates a FrozenCircuit from this circuit.
If 'self' is a FrozenCircuit, the original object is returned.
"""
from cirq.circuits import FrozenCircuit
if isinstance(self, FrozenCircuit):
return self
return FrozenCircuit(self, strategy=InsertStrategy.EARLIEST)
def unfreeze(self, copy: bool = True) -> 'cirq.Circuit':
"""Creates a Circuit from this circuit.
Args:
copy: If True and 'self' is a Circuit, returns a copy that circuit.
"""
if isinstance(self, Circuit):
return Circuit.copy(self) if copy else self
return Circuit(self, strategy=InsertStrategy.EARLIEST)
def __bool__(self):
return bool(self.moments)
def __eq__(self, other):
if not isinstance(other, AbstractCircuit):
return NotImplemented
return tuple(self.moments) == tuple(other.moments)
def _approx_eq_(self, other: Any, atol: Union[int, float]) -> bool:
"""See `cirq.protocols.SupportsApproximateEquality`."""
if not isinstance(other, AbstractCircuit):
return NotImplemented
return cirq.protocols.approx_eq(tuple(self.moments), tuple(other.moments), atol=atol)
def __ne__(self, other) -> bool:
return not self == other
def __len__(self) -> int:
return len(self.moments)
def __iter__(self) -> Iterator['cirq.Moment']:
return iter(self.moments)
def _decompose_(self) -> 'cirq.OP_TREE':
"""See `cirq.SupportsDecompose`."""
return self.all_operations()
# pylint: disable=function-redefined
@overload
def __getitem__(self, key: int) -> 'cirq.Moment':
pass
@overload
def __getitem__(self, key: Tuple[int, 'cirq.Qid']) -> 'cirq.Operation':
pass
@overload
def __getitem__(self, key: Tuple[int, Iterable['cirq.Qid']]) -> 'cirq.Moment':
pass
@overload
def __getitem__(self: CIRCUIT_TYPE, key: slice) -> CIRCUIT_TYPE:
pass
@overload
def __getitem__(self: CIRCUIT_TYPE, key: Tuple[slice, 'cirq.Qid']) -> CIRCUIT_TYPE:
pass
@overload
def __getitem__(self: CIRCUIT_TYPE, key: Tuple[slice, Iterable['cirq.Qid']]) -> CIRCUIT_TYPE:
pass
def __getitem__(self, key):
if isinstance(key, slice):
return self._from_moments(self.moments[key])
if hasattr(key, '__index__'):
return self.moments[key]
if isinstance(key, tuple):
if len(key) != 2:
raise ValueError('If key is tuple, it must be a pair.')
moment_idx, qubit_idx = key
# moment_idx - int or slice; qubit_idx - Qid or Iterable[Qid].
selected_moments = self.moments[moment_idx]
if isinstance(selected_moments, Moment):
return selected_moments[qubit_idx]
if isinstance(qubit_idx, ops.Qid):
qubit_idx = [qubit_idx]
return self._from_moments(moment[qubit_idx] for moment in selected_moments)
raise TypeError('__getitem__ called with key not of type slice, int, or tuple.')
# pylint: enable=function-redefined
def __str__(self) -> str:
return self.to_text_diagram()
def __repr__(self) -> str:
cls_name = self.__class__.__name__
args = []
if self.moments:
args.append(_list_repr_with_indented_item_lines(self.moments))
return f'cirq.{cls_name}({", ".join(args)})'
def _repr_pretty_(self, p: Any, cycle: bool) -> None:
"""Print ASCII diagram in Jupyter."""
cls_name = self.__class__.__name__
if cycle:
# There should never be a cycle. This is just in case.
p.text(f'{cls_name}(...)')
else:
p.text(self.to_text_diagram())
def _repr_html_(self) -> str:
"""Print ASCII diagram in Jupyter notebook without wrapping lines."""
return (
'<pre style="overflow: auto; white-space: pre;">'
+ html.escape(self.to_text_diagram())
+ '</pre>'
)
def _first_moment_operating_on(
self, qubits: Iterable['cirq.Qid'], indices: Iterable[int]
) -> Optional[int]:
qubits = frozenset(qubits)
for m in indices:
if self._has_op_at(m, qubits):
return m
return None
def next_moment_operating_on(
self, qubits: Iterable['cirq.Qid'], start_moment_index: int = 0, max_distance: int = None
) -> Optional[int]:
"""Finds the index of the next moment that touches the given qubits.
Args:
qubits: We're looking for operations affecting any of these qubits.
start_moment_index: The starting point of the search.
max_distance: The number of moments (starting from the start index
and moving forward) to check. Defaults to no limit.
Returns:
None if there is no matching moment, otherwise the index of the
earliest matching moment.
Raises:
ValueError: negative max_distance.
"""
max_circuit_distance = len(self.moments) - start_moment_index
if max_distance is None:
max_distance = max_circuit_distance
elif max_distance < 0:
raise ValueError(f'Negative max_distance: {max_distance}')
else:
max_distance = min(max_distance, max_circuit_distance)
return self._first_moment_operating_on(
qubits, range(start_moment_index, start_moment_index + max_distance)
)
def next_moments_operating_on(
self, qubits: Iterable['cirq.Qid'], start_moment_index: int = 0
) -> Dict['cirq.Qid', int]:
"""Finds the index of the next moment that touches each qubit.
Args:
qubits: The qubits to find the next moments acting on.
start_moment_index: The starting point of the search.
Returns:
The index of the next moment that touches each qubit. If there
is no such moment, the next moment is specified as the number of
moments in the circuit. Equivalently, can be characterized as one
plus the index of the last moment after start_moment_index
(inclusive) that does *not* act on a given qubit.
"""
next_moments = {}
for q in qubits:
next_moment = self.next_moment_operating_on([q], start_moment_index)
next_moments[q] = len(self.moments) if next_moment is None else next_moment
return next_moments
def prev_moment_operating_on(
self,
qubits: Sequence['cirq.Qid'],
end_moment_index: Optional[int] = None,
max_distance: Optional[int] = None,
) -> Optional[int]:
"""Finds the index of the previous moment that touches the given qubits.
Args:
qubits: We're looking for operations affecting any of these qubits.
end_moment_index: The moment index just after the starting point of
the reverse search. Defaults to the length of the list of
moments.
max_distance: The number of moments (starting just before from the
end index and moving backward) to check. Defaults to no limit.
Returns:
None if there is no matching moment, otherwise the index of the
latest matching moment.
Raises:
ValueError: negative max_distance.
"""
if end_moment_index is None:
end_moment_index = len(self.moments)
if max_distance is None:
max_distance = len(self.moments)
elif max_distance < 0:
raise ValueError(f'Negative max_distance: {max_distance}')
else:
max_distance = min(end_moment_index, max_distance)
# Don't bother searching indices past the end of the list.
if end_moment_index > len(self.moments):
d = end_moment_index - len(self.moments)
end_moment_index -= d
max_distance -= d
if max_distance <= 0:
return None
return self._first_moment_operating_on(
qubits, (end_moment_index - k - 1 for k in range(max_distance))
)
def reachable_frontier_from(
self,
start_frontier: Dict['cirq.Qid', int],
*,
is_blocker: Callable[['cirq.Operation'], bool] = lambda op: False,
) -> Dict['cirq.Qid', int]:
"""Determines how far can be reached into a circuit under certain rules.
The location L = (qubit, moment_index) is *reachable* if and only if the
following all hold true:
- There is not a blocking operation covering L.
- At least one of the following holds:
- qubit is in start frontier and moment_index =
max(start_frontier[qubit], 0).
- There is no operation at L and prev(L) = (qubit,
moment_index-1) is reachable.
- There is an (non-blocking) operation P covering L such that
(q', moment_index - 1) is reachable for every q' on which P
acts.
An operation in moment moment_index is blocking if at least one of the
following hold:
- `is_blocker` returns a truthy value.
- The operation acts on a qubit not in start_frontier.
- The operation acts on a qubit q such that start_frontier[q] >
moment_index.
In other words, the reachable region extends forward through time along
each qubit in start_frontier until it hits a blocking operation. Any
location involving a qubit not in start_frontier is unreachable.
For each qubit q in `start_frontier`, the reachable locations will
correspond to a contiguous range starting at start_frontier[q] and
ending just before some index end_q. The result of this method is a
dictionary, and that dictionary maps each qubit q to its end_q.
Examples:
If `start_frontier` is
```
{
cirq.LineQubit(0): 6,
cirq.LineQubit(1): 2,
cirq.LineQubit(2): 2
}
```
then the reachable wire locations in the following circuit are
highlighted with '█' characters:
```
0 1 2 3 4 5 6 7 8 9 10 11 12 13
0: ───H───@─────────────────█████████████████████─@───H───
│ │
1: ───────@─██H███@██████████████████████─@───H───@───────
│ │
2: ─────────██████@███H██─@───────@───H───@───────────────
│ │
3: ───────────────────────@───H───@───────────────────────
```
And the computed `end_frontier` is
```
{
cirq.LineQubit(0): 11,
cirq.LineQubit(1): 9,
cirq.LineQubit(2): 6,
}
```
Note that the frontier indices (shown above the circuit) are
best thought of (and shown) as happening *between* moment indices.
If we specify a blocker as follows:
```
is_blocker=lambda: op == cirq.CZ(cirq.LineQubit(1),
cirq.LineQubit(2))
```
and use this `start_frontier`:
```
{
cirq.LineQubit(0): 0,
cirq.LineQubit(1): 0,
cirq.LineQubit(2): 0,
cirq.LineQubit(3): 0,
}
```
Then this is the reachable area:
```
0 1 2 3 4 5 6 7 8 9 10 11 12 13
0: ─██H███@██████████████████████████████████████─@───H───
│ │
1: ─██████@███H██─@───────────────────────@───H───@───────
│ │
2: ─█████████████─@───H───@───────@───H───@───────────────
│ │
3: ─█████████████████████─@───H───@───────────────────────
```
and the computed `end_frontier` is:
```
{
cirq.LineQubit(0): 11,
cirq.LineQubit(1): 3,
cirq.LineQubit(2): 3,
cirq.LineQubit(3): 5,
}
```
Args:
start_frontier: A starting set of reachable locations.
is_blocker: A predicate that determines if operations block
reachability. Any location covered by an operation that causes
`is_blocker` to return True is considered to be an unreachable
location.
Returns:
An end_frontier dictionary, containing an end index for each qubit q
mapped to a start index by the given `start_frontier` dictionary.
To determine if a location (q, i) was reachable, you can use
this expression:
q in start_frontier and start_frontier[q] <= i < end_frontier[q]
where i is the moment index, q is the qubit, and end_frontier is the
result of this method.
"""
active: Set['cirq.Qid'] = set()
end_frontier = {}
queue = BucketPriorityQueue[ops.Operation](drop_duplicate_entries=True)
def enqueue_next(qubit: 'cirq.Qid', moment: int) -> None:
next_moment = self.next_moment_operating_on([qubit], moment)
if next_moment is None:
end_frontier[qubit] = max(len(self), start_frontier[qubit])
if qubit in active:
active.remove(qubit)
else:
next_op = self.operation_at(qubit, next_moment)
assert next_op is not None
queue.enqueue(next_moment, next_op)
for start_qubit, start_moment in start_frontier.items():
enqueue_next(start_qubit, start_moment)
while queue:
cur_moment, cur_op = queue.dequeue()
for q in cur_op.qubits:
if (
q in start_frontier
and cur_moment >= start_frontier[q]
and q not in end_frontier
):
active.add(q)
continue_past = (
cur_op is not None and active.issuperset(cur_op.qubits) and not is_blocker(cur_op)
)
if continue_past:
for q in cur_op.qubits:
enqueue_next(q, cur_moment + 1)
else:
for q in cur_op.qubits:
if q in active:
end_frontier[q] = cur_moment
active.remove(q)
return end_frontier
def findall_operations_between(
self,
start_frontier: Dict['cirq.Qid', int],
end_frontier: Dict['cirq.Qid', int],
omit_crossing_operations: bool = False,
) -> List[Tuple[int, 'cirq.Operation']]:
"""Finds operations between the two given frontiers.
If a qubit is in `start_frontier` but not `end_frontier`, its end index
defaults to the end of the circuit. If a qubit is in `end_frontier` but
not `start_frontier`, its start index defaults to the start of the
circuit. Operations on qubits not mentioned in either frontier are not
included in the results.
Args:
start_frontier: Just before where to start searching for operations,
for each qubit of interest. Start frontier indices are
inclusive.
end_frontier: Just before where to stop searching for operations,
for each qubit of interest. End frontier indices are exclusive.
omit_crossing_operations: Determines whether or not operations that
cross from a location between the two frontiers to a location
outside the two frontiers are included or excluded. (Operations
completely inside are always included, and operations completely
outside are always excluded.)
Returns:
A list of tuples. Each tuple describes an operation found between
the two frontiers. The first item of each tuple is the index of the
moment containing the operation, and the second item is the
operation itself. The list is sorted so that the moment index
increases monotonically.
"""
result = BucketPriorityQueue[ops.Operation](drop_duplicate_entries=True)
involved_qubits = set(start_frontier.keys()) | set(end_frontier.keys())
# Note: only sorted to ensure a deterministic result ordering.
for q in sorted(involved_qubits):
for i in range(start_frontier.get(q, 0), end_frontier.get(q, len(self))):
op = self.operation_at(q, i)
if op is None:
continue
if omit_crossing_operations and not involved_qubits.issuperset(op.qubits):
continue
result.enqueue(i, op)
return list(result)
def findall_operations_until_blocked(
self,
start_frontier: Dict['cirq.Qid', int],
is_blocker: Callable[['cirq.Operation'], bool] = lambda op: False,
) -> List[Tuple[int, 'cirq.Operation']]:
"""Finds all operations until a blocking operation is hit.
An operation is considered blocking if both of the following hold:
- It is in the 'light cone' of start_frontier.
- `is_blocker` returns a truthy value, or it acts on a blocked qubit
Every qubit acted on by a blocking operation is thereafter itself
blocked.
The notion of reachability here differs from that in
reachable_frontier_from in two respects:
- An operation is not considered blocking only because it is in a
moment before the start_frontier of one of the qubits on which it
acts.
- Operations that act on qubits not in start_frontier are not
automatically blocking.
For every (moment_index, operation) returned:
- moment_index >= min((start_frontier[q] for q in operation.qubits
if q in start_frontier), default=0)
- set(operation.qubits).intersection(start_frontier)
Below are some examples, where on the left the opening parentheses show
`start_frontier` and on the right are the operations included (with
their moment indices) in the output. `F` and `T` indicate that
`is_blocker` return `False` or `True`, respectively, when applied to
the gates; `M` indicates that it doesn't matter.
```
─(─F───F─────── ┄(─F───F─)┄┄┄┄┄
│ │ │ │
─(─F───F───T─── => ┄(─F───F─)┄┄┄┄┄
│ ┊
───────────T─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
───M─────(─F─── ┄┄┄┄┄┄┄┄┄(─F─)┄┄
│ │ ┊ │
───M───M─(─F─── ┄┄┄┄┄┄┄┄┄(─F─)┄┄
│ => ┊
───────M───M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│ ┊
───────────M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
───M─(─────M─── ┄┄┄┄┄()┄┄┄┄┄┄┄┄
│ │ ┊ ┊
───M─(─T───M─── ┄┄┄┄┄()┄┄┄┄┄┄┄┄
│ => ┊
───────T───M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│ ┊
───────────M─── ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
─(─F───F─── ┄(─F───F─)┄
│ │ => │ │
───F─(─F─── ┄(─F───F─)┄
─(─F─────────── ┄(─F─)┄┄┄┄┄┄┄┄┄
│ │
───F───F─────── ┄(─F─)┄┄┄┄┄┄┄┄┄
│ => ┊
───────F───F─── ┄┄┄┄┄┄┄┄┄(─F─)┄
│ │
─(─────────F─── ┄┄┄┄┄┄┄┄┄(─F─)┄
```
Args:
start_frontier: A starting set of reachable locations.
is_blocker: A predicate that determines if operations block
reachability. Any location covered by an operation that causes
`is_blocker` to return True is considered to be an unreachable
location.
Returns:
A list of tuples. Each tuple describes an operation found between
the start frontier and a blocking operation. The first item of
each tuple is the index of the moment containing the operation,
and the second item is the operation itself.
"""
op_list: List[Tuple[int, ops.Operation]] = []
if not start_frontier:
return op_list
start_index = min(start_frontier.values())
blocked_qubits: Set[cirq.Qid] = set()
for index, moment in enumerate(self[start_index:], start_index):
active_qubits = set(q for q, s in start_frontier.items() if s <= index)
for op in moment.operations:
if is_blocker(op) or blocked_qubits.intersection(op.qubits):
blocked_qubits.update(op.qubits)
elif active_qubits.intersection(op.qubits):
op_list.append((index, op))
if blocked_qubits.issuperset(start_frontier):
break
return op_list
def operation_at(self, qubit: 'cirq.Qid', moment_index: int) -> Optional['cirq.Operation']:
"""Finds the operation on a qubit within a moment, if any.
Args:
qubit: The qubit to check for an operation on.
moment_index: The index of the moment to check for an operation
within. Allowed to be beyond the end of the circuit.
Returns:
None if there is no operation on the qubit at the given moment, or
else the operation.
"""
if not 0 <= moment_index < len(self.moments):
return None
return self.moments[moment_index].operation_at(qubit)
def findall_operations(
self, predicate: Callable[['cirq.Operation'], bool]
) -> Iterable[Tuple[int, 'cirq.Operation']]:
"""Find the locations of all operations that satisfy a given condition.
This returns an iterator of (index, operation) tuples where each
operation satisfies op_cond(operation) is truthy. The indices are
in order of the moments and then order of the ops within that moment.
Args:
predicate: A method that takes an Operation and returns a Truthy
value indicating the operation meets the find condition.
Returns:
An iterator (index, operation)'s that satisfy the op_condition.
"""
for index, moment in enumerate(self.moments):
for op in moment.operations:
if predicate(op):
yield index, op
def findall_operations_with_gate_type(
self, gate_type: Type[_TGate]
) -> Iterable[Tuple[int, 'cirq.GateOperation', _TGate]]:
"""Find the locations of all gate operations of a given type.
Args:
gate_type: The type of gate to find, e.g. XPowGate or
MeasurementGate.
Returns:
An iterator (index, operation, gate)'s for operations with the given
gate type.
"""
result = self.findall_operations(lambda operation: isinstance(operation.gate, gate_type))
for index, op in result:
gate_op = cast(ops.GateOperation, op)
yield index, gate_op, cast(_TGate, gate_op.gate)
def has_measurements(self):
"""Returns whether or not this circuit has measurements.
Returns: True if `cirq.is_measurement(self)` is True otherwise False.
"""
return protocols.is_measurement(self)
def are_all_measurements_terminal(self) -> bool:
"""Whether all measurement gates are at the end of the circuit.
Returns: True iff no measurement is followed by a gate.
"""
return self.are_all_matches_terminal(protocols.is_measurement)
def are_all_matches_terminal(self, predicate: Callable[['cirq.Operation'], bool]) -> bool:
"""Check whether all of the ops that satisfy a predicate are terminal.
This method will transparently descend into any CircuitOperations this
circuit contains; as a result, it will misbehave if the predicate
refers to CircuitOperations. See the tests for an example of this.
Args:
predicate: A predicate on ops.Operations which is being checked.
Returns:
Whether or not all `Operation` s in a circuit that satisfy the
given predicate are terminal. Also checks within any CircuitGates
the circuit may contain.
"""
from cirq.circuits import CircuitOperation
if not all(
self.next_moment_operating_on(op.qubits, i + 1) is None
for (i, op) in self.findall_operations(predicate)
if not isinstance(op.untagged, CircuitOperation)
):
return False
for i, moment in enumerate(self.moments):
for op in moment.operations:
circuit = getattr(op.untagged, 'circuit', None)
if circuit is None:
continue
if not circuit.are_all_matches_terminal(predicate):
return False
if i < len(self.moments) - 1 and not all(
self.next_moment_operating_on(op.qubits, i + 1) is None
for _, op in circuit.findall_operations(predicate)
):
return False
return True
def are_any_measurements_terminal(self) -> bool:
"""Whether any measurement gates are at the end of the circuit.
Returns: True iff some measurements are not followed by a gate.
"""
return self.are_any_matches_terminal(protocols.is_measurement)
def are_any_matches_terminal(self, predicate: Callable[['cirq.Operation'], bool]) -> bool:
"""Check whether any of the ops that satisfy a predicate are terminal.
This method will transparently descend into any CircuitOperations this
circuit contains; as a result, it will misbehave if the predicate
refers to CircuitOperations. See the tests for an example of this.
Args:
predicate: A predicate on ops.Operations which is being checked.
Returns:
Whether or not any `Operation` s in a circuit that satisfy the
given predicate are terminal. Also checks within any CircuitGates
the circuit may contain.
"""
from cirq.circuits import CircuitOperation
if any(
self.next_moment_operating_on(op.qubits, i + 1) is None
for (i, op) in self.findall_operations(predicate)
if not isinstance(op.untagged, CircuitOperation)
):
return True
for i, moment in reversed(list(enumerate(self.moments))):
for op in moment.operations:
circuit = getattr(op.untagged, 'circuit', None)
if circuit is None:
continue
if not circuit.are_any_matches_terminal(predicate):
continue
if i == len(self.moments) - 1 or any(
self.next_moment_operating_on(op.qubits, i + 1) is None
for _, op in circuit.findall_operations(predicate)
):
return True
return False
def _has_op_at(self, moment_index: int, qubits: Iterable['cirq.Qid']) -> bool:
return 0 <= moment_index < len(self.moments) and self.moments[moment_index].operates_on(
qubits
)
def all_qubits(self) -> FrozenSet['cirq.Qid']:
"""Returns the qubits acted upon by Operations in this circuit.
Returns: FrozenSet of `cirq.Qid` objects acted on by all operations
in this circuit.
"""
return frozenset(q for m in self.moments for q in m.qubits)
def all_operations(self) -> Iterator['cirq.Operation']:
"""Returns an iterator over the operations in the circuit.
Returns: Iterator over `cirq.Operation` elements found in this circuit.
"""
return (op for moment in self for op in moment.operations)
def map_operations(
self: CIRCUIT_TYPE, func: Callable[['cirq.Operation'], 'cirq.OP_TREE']
) -> CIRCUIT_TYPE:
"""Applies the given function to all operations in this circuit.
Args:
func: a mapping function from operations to OP_TREEs.
Returns:
A circuit with the same basic structure as the original, but with
each operation `op` replaced with `func(op)`.
"""
def map_moment(moment: 'cirq.Moment') -> 'cirq.Circuit':
"""Apply func to expand each op into a circuit, then zip up the circuits."""
return Circuit.zip(*[Circuit(func(op)) for op in moment])
return self._from_moments(m for moment in self for m in map_moment(moment))
def qid_shape(
self, qubit_order: 'cirq.QubitOrderOrList' = ops.QubitOrder.DEFAULT
) -> Tuple[int, ...]:
"""Get the qubit shapes of all qubits in this circuit.
Returns: A tuple containing the dimensions (shape) of all qudits
found in this circuit according to `qubit_order`.
"""
qids = ops.QubitOrder.as_qubit_order(qubit_order).order_for(self.all_qubits())
return protocols.qid_shape(qids)
def all_measurement_key_objs(self) -> FrozenSet['cirq.MeasurementKey']:
return frozenset(
key for op in self.all_operations() for key in protocols.measurement_key_objs(op)
)
def _measurement_key_objs_(self) -> FrozenSet['cirq.MeasurementKey']:
"""Returns the set of all measurement keys in this circuit.
Returns: FrozenSet of `cirq.MeasurementKey` objects that are
in this circuit.
"""
return self.all_measurement_key_objs()
def all_measurement_key_names(self) -> FrozenSet[str]:
"""Returns the set of all measurement key names in this circuit.
Returns: FrozenSet of strings that are the measurement key
names in this circuit.
"""
return frozenset(
key for op in self.all_operations() for key in protocols.measurement_key_names(op)
)
def _measurement_key_names_(self) -> FrozenSet[str]:
return self.all_measurement_key_names()
def _with_measurement_key_mapping_(self, key_map: Mapping[str, str]):
return self._from_moments(
protocols.with_measurement_key_mapping(moment, key_map) for moment in self.moments
)
def _with_key_path_(self, path: Tuple[str, ...]):
return self._from_moments(protocols.with_key_path(moment, path) for moment in self.moments)
def _with_key_path_prefix_(self, prefix: Tuple[str, ...]):
return self._from_moments(
protocols.with_key_path_prefix(moment, prefix) for moment in self.moments
)
def _with_rescoped_keys_(
self, path: Tuple[str, ...], bindable_keys: FrozenSet['cirq.MeasurementKey']
):
moments = []
for moment in self.moments:
new_moment = protocols.with_rescoped_keys(moment, path, bindable_keys)
moments.append(new_moment)
bindable_keys |= protocols.measurement_key_objs(new_moment)
return self._from_moments(moments)
def _qid_shape_(self) -> Tuple[int, ...]:
return self.qid_shape()
def _has_unitary_(self) -> bool:
if not self.are_all_measurements_terminal():
return False
unitary_ops = protocols.decompose(
self.all_operations(),