-
Notifications
You must be signed in to change notification settings - Fork 199
Expand file tree
/
Copy path__init__.pyi
More file actions
13532 lines (12041 loc) · 482 KB
/
__init__.pyi
File metadata and controls
13532 lines (12041 loc) · 482 KB
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
"""Stim (Development Version): a fast quantum stabilizer circuit library."""
# (This is a stubs file describing the classes and methods in stim.)
from __future__ import annotations
from typing import overload, TYPE_CHECKING, List, Dict, Tuple, Any, Union, Iterable, Optional
if TYPE_CHECKING:
import io
import pathlib
import numpy as np
import stim
class Circuit:
"""A mutable stabilizer circuit.
The stim.Circuit class is arguably the most important object in the
entire library. It is the interface through which you explain a
noisy quantum computation to Stim, in order to do fast bulk sampling
or fast error analysis.
For example, suppose you want to use a matching-based decoder on a
new quantum error correction construction. Stim can help you do this
but the very first step is to create a circuit implementing the
construction. Once you have the circuit you can then use methods like
stim.Circuit.detector_error_model() to create an object that can be
used to configure the decoder, or like
stim.Circuit.compile_detector_sampler() to produce problems for the
decoder to solve, or like stim.Circuit.shortest_graphlike_error() to
check for mistakes in the implementation of the code.
Examples:
>>> import stim
>>> c = stim.Circuit()
>>> c.append("X", 0)
>>> c.append("M", 0)
>>> c.compile_sampler().sample(shots=1)
array([[ True]])
>>> stim.Circuit('''
... H 0
... CNOT 0 1
... M 0 1
... DETECTOR rec[-1] rec[-2]
... ''').compile_detector_sampler().sample(shots=1)
array([[False]])
"""
def __add__(
self,
second: stim.Circuit,
) -> stim.Circuit:
"""Creates a circuit by appending two circuits.
Examples:
>>> import stim
>>> c1 = stim.Circuit('''
... X 0
... Y 1 2
... ''')
>>> c2 = stim.Circuit('''
... M 0 1 2
... ''')
>>> c1 + c2
stim.Circuit('''
X 0
Y 1 2
M 0 1 2
''')
"""
def __eq__(
self,
arg0: stim.Circuit,
) -> bool:
"""Determines if two circuits have identical contents.
"""
@overload
def __getitem__(
self,
index_or_slice: int,
) -> Union[stim.CircuitInstruction, stim.CircuitRepeatBlock]:
pass
@overload
def __getitem__(
self,
index_or_slice: slice,
) -> stim.Circuit:
pass
def __getitem__(
self,
index_or_slice: object,
) -> object:
"""Returns copies of instructions from the circuit.
Args:
index_or_slice: An integer index picking out an instruction to return, or a
slice picking out a range of instructions to return as a circuit.
Returns:
If the index was an integer, then an instruction from the circuit.
If the index was a slice, then a circuit made up of the instructions in that
slice.
Examples:
>>> import stim
>>> circuit = stim.Circuit('''
... X 0
... X_ERROR(0.5) 2
... REPEAT 100 {
... X 0
... Y 1 2
... }
... TICK
... M 0
... DETECTOR rec[-1]
... ''')
>>> circuit[1]
stim.CircuitInstruction('X_ERROR', [stim.GateTarget(2)], [0.5])
>>> circuit[2]
stim.CircuitRepeatBlock(100, stim.Circuit('''
X 0
Y 1 2
'''))
>>> circuit[1::2]
stim.Circuit('''
X_ERROR(0.5) 2
TICK
DETECTOR rec[-1]
''')
"""
def __iadd__(
self,
second: stim.Circuit,
) -> stim.Circuit:
"""Appends a circuit into the receiving circuit (mutating it).
Examples:
>>> import stim
>>> c1 = stim.Circuit('''
... X 0
... Y 1 2
... ''')
>>> c2 = stim.Circuit('''
... M 0 1 2
... ''')
>>> c1 += c2
>>> print(repr(c1))
stim.Circuit('''
X 0
Y 1 2
M 0 1 2
''')
"""
def __imul__(
self,
repetitions: int,
) -> stim.Circuit:
"""Mutates the circuit by putting its contents into a REPEAT block.
Special case: if the repetition count is 0, the circuit is cleared.
Special case: if the repetition count is 1, nothing happens.
Args:
repetitions: The number of times the REPEAT block should repeat.
Examples:
>>> import stim
>>> c = stim.Circuit('''
... X 0
... Y 1 2
... ''')
>>> c *= 3
>>> print(repr(c))
stim.Circuit('''
REPEAT 3 {
X 0
Y 1 2
}
''')
"""
def __init__(
self,
stim_program_text: str = '',
) -> None:
"""Creates a stim.Circuit.
Args:
stim_program_text: Defaults to empty. Describes operations to append into
the circuit.
Examples:
>>> import stim
>>> empty = stim.Circuit()
>>> not_empty = stim.Circuit('''
... X 0
... CNOT 0 1
... M 1
... ''')
"""
def __len__(
self,
) -> int:
"""Returns the number of top-level instructions and blocks in the circuit.
Instructions inside of blocks are not included in this count.
Examples:
>>> import stim
>>> len(stim.Circuit())
0
>>> len(stim.Circuit('''
... X 0
... X_ERROR(0.5) 1 2
... TICK
... M 0
... DETECTOR rec[-1]
... '''))
5
>>> len(stim.Circuit('''
... REPEAT 100 {
... X 0
... Y 1 2
... }
... '''))
1
"""
def __mul__(
self,
repetitions: int,
) -> stim.Circuit:
"""Repeats the circuit using a REPEAT block.
Has special cases for 0 repetitions and 1 repetitions.
Args:
repetitions: The number of times the REPEAT block should repeat.
Returns:
repetitions=0: An empty circuit.
repetitions=1: A copy of this circuit.
repetitions>=2: A circuit with a single REPEAT block, where the contents of
that repeat block are this circuit.
Examples:
>>> import stim
>>> c = stim.Circuit('''
... X 0
... Y 1 2
... ''')
>>> c * 3
stim.Circuit('''
REPEAT 3 {
X 0
Y 1 2
}
''')
"""
def __ne__(
self,
arg0: stim.Circuit,
) -> bool:
"""Determines if two circuits have non-identical contents.
"""
def __repr__(
self,
) -> str:
"""Returns text that is a valid python expression evaluating to an equivalent `stim.Circuit`.
"""
def __rmul__(
self,
repetitions: int,
) -> stim.Circuit:
"""Repeats the circuit using a REPEAT block.
Has special cases for 0 repetitions and 1 repetitions.
Args:
repetitions: The number of times the REPEAT block should repeat.
Returns:
repetitions=0: An empty circuit.
repetitions=1: A copy of this circuit.
repetitions>=2: A circuit with a single REPEAT block, where the contents of
that repeat block are this circuit.
Examples:
>>> import stim
>>> c = stim.Circuit('''
... X 0
... Y 1 2
... ''')
>>> 3 * c
stim.Circuit('''
REPEAT 3 {
X 0
Y 1 2
}
''')
"""
def __str__(
self,
) -> str:
"""Returns stim instructions (that can be saved to a file and parsed by stim) for the current circuit.
"""
@overload
def append(
self,
name: str,
targets: Union[int, stim.GateTarget, stim.PauliString, Iterable[Union[int, stim.GateTarget, stim.PauliString]]],
arg: Union[float, Iterable[float]],
*,
tag: str = "",
) -> None:
pass
@overload
def append(
self,
name: Union[stim.CircuitOperation, stim.CircuitRepeatBlock],
) -> None:
pass
def append(
self,
name: object,
targets: object = (),
arg: object = None,
*,
tag: str = '',
) -> None:
"""Appends an operation into the circuit.
Note: `stim.Circuit.append_operation` is an alias of `stim.Circuit.append`.
Examples:
>>> import stim
>>> c = stim.Circuit()
>>> c.append("X", 0)
>>> c.append("H", [0, 1])
>>> c.append("M", [0, stim.target_inv(1)])
>>> c.append("CNOT", [stim.target_rec(-1), 0])
>>> c.append("X_ERROR", [0], 0.125)
>>> c.append("CORRELATED_ERROR", [stim.target_x(0), stim.target_y(2)], 0.25)
>>> c.append("MPP", [stim.PauliString("X1*Y2"), stim.GateTarget("Z3")])
>>> print(repr(c))
stim.Circuit('''
X 0
H 0 1
M 0 !1
CX rec[-1] 0
X_ERROR(0.125) 0
E(0.25) X0 Y2
MPP X1*Y2 Z3
''')
Args:
name: The name of the operation's gate (e.g. "H" or "M" or "CNOT").
This argument can also be set to a `stim.CircuitInstruction` or
`stim.CircuitInstructionBlock`, which results in the instruction or
block being appended to the circuit. The other arguments (targets
and arg) can't be specified when doing so.
(The argument being called `name` is no longer quite right, but
is being kept for backwards compatibility.)
targets: The objects operated on by the gate. This can be either a
single target or an iterable of multiple targets.
Each target can be:
An int: The index of a targeted qubit.
A `stim.GateTarget`: Could be a variety of things. Methods like
`stim.target_rec`, `stim.target_sweet`, `stim.target_x`, and
`stim.CircuitInstruction.__getitem__` all return this type.
A `stim.PauliString`: This will automatically be expanded into
a product of pauli targets like `X1*Y2*Z3`.
arg: The "parens arguments" for the gate, such as the probability for a
noise operation. A double or list of doubles parameterizing the
gate. Different gates take different parens arguments. For example,
X_ERROR takes a probability, OBSERVABLE_INCLUDE takes an observable
index, and PAULI_CHANNEL_1 takes three disjoint probabilities.
Note: Defaults to no parens arguments. Except, for backwards
compatibility reasons, `cirq.append_operation` (but not
`cirq.append`) will default to a single 0.0 argument for gates that
take exactly one argument.
tag: A customizable string attached to the instruction.
"""
def append_from_stim_program_text(
self,
stim_program_text: str,
) -> None:
"""Appends operations described by a STIM format program into the circuit.
Examples:
>>> import stim
>>> c = stim.Circuit()
>>> c.append_from_stim_program_text('''
... H 0 # comment
... CNOT 0 2
...
... M 2
... CNOT rec[-1] 1
... ''')
>>> print(c)
H 0
CX 0 2
M 2
CX rec[-1] 1
Args:
stim_program_text: The STIM program text containing the circuit operations
to append.
"""
def append_operation(
self,
name: object,
targets: object = (),
arg: object = None,
*,
tag: str = '',
) -> None:
"""[DEPRECATED] use stim.Circuit.append instead
"""
def approx_equals(
self,
other: object,
*,
atol: float,
) -> bool:
"""Checks if a circuit is approximately equal to another circuit.
Two circuits are approximately equal if they are equal up to slight
perturbations of instruction arguments such as probabilities. For example,
`X_ERROR(0.100) 0` is approximately equal to `X_ERROR(0.099)` within an absolute
tolerance of 0.002. All other details of the circuits (such as the ordering of
instructions and targets) must be exactly the same.
Args:
other: The circuit, or other object, to compare to this one.
atol: The absolute error tolerance. The maximum amount each probability may
have been perturbed by.
Returns:
True if the given object is a circuit approximately equal up to the
receiving circuit up to the given tolerance, otherwise False.
Examples:
>>> import stim
>>> base = stim.Circuit('''
... X_ERROR(0.099) 0 1 2
... M 0 1 2
... ''')
>>> base.approx_equals(base, atol=0)
True
>>> base.approx_equals(stim.Circuit('''
... X_ERROR(0.101) 0 1 2
... M 0 1 2
... '''), atol=0)
False
>>> base.approx_equals(stim.Circuit('''
... X_ERROR(0.101) 0 1 2
... M 0 1 2
... '''), atol=0.0001)
False
>>> base.approx_equals(stim.Circuit('''
... X_ERROR(0.101) 0 1 2
... M 0 1 2
... '''), atol=0.01)
True
>>> base.approx_equals(stim.Circuit('''
... DEPOLARIZE1(0.099) 0 1 2
... MRX 0 1 2
... '''), atol=9999)
False
"""
def clear(
self,
) -> None:
"""Clears the contents of the circuit.
Examples:
>>> import stim
>>> c = stim.Circuit('''
... X 0
... Y 1 2
... ''')
>>> c.clear()
>>> c
stim.Circuit()
"""
def compile_detector_sampler(
self,
*,
seed: object = None,
) -> stim.CompiledDetectorSampler:
"""Returns an object that can batch sample detection events from the circuit.
Args:
seed: PARTIALLY determines simulation results by deterministically seeding
the random number generator.
Must be None or an integer in range(2**64).
Defaults to None. When None, the prng is seeded from system entropy.
When set to an integer, making the exact same series calls on the exact
same machine with the exact same version of Stim will produce the exact
same simulation results.
CAUTION: simulation results *WILL NOT* be consistent between versions of
Stim. This restriction is present to make it possible to have future
optimizations to the random sampling, and is enforced by introducing
intentional differences in the seeding strategy from version to version.
CAUTION: simulation results *MAY NOT* be consistent across machines that
differ in the width of supported SIMD instructions. For example, using
the same seed on a machine that supports AVX instructions and one that
only supports SSE instructions may produce different simulation results.
CAUTION: simulation results *MAY NOT* be consistent if you vary how many
shots are taken. For example, taking 10 shots and then 90 shots will
give different results from taking 100 shots in one call.
Examples:
>>> import stim
>>> c = stim.Circuit('''
... H 0
... CNOT 0 1
... M 0 1
... DETECTOR rec[-1] rec[-2]
... ''')
>>> s = c.compile_detector_sampler()
>>> s.sample(shots=1)
array([[False]])
"""
def compile_m2d_converter(
self,
*,
skip_reference_sample: bool = False,
) -> stim.CompiledMeasurementsToDetectionEventsConverter:
"""Creates a measurement-to-detection-event converter for the given circuit.
The converter uses a noiseless reference sample, collected from the circuit
using stim's Tableau simulator during initialization of the converter, as a
baseline for determining what the expected value of a detector is.
Note that the expected behavior of gauge detectors (detectors that are not
actually deterministic under noiseless execution) can vary depending on the
reference sample. Stim mitigates this by always generating the same reference
sample for a given circuit.
Args:
skip_reference_sample: Defaults to False. When set to True, the reference
sample used by the converter is initialized to all-zeroes instead of
being collected from the circuit. This should only be used if it's known
that the all-zeroes sample is actually a possible result from the
circuit (under noiseless execution).
Returns:
An initialized stim.CompiledMeasurementsToDetectionEventsConverter.
Examples:
>>> import stim
>>> import numpy as np
>>> converter = stim.Circuit('''
... X 0
... M 0
... DETECTOR rec[-1]
... ''').compile_m2d_converter()
>>> converter.convert(
... measurements=np.array([[0], [1]], dtype=np.bool_),
... append_observables=False,
... )
array([[ True],
[False]])
"""
def compile_sampler(
self,
*,
skip_reference_sample: bool = False,
seed: Optional[int] = None,
reference_sample: Optional[np.ndarray] = None,
) -> stim.CompiledMeasurementSampler:
"""Returns an object that can quickly batch sample measurements from the circuit.
Args:
skip_reference_sample: Defaults to False. When set to True, the reference
sample used by the sampler is initialized to all-zeroes instead of being
collected from the circuit. This means that the results returned by the
sampler are actually whether or not each measurement was *flipped*,
instead of true measurement results.
Forcing an all-zero reference sample is useful when you are only
interested in error propagation and don't want to have to deal with the
fact that some measurements want to be On when no errors occur. It is
also useful when you know for sure that the all-zero result is actually
a possible result from the circuit (under noiseless execution), meaning
it is a valid reference sample as good as any other. Computing the
reference sample is the most time consuming and memory intensive part of
simulating the circuit, so promising that the simulator can safely skip
that step is an effective optimization.
seed: PARTIALLY determines simulation results by deterministically seeding
the random number generator.
Must be None or an integer in range(2**64).
Defaults to None. When None, the prng is seeded from system entropy.
When set to an integer, making the exact same series calls on the exact
same machine with the exact same version of Stim will produce the exact
same simulation results.
CAUTION: simulation results *WILL NOT* be consistent between versions of
Stim. This restriction is present to make it possible to have future
optimizations to the random sampling, and is enforced by introducing
intentional differences in the seeding strategy from version to version.
CAUTION: simulation results *MAY NOT* be consistent across machines that
differ in the width of supported SIMD instructions. For example, using
the same seed on a machine that supports AVX instructions and one that
only supports SSE instructions may produce different simulation results.
CAUTION: simulation results *MAY NOT* be consistent if you vary how many
shots are taken. For example, taking 10 shots and then 90 shots will
give different results from taking 100 shots in one call.
reference_sample: The data to xor into the measurement flips produced by the
frame simulator, in order to produce proper measurement results.
This can either be specified as an `np.bool_` array or a bit packed
`np.uint8` array (little endian). Under normal conditions, the reference
sample should be a valid noiseless sample of the circuit, such as the
one returned by `circuit.reference_sample()`. If this argument is not
provided, the reference sample will be set to
`circuit.reference_sample()`, unless `skip_reference_sample=True`
is used, in which case it will be set to all-zeros.
Raises:
ValueError: skip_reference_sample is True and reference_sample is not None.
Examples:
>>> import stim
>>> c = stim.Circuit('''
... X 2
... M 0 1 2
... ''')
>>> s = c.compile_sampler()
>>> s.sample(shots=1)
array([[False, False, True]])
"""
def copy(
self,
) -> stim.Circuit:
"""Returns a copy of the circuit. An independent circuit with the same contents.
Examples:
>>> import stim
>>> c1 = stim.Circuit("H 0")
>>> c2 = c1.copy()
>>> c2 is c1
False
>>> c2 == c1
True
"""
def count_determined_measurements(
self,
) -> int:
"""Counts the number of predictable measurements in the circuit.
This method ignores any noise in the circuit.
This method works by performing a tableau stabilizer simulation of the circuit
and, before each measurement is simulated, checking if its expectation is
non-zero.
A measurement is predictable if its result can be predicted by using other
measurements that have already been performed, assuming the circuit is executed
without any noise.
Note that, when multiple measurements occur at the same time, re-ordering the
order they are resolved can change which specific measurements are predictable
but won't change how many of them were predictable in total.
The number of predictable measurements is a useful quantity because it's
related to the number of detectors and observables that a circuit should
declare. If circuit.num_detectors + circuit.num_observables is less than
circuit.count_determined_measurements(), this is a warning sign that you've
missed some detector declarations.
The exact relationship between the number of determined measurements and the
number of detectors and observables can differ from code to code. For example,
the toric code has an extra redundant measurement compared to the surface code
because in the toric code the last X stabilizer to be measured is equal to the
product of all other X stabilizers even in the first round when initializing in
the Z basis. Typically this relationship is not declared as a detector, because
it's not local, or as an observable, because it doesn't store a qubit.
Returns:
The number of measurements that were predictable.
Examples:
>>> import stim
>>> stim.Circuit('''
... R 0
... M 0
... ''').count_determined_measurements()
1
>>> stim.Circuit('''
... R 0
... H 0
... M 0
... ''').count_determined_measurements()
0
>>> stim.Circuit('''
... R 0 1
... MZZ 0 1
... MYY 0 1
... MXX 0 1
... ''').count_determined_measurements()
2
>>> circuit = stim.Circuit.generated(
... "surface_code:rotated_memory_x",
... distance=5,
... rounds=9,
... )
>>> circuit.count_determined_measurements()
217
>>> circuit.num_detectors + circuit.num_observables
217
"""
def decomposed(
self,
) -> stim.Circuit:
"""Recreates the circuit using (mostly) the {H,S,CX,M,R} gate set.
The intent of this method is to simplify the circuit to use fewer gate types,
so it's easier for other tools to consume. Currently, this method performs the
following simplifications:
- Single qubit cliffords are decomposed into {H,S}.
- Multi-qubit cliffords are decomposed into {H,S,CX}.
- Single qubit dissipative gates are decomposed into {H,S,M,R}.
- Multi-qubit dissipative gates are decomposed into {H,S,CX,M,R}.
Currently, the following types of gate *aren't* simplified, but they may be
in the future:
- Noise instructions (like X_ERROR, DEPOLARIZE2, and E).
- Annotations (like TICK, DETECTOR, and SHIFT_COORDS).
- The MPAD instruction.
- Repeat blocks are not flattened.
Returns:
A `stim.Circuit` whose function is equivalent to the original circuit,
but with most gates decomposed into the {H,S,CX,M,R} gate set.
Examples:
>>> import stim
>>> stim.Circuit('''
... SWAP 0 1
... ''').decomposed()
stim.Circuit('''
CX 0 1 1 0 0 1
''')
>>> stim.Circuit('''
... ISWAP 0 1 2 1
... TICK
... MPP !X1*Y2*Z3
... ''').decomposed()
stim.Circuit('''
H 0
CX 0 1 1 0
H 1
S 1 0
H 2
CX 2 1 1 2
H 1
S 1 2
TICK
H 1 2
S 2
H 2
S 2 2
CX 2 1 3 1
M !1
CX 2 1 3 1
H 2
S 2
H 2
S 2 2
H 1
''')
"""
def detecting_regions(
self,
*,
targets: Optional[Iterable[stim.DemTarget | str | Iterable[float]]] = None,
ticks: Optional[Iterable[int]] = None,
) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]:
"""Records where detectors and observables are sensitive to errors over time.
The result of this method is a nested dictionary, mapping detectors/observables
and ticks to Pauli sensitivities for that detector/observable at that time.
For example, if observable 2 has Z-type sensitivity on qubits 5 and 6 during
tick 3, then `result[stim.target_logical_observable_id(2)][3]` will be equal to
`stim.PauliString("Z5*Z6")`.
If you want sensitivities from more places in the circuit, besides just at the
TICK instructions, you can work around this by making a version of the circuit
with more TICKs.
Args:
targets: Defaults to everything (None).
When specified, this should be an iterable of filters where items
matching any one filter are included.
A variety of filters are supported:
stim.DemTarget: Includes the targeted detector or observable.
Iterable[float]: Coordinate prefix match. Includes detectors whose
coordinate data begins with the same floats.
"D": Includes all detectors.
"L": Includes all observables.
"D#" (e.g. "D5"): Includes the detector with the specified index.
"L#" (e.g. "L5"): Includes the observable with the specified index.
ticks: Defaults to everything (None).
When specified, this should be a list of integers corresponding to
the tick indices to report sensitivities for.
ignore_anticommutation_errors: Defaults to False.
When set to False, invalid detecting regions that anticommute with a
reset will cause the method to raise an exception. When set to True,
the offending component will simply be silently dropped. This can
result in broken detectors having apparently enormous detecting
regions.
Returns:
Nested dictionaries keyed first by a `stim.DemTarget` identifying the
detector or observable, then by the index of the tick, leading to a
PauliString with that target's error sensitivity at that tick.
Note you can use `stim.PauliString.pauli_indices` to quickly get to the
non-identity terms in the sensitivity.
Examples:
>>> import stim
>>> detecting_regions = stim.Circuit('''
... R 0
... TICK
... H 0
... TICK
... CX 0 1
... TICK
... MX 0 1
... DETECTOR rec[-1] rec[-2]
... ''').detecting_regions()
>>> for target, tick_regions in detecting_regions.items():
... print("target", target)
... for tick, sensitivity in tick_regions.items():
... print(" tick", tick, "=", sensitivity)
target D0
tick 0 = +Z_
tick 1 = +X_
tick 2 = +XX
>>> circuit = stim.Circuit.generated(
... "surface_code:rotated_memory_x",
... rounds=5,
... distance=4,
... )
>>> detecting_regions = circuit.detecting_regions(
... targets=["L0", (2, 4), stim.DemTarget.relative_detector_id(5)],
... ticks=range(5, 15),
... )
>>> for target, tick_regions in detecting_regions.items():
... print("target", target)
... for tick, sensitivity in tick_regions.items():
... print(" tick", tick, "=", sensitivity)
target D1
tick 5 = +____________________X______________________
tick 6 = +____________________Z______________________
target D5
tick 5 = +______X____________________________________
tick 6 = +______Z____________________________________
target D14
tick 5 = +__________X_X______XXX_____________________
tick 6 = +__________X_X______XZX_____________________
tick 7 = +__________X_X______XZX_____________________
tick 8 = +__________X_X______XXX_____________________
tick 9 = +__________XXX_____XXX______________________
tick 10 = +__________XXX_______X______________________
tick 11 = +__________X_________X______________________
tick 12 = +____________________X______________________
tick 13 = +____________________Z______________________
target D29
tick 7 = +____________________Z______________________
tick 8 = +____________________X______________________
tick 9 = +____________________XX_____________________
tick 10 = +___________________XXX_______X_____________
tick 11 = +____________X______XXXX______X_____________
tick 12 = +__________X_X______XXX_____________________
tick 13 = +__________X_X______XZX_____________________
tick 14 = +__________X_X______XZX_____________________
target D44
tick 14 = +____________________Z______________________
target L0
tick 5 = +_X________X________X________X______________
tick 6 = +_X________X________X________X______________
tick 7 = +_X________X________X________X______________
tick 8 = +_X________X________X________X______________
tick 9 = +_X________X_______XX________X______________
tick 10 = +_X________X________X________X______________
tick 11 = +_X________XX_______X________XX_____________
tick 12 = +_X________X________X________X______________
tick 13 = +_X________X________X________X______________
tick 14 = +_X________X________X________X______________
"""
def detector_error_model(
self,
*,
decompose_errors: bool = False,
flatten_loops: bool = False,
allow_gauge_detectors: bool = False,
approximate_disjoint_errors: float = False,
ignore_decomposition_failures: bool = False,
block_decomposition_from_introducing_remnant_edges: bool = False,
) -> stim.DetectorErrorModel:
"""Returns a stim.DetectorErrorModel describing the error processes in the circuit.
Args:
decompose_errors: Defaults to false. When set to true, the error analysis
attempts to decompose the components of composite error mechanisms (such
as depolarization errors) into simpler errors, and suggest this
decomposition via `stim.target_separator()` between the components. For
example, in an XZ surface code, single qubit depolarization has a Y
error term which can be decomposed into simpler X and Z error terms.
Decomposition fails (causing this method to throw) if it's not possible
to decompose large errors into simple errors that affect at most two
detectors.
flatten_loops: Defaults to false. When set to True, the output will not
contain any `repeat` blocks. When set to False, the error analysis
watches for loops in the circuit reaching a periodic steady state with
respect to the detectors being introduced, the error mechanisms that
affect them, and the locations of the logical observables. When it
identifies such a steady state, it outputs a repeat block. This is
massively more efficient than flattening for circuits that contain
loops, but creates a more complex output.
allow_gauge_detectors: Defaults to false. When set to false, the error
analysis verifies that detectors in the circuit are actually
deterministic under noiseless execution of the circuit. When set to
True, these detectors are instead considered to be part of degrees
freedom that can be removed from the error model. For example, if
detectors D1 and D3 both anti-commute with a reset, then the error model
has a gauge `error(0.5) D1 D3`. When gauges are identified, one of the
involved detectors is removed from the system using Gaussian
elimination.
Note that logical observables are still verified to be deterministic,
even if this option is set.
approximate_disjoint_errors: Defaults to false. When set to false, composite
error mechanisms with disjoint components (such as
`PAULI_CHANNEL_1(0.1, 0.2, 0.0)`) can cause the error analysis to throw
exceptions (because detector error models can only contain independent
error mechanisms). When set to true, the probabilities of the disjoint
cases are instead assumed to be independent probabilities. For example,
a `PAULI_CHANNEL_1(0.1, 0.2, 0.0)` becomes equivalent to an
`X_ERROR(0.1)` followed by a `Y_ERROR(0.2)`. This assumption is an
approximation, but it is a good approximation for small probabilities.
This argument can also be set to a probability between 0 and 1, setting
a threshold below which the approximation is acceptable. Any error
mechanisms that have a component probability above the threshold will
cause an exception to be thrown.
ignore_decomposition_failures: Defaults to False.
When this is set to True, circuit errors that fail to decompose into
graphlike detector error model errors no longer cause the conversion
process to abort. Instead, the undecomposed error is inserted into the
output. Whatever tool the detector error model is then given to is
responsible for dealing with the undecomposed errors (e.g. a tool may
choose to simply ignore them).
Irrelevant unless decompose_errors=True.
block_decomposition_from_introducing_remnant_edges: Defaults to False.
Requires that both A B and C D be present elsewhere in the detector
error model in order to decompose A B C D into A B ^ C D. Normally, only
one of A B or C D needs to appear to allow this decomposition.
Remnant edges can be a useful feature for ensuring decomposition
succeeds, but they can also reduce the effective code distance by giving
the decoder single edges that actually represent multiple errors in the
circuit (resulting in the decoder making misinformed choices when
decoding).