/
circuit.py
3156 lines (2703 loc) · 138 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
""" Defines the Circuit class """
from __future__ import division, print_function, absolute_import, unicode_literals
#***************************************************************************************************
# Copyright 2015, 2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
# Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights
# in this software.
# 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
# http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root pyGSTi directory.
#***************************************************************************************************
import numbers as _numbers
import numpy as _np
import copy as _copy
import sys as _sys
import itertools as _itertools
import warnings as _warnings
from . import labeldicts as _ld
from ..baseobjs import Label as _Label
from ..baseobjs.label import CircuitLabel as _CircuitLabel
from ..baseobjs import CircuitParser as _CircuitParser
from ..tools import internalgates as _itgs
from ..tools import compattools as _compat
from ..tools import slicetools as _slct
#Internally:
# when static: a tuple of Label objects labelling each top-level circuit layer
# when editable: a list of lists, one per top-level layer, holding just
# the non-LabelTupTup (non-compound) labels.
#Externally, we'd like to do thinks like:
# c = Circuit( LabelList )
# c.append_line("Q0")
# c.append_layer(layer_label)
# c[2]['Q0'] = 'Gx' # puts Gx:Q0 into circuit (at 3rd layer)
# c[2,'Q0'] = 'Gx'
# c[2,('Q0','Q1')] = Label('Gcnot') # puts Gcnot:Q0:Q1 into circuit
# c[2,('Q1','Q0')] = 'Gcnot' # puts Gcnot:Q1:Q0 into circuit
# c[2] = (Label('Gx','Q0'), Label('Gy','Q1')) # assigns a circuit layer
# c[2,:] = (Label('Gx','Q0'), Label('Gy','Q1')) # assigns a circuit layer
# del c[2]
# c.insert(2, (Label('Gx','Q0'), Label('Gy','Q1')) ) # inserts a layer
# c[:,'Q0'] = ('Gx','Gy','','Gx') # assigns the Q0 line
# c[1:3,'Q0'] = ('Gx','Gy') # assigns to a part of the Q0 line
def _label_to_nested_lists_of_simple_labels(lbl, default_sslbls=None, always_return_list=True):
""" Convert lbl into nested lists of *simple* labels """
if not isinstance(lbl, _Label): # if not a Label, make into a label,
lbl = _Label(lbl) # e.g. a string or list/tuple of labels, etc.
if lbl.issimple(): # a *simple* label - the elements of our lists
if lbl.sslbls is None and default_sslbls is not None:
lbl = _Label(lbl.name, default_sslbls)
return [lbl] if always_return_list else lbl
return [_label_to_nested_lists_of_simple_labels(l, default_sslbls, False)
for l in lbl.components] # a *list*
def _sslbls_of_nested_lists_of_simple_labels(obj, labels_to_ignore=None):
""" Get state space labels from a nested lists of simple (not compound) Labels. """
if isinstance(obj, _Label):
if labels_to_ignore and (obj in labels_to_ignore):
return ()
return obj.sslbls
else:
sub_sslbls = [_sslbls_of_nested_lists_of_simple_labels(sub, labels_to_ignore) for sub in obj]
return None if (None in sub_sslbls) else set(_itertools.chain(*sub_sslbls))
def _accumulate_explicit_sslbls(obj):
"""
Get all the explicitly given state-space labels within `obj`,
which can be a Label or a list/tuple of labels. Returns a *set*.
"""
ret = set()
if isinstance(obj, _Label):
if not obj.issimple():
for lbl in obj.components:
ret.update(_accumulate_explicit_sslbls(lbl))
else: # a simple label
if obj.sslbls is not None: # don't know how to interpet None sslbls
return set(obj.sslbls)
else: # things that aren't labels we assume are iterable
for lbl in obj:
ret.update(_accumulate_explicit_sslbls(lbl))
return ret
def _opSeqToStr(seq, line_labels):
""" Used for creating default string representations. """
if len(seq) == 0: return "{}" # special case of empty operation sequence
def process_lists(el): return el if not isinstance(el, list) else \
('[%s]' % ''.join(map(str, el)) if (len(el) != 1) else str(el[0]))
if line_labels is None or line_labels == ('*',):
return ''.join(map(str, map(process_lists, seq)))
else:
return ''.join(map(str, map(process_lists, seq))) \
+ "@(" + ','.join(map(str, line_labels)) + ")"
def toLabel(x):
""" Helper function for converting `x` to a single Label object """
if isinstance(x, _Label): return x
# # do this manually when desired, as it "boxes" a circuit being inserted
#elif isinstance(x,Circuit): return x.to_circuit_label()
else: return _Label(x)
class Circuit(object):
"""
A Circuit represents a quantum circuit, consisting of state preparation,
gates, and measurement operations. It is composed of some number of "lines",
typically one per qubit, and stores the operations on these lines as a
sequence of :class:`Label` objects, one per circuit layer, whose `.sslbls`
members indicate which line(s) the label belongs on. When a circuit is
created with 'editable=True', a rich set of operations may be used to
construct the circuit in place, after which `done_editing()` should be
called so that the Circuit can be properly hashed as needed.
"""
default_expand_subcircuits = True
def __init__(self, layer_labels=(), line_labels='auto', num_lines=None, editable=False,
stringrep=None, name='', check=True, expand_subcircuits="default"):
"""
Creates a new Circuit object, encapsulating a quantum circuit.
You only need to supply the first `layer_labels` argument, though
usually (except for just 1 or 2 qubits) you'll want to also supply
`line_labels` or `num_lines`. If you'll be adding to or altering
the circuit before using it, you should set `editable=True`.
Parameters
----------
layer_labels : iterable of Labels or str
This argument provides a list of the layer labels specifying the
state preparations, gates, and measurements for the circuit. This
argument can also be a :class:`Circuit` or a string, in which case
it is parsed as a text-formatted circuit. Internally this will
eventually be converted to a list of `Label` objects, one per layer,
but it may be specified using anything that can be readily converted
to a Label objects. For example, any of the following are allowed:
- `['Gx','Gx']` : X gate on each of 2 layers
- `[Label('Gx'),Label('Gx')] : same as above
- `[('Gx',0),('Gy',0)]` : X then Y on qubit 0 (2 layers)
- `[[('Gx',0),('Gx',1)],[('Gy',0),('Gy',1)]]` : parallel X then Y on qubits 0 & 1
line_labels : iterable, optional
The (string valued) label for each circuit line. If `'auto'`, then
`line_labels` is taken to be the list of all state-space labels
present within `layer_labels`. If there are no such labels (e.g.
if `layer_labels` contains just gate names like `('Gx','Gy')`), then
the special value `'*'` is used as a single line label.
num_lines : int, optional
Specify this instead of `line_labels` to set the latter to the
integers between 0 and `num_lines-1`.
editable : bool, optional
Whether the created `Circuit` is created in able to be modified. If
`True`, then you should call `done_editing()` once the circuit is
completely assembled, as this makes the circuit read-only and
allows it to be hashed.
stringrep : string, optional
A string representation for the circuit. If `None` (the default),
then this will be generated automatically when needed. One
reason you'd want to specify this is if you know of a nice compact
string representation that you'd rather use, e.g. `"Gx^4"` instead
of the automatically generated `"GxGxGxGx"`. If you want to
initialize a `Circuit` entirely from a string representation you
can either specify the string in as `layer_labels` or set
`layer_labels` to `None` and `stringrep` to any valid (one-line)
circuit string.
name : str, optional
A name for this circuit (useful if/when used as a block within
larger circuits).
check : bool, optional
Whether `stringrep` should be checked against `layer_labels` to
ensure they are consistent, and whether the labels in `layer_labels`
are a subset of `line_labels`. The only reason you'd want to set
this to `False` is if you're absolutely sure `stringrep` and
`line_labels` are consistent and want to save computation time.
expand_subcircuits : bool or "default"
If `"default"`, then the value of `Circuit.default_expand_subcircuits`
is used. If True, then any sub-circuits (e.g. anything exponentiated
like "(GxGy)^4") will be expanded when it is stored within the created
Circuit. If False, then such sub-circuits will be left as-is. It's
typically more robust to expand sub-circuits as this facilitates
comparison (e.g. so "GxGx" == "Gx^2"), but in cases when you have
massive exponents (e.g. "Gx^8192") it may improve performance to
set `expand_subcircuits=False`.
"""
layer_labels_objs = None # layer_labels elements as Label objects (only if needed)
if _compat.isstr(layer_labels):
cparser = _CircuitParser(); cparser.lookup = None
layer_labels, chk_labels = cparser.parse(layer_labels)
if chk_labels is not None:
if line_labels == 'auto':
line_labels = chk_labels
elif tuple(line_labels) != chk_labels:
raise ValueError(("Error intializing Circuit: "
" `line_labels` and line labels in `layer_labels` do not match: %s != %s")
% (line_labels, chk_labels))
if expand_subcircuits == "default":
expand_subcircuits = Circuit.default_expand_subcircuits
if expand_subcircuits and layer_labels is not None:
layer_labels_objs = tuple(_itertools.chain(*[x.expand_subcircuits() for x in map(toLabel, layer_labels)]))
#print("DB: Layer labels = ",layer_labels_objs)
#Parse stringrep if needed
if stringrep is not None and (layer_labels is None or check):
cparser = _CircuitParser()
cparser.lookup = None # lookup - functionality removed as it wasn't used
chk, chk_labels = cparser.parse(stringrep) # tuple of Labels
if expand_subcircuits and chk is not None:
chk = tuple(_itertools.chain(*[x.expand_subcircuits() for x in map(toLabel, chk)]))
#print("DB: Check Layer labels = ",chk)
if layer_labels is None:
layer_labels = chk
else: # check == True
if layer_labels_objs is None:
layer_labels_objs = tuple(map(toLabel, layer_labels))
if layer_labels_objs != tuple(chk):
#print("DB: ",layer_labels_objs,"VS",tuple(chk))
raise ValueError(("Error intializing Circuit: "
" `layer_labels` and `stringrep` do not match: %s != %s\n"
"(set `layer_labels` to None to infer it from `stringrep`)")
% (layer_labels, stringrep))
if chk_labels is not None:
if line_labels == 'auto':
line_labels = chk_labels
elif tuple(line_labels) != chk_labels:
raise ValueError(("Error intializing Circuit: "
" `line_labels` and `stringrep` do not match: %s != %s (from %s)\n"
"(set `line_labels` to None to infer it from `stringrep`)")
% (line_labels, chk_labels, stringrep))
if layer_labels is None:
raise ValueError("Must specify `stringrep` when `layer_labels` is None")
# Set self._line_labels
if line_labels == 'auto':
if layer_labels_objs is None:
layer_labels_objs = tuple(map(toLabel, layer_labels))
explicit_lbls = _accumulate_explicit_sslbls(layer_labels_objs)
if len(explicit_lbls) == 0:
if num_lines is not None:
assert(num_lines >= 0), "`num_lines` must be >= 0!"
if len(layer_labels) > 0:
assert(num_lines > 0), "`num_lines` must be > 0!"
self._line_labels = tuple(range(num_lines))
elif len(layer_labels) > 0:
self._line_labels = ('*',) # special single line-label when no line labels are given
else:
self._line_labels = () # empty circuit can have zero line labels
else:
self._line_labels = tuple(sorted(explicit_lbls))
else:
explicit_lbls = None
self._line_labels = tuple(line_labels)
if (num_lines is not None) and (num_lines != len(self.line_labels)):
if num_lines > len(self.line_labels) and \
set(self.line_labels).issubset(set(range(num_lines))):
# special case where we just add missing integer-labeled line(s)
self._line_labels = tuple(range(num_lines))
else:
raise ValueError("`num_lines` was expected to be %d but equals %d!" %
(len(self.line_labels), num_lines))
if check:
if explicit_lbls is None:
if layer_labels_objs is None:
layer_labels_objs = tuple(map(toLabel, layer_labels))
explicit_lbls = _accumulate_explicit_sslbls(layer_labels_objs)
if not set(explicit_lbls).issubset(self.line_labels):
raise ValueError("line labels must contain at least %s" % str(explicit_lbls))
#Set self._labels, which is either a nested list of simple labels (non-static case)
# or a tuple of Label objects (static case)
if not editable:
if layer_labels_objs is None:
layer_labels_objs = tuple(map(toLabel, layer_labels))
self._labels = layer_labels_objs
else:
self._labels = [_label_to_nested_lists_of_simple_labels(layer_lbl)
for layer_lbl in layer_labels]
#Set self._static, _reps, _name
self._static = not editable
#self._reps = reps # repetitions: default=1, which remains unless we initialize from a CircuitLabel...
self._name = name # can be None
self._str = stringrep if self._static else None # can be None (lazy generation)
self._times = None # for FUTURE expansion
self.auxinfo = {} # for FUTURE expansion / user metadata
# # Special case: layer_labels can be a single CircuitLabel or Circuit
# # (Note: a Circuit would work just fine, as a list of layers, but this performs some extra checks)
# isCircuit = isinstance(layer_labels, _Circuit)
# isCircuitLabel = isinstance(layer_labels, _CircuitLabel)
# if isCircuitLabel:
# assert(line_labels is None or line_labels == "auto" or line_labels == expected_line_labels), \
# "Given `line_labels` (%s) are inconsistent with CircuitLabel's sslbls (%s)" \
# % (str(line_labels),str(layer_labels.sslbls))
# assert(num_lines is None or layer_labels.sslbls == tuple(range(num_lines))), \
# "Given `num_lines` (%d) is inconsistend with CircuitLabel's sslbls (%s)" \
# % (num_lines,str(layer_labels.sslbls))
# if name is None: name = layer_labels.name # Note: `name` can be used to rename a CircuitLabel
# self._line_labels = layer_labels.sslbls
# self._reps = layer_labels.reps
# self._name = name
# self._static = not editable
def as_label(self, nreps=1):
"""
Construct and return this entire circuit as a :class:`CircuitLabel`.
Parameters
----------
nreps : int, optional
The number of times this circuit will be repeated (`CircuitLabels`
support exponentiation and you can specify this here).
Returns
-------
CircuitLabel
"""
eff_line_labels = None if self._line_labels == ('*',) else self._line_labels # special case
return _CircuitLabel(self._name, self._labels, eff_line_labels, nreps)
@property
def line_labels(self):
return self._line_labels
@line_labels.setter
def line_labels(self, value):
if value == self._line_labels: return
#added_line_labels = set(value) - set(self._line_labels) # it's always OK to add lines
removed_line_labels = set(self._line_labels) - set(value)
if removed_line_labels:
idling_line_labels = set(self.get_idling_lines())
removed_not_idling = removed_line_labels - idling_line_labels
if removed_not_idling and self._static:
raise ValueError("Cannot remove non-idling lines %s from a read-only circuit!" %
str(removed_not_idling))
else:
self.delete_lines(tuple(removed_not_idling))
self._line_labels = value
@property
def name(self):
return self._name
#TODO REMOVE
#@property
#def reps(self):
# return self._reps
@property
def tup(self):
""" This Circuit as a standard Python tuple of layer Labels."""
if self._static:
return self._labels
else:
return tuple([toLabel(layer_lbl) for layer_lbl in self._labels])
@property
def str(self):
""" The Python string representation of this Circuit."""
if self._str is None:
generated_str = _opSeqToStr(self._labels, self.line_labels) # lazy generation
if self._static: # if we're read-only then cache the string one and for all,
self._str = generated_str # otherwise keep generating it as needed (unless it's set by the user?)
return generated_str
else:
return self._str
def _labels_lines_str(self):
""" Split the string representation up into layer-labels & line-labels parts """
if '@' in self.str:
return self.str.split('@')
else:
return self.str, None
@str.setter
def str(self, value):
""" The Python string representation of this Circuit."""
assert(not self._static), \
("Cannot edit a read-only circuit! "
"Set editable=True when calling pygsti.obj.Circuit to create editable circuit.")
cparser = _CircuitParser()
chk, chk_labels = cparser.parse(value)
if not all([my_layer in (chk_lbl, [chk_lbl]) for chk_lbl, my_layer in zip(chk, self._labels)]):
raise ValueError(("Cannot set .str to %s because it doesn't"
" evaluate to %s (this circuit)") %
(value, self.str))
if chk_labels is not None:
if tuple(self.line_labels) != chk_labels:
raise ValueError(("Cannot set .str to %s because line labels evaluate to"
" %s which is != this circuit's line labels (%s).") %
(value, chk_labels, str(self.line_labels)))
self._str = value
def __hash__(self):
if not self._static:
_warnings.warn(("Editable circuit is being converted to read-only"
" mode in order to hash it. You should call"
" circuit.done_editing() beforehand."))
self.done_editing()
return hash(self._labels) # just hash the tuple of labels
def __len__(self):
return len(self._labels)
def __iter__(self):
return self._labels.__iter__()
def __contains__(self, x):
"""Note: this is not covered by __iter__ for case of contained CircuitLabels """
return any([(x == layer or x in layer) for layer in self._labels])
def __add__(self, x):
if not isinstance(x, Circuit):
raise ValueError("Can only add Circuits objects to other Circuit objects")
if self.str is None or x.str is None:
s = None
else:
mystr, _ = self._labels_lines_str()
xstr, _ = x._labels_lines_str()
if mystr != "{}":
s = (mystr + xstr) if xstr != "{}" else mystr
else: s = xstr
editable = not self._static or not x._static
added_labels = tuple([l for l in x.line_labels if l not in self.line_labels])
new_line_labels = self.line_labels + added_labels
if new_line_labels != ('*',):
s += "@(" + ','.join(map(str, new_line_labels)) + ")" # matches to _opSeqToStr in circuit.py!
return Circuit(self.tup + x.tup, new_line_labels,
None, editable, s, check=False)
def repeat(self, ntimes, expand="default"):
if expand == "default": expand = Circuit.default_expand_subcircuits
assert((_compat.isint(ntimes) or _np.issubdtype(ntimes, int)) and ntimes >= 0)
mystr, mylines = self._labels_lines_str()
if ntimes > 1: s = "(%s)^%d" % (mystr, ntimes)
elif ntimes == 1: s = "(%s)" % mystr
else: s = "{}"
if mylines is not None:
s += "@" + mylines # add line labels
if ntimes > 1 and expand is False:
reppedCircuitLbl = self.as_label(nreps=ntimes)
return Circuit((reppedCircuitLbl,), self.line_labels, None, not self._static, s, check=False)
else:
# just adds parens to string rep & copies
return Circuit(self.tup * ntimes, self.line_labels, None, not self._static, s, check=False)
def __mul__(self, x):
return self.repeat(x)
def __pow__(self, x): # same as __mul__()
return self.__mul__(x)
def __eq__(self, x):
if x is None: return False
xtup = x.tup if isinstance(x, Circuit) else tuple(x)
return self.tup == xtup # better than x.tup since x can be a tuple
def __lt__(self, x):
return self.tup.__lt__(tuple(x))
def __gt__(self, x):
return self.tup.__gt__(tuple(x))
def number_of_lines(self):
"""
The number of lines in this circuit.
Returns
-------
int
"""
return len(self.line_labels)
def copy(self, editable="auto"):
"""
Returns a copy of the circuit.
Parameters
----------
editable : {True,False,"auto"}
Whether returned copy is editable. If `"auto"` is given,
then the copy is editable if and only if this Circuit is.
Returns
-------
Circuit
"""
if editable == "auto": editable = not self._static
return Circuit(self.tup, self.line_labels, None, editable, self._str, check=False)
def clear(self):
"""
Removes all the gates in a circuit (preserving the number of lines).
"""
assert(not self._static), "Cannot edit a read-only circuit!"
self._labels = []
def _proc_layers_arg(self, layers):
""" Pre-process the layers argument used by many methods """
if layers is None:
layers = list(range(len(self._labels)))
elif isinstance(layers, slice):
if layers.start is None and layers.stop is None:
layers = ()
else:
layers = _slct.indices(layers, len(self._labels))
elif not isinstance(layers, (list, tuple)):
layers = (layers,)
return layers
def _proc_lines_arg(self, lines):
""" Pre-process the lines argument used by many methods """
if lines is None:
lines = self.line_labels
elif isinstance(lines, slice):
if lines.start is None and lines.stop is None:
lines = ()
else:
lines = _slct.indices(lines)
elif not isinstance(lines, (list, tuple)):
lines = (lines,)
return lines
def _proc_key_arg(self, key):
""" Pre-process the key argument used by many methods """
if isinstance(key, tuple):
if len(key) != 2: return IndexError("Index must be of the form <layerIndex>,<lineIndex>")
layers = key[0]
lines = key[1]
else:
layers = key
lines = None
return layers, lines
def _layer_components(self, ilayer):
""" Get the components of the `ilayer`-th layer as a list/tuple. """
#(works for static and non-static Circuits)
if self._static:
if self._labels[ilayer].issimple(): return [self._labels[ilayer]]
else: return self._labels[ilayer].components
else:
return self._labels[ilayer] if isinstance(self._labels[ilayer], list) \
else [self._labels[ilayer]]
def _remove_layer_component(self, ilayer, indx):
""" Removes the `indx`-th component from the `ilayer`-th layer """
#(works for special case when layer is just a *single* component)
assert(not self._static), "Cannot edit a read-only circuit!"
if isinstance(self._labels[ilayer], list):
del self._labels[ilayer][indx]
else:
assert(indx == 0), "Only index 0 exists for a single-simple-Label level"
# don't remove *layer* - when final component is removed we're left with an empty layer
self._labels[ilayer] = []
def _append_layer_component(self, ilayer, val):
""" Add `val` to the `ilayer`-th layer """
#(works for special case when layer is just a *single* component)
assert(not self._static), "Cannot edit a read-only circuit!"
if isinstance(self._labels[ilayer], list):
self._labels[ilayer].append(val)
else: # currently ilayer-th layer is a single component!
self._labels[ilayer] = [self._labels[ilayer], val]
def _replace_layer_component(self, ilayer, indx, val):
assert(not self._static), "Cannot edit a read-only circuit!"
""" Replace `indx`-th component of `ilayer`-th layer with `val` """
#(works for special case when layer is just a *single* component)
if isinstance(self._labels[ilayer], list):
self._labels[ilayer][indx] = val
else:
assert(indx == 0), "Only index 0 exists for a single-simple-Label level"
self._labels[ilayer] = val
def get_labels(self, layers=None, lines=None, strict=True):
"""
Get a subregion - a "rectangle" - of this Circuit.
This can be used to select multiple layers and/or lines of this Circuit.
The `strict` argument controls whether gates need to be entirely within
the given rectangle or can be intersecting it. If `layers` is a single
integer then a :class:`Label` is returned (representing a layer or a
part of a layer), otherwise a :class:`Circuit` is returned.
Parameters
----------
layers : int, slice, or list/tuple of ints
Which layers to select (the horizontal dimension of the selection
rectangle). Layers are always selected by index, and this
argument can be a single (integer) index - in which case a `Label`
is returned - or multiple indices as given by a slice or list -
in which case a `Circuit` is returned. Note that, even though
we speak of a "rectangle", layer indices do not need to be
contiguous. The special value `None` selects all layers.
lines : str/int, slice, or list/tuple of strs/ints
Which lines to select (the vertical dimension of the selection
rectangle). Lines are selected by their line-labels (elements
of the circuit's `.line_labels` property), which can be strings
and/or integers. A single or multiple line-labels can be
specified. If the line labels are integers a slice can be used,
otherwise a list or tuple of labels is the only way to select
multiple of them. Note that line-labels do not need to be
contiguous. The special value `None` selects all lines.
strict : bool, optional
When `True`, only gates lying completely within the selected
region are included in the return value. If a gate straddles
the region boundary (e.g. if we select just line `1` and the
circuit contains `"Gcnot:1:2"`) then it is *silently* not-included
in the returned label or circuit. If `False`, then gates which
straddle the region boundary *are* included. Note that this may
result in a `Label` or `Circuit` containing more line labels than
where requested in the call to `get_labels(...)`..
Returns
-------
Label or Circuit
The requested portion of this circuit, given as a `Label` if
`layers` is a single integer and as a `Circuit` otherwise.
Note: if you want a `Circuit` when only selecting one layer,
set `layers` to a slice or tuple containing just a single index.
"""
nonint_layers = not isinstance(layers, int)
layers = self._proc_layers_arg(layers)
lines = self._proc_lines_arg(lines)
if len(layers) == 0 or len(lines) == 0:
return Circuit((), lines, None, not self._static, stringrep=None, check=False) \
if nonint_layers else None # zero-area region
ret = []
if self._static:
def get_sslbls(lbl): return lbl.sslbls
else:
get_sslbls = _sslbls_of_nested_lists_of_simple_labels
for i in layers:
ret_layer = []
for l in self._layer_components(i): # loop over labels in this layer
sslbls = get_sslbls(l)
if sslbls is None:
## add in special case of identity layer
#if (isinstance(l,_Label) and l.name == self.identity): # ~ is_identity_layer(l)
# ret_layer.append(l); continue
sslbls = set(self.line_labels) # otherwise, treat None sslbs as *all* labels
else:
sslbls = set(sslbls)
if (strict and sslbls.issubset(lines)) or \
(not strict and len(sslbls.intersection(lines)) >= 0):
ret_layer.append(l)
ret.append(ret_layer)
if nonint_layers:
if not strict: lines = "auto" # since we may have included lbls on other lines
# don't worry about string rep for now...
return Circuit(ret, lines, None, not self._static, stringrep=None, check=False)
else:
return _Label(ret[0])
def set_labels(self, lbls, layers=None, lines=None):
"""
Write `lbls`, which can be anything that can be interpreted as a
:class:`Label` or list of labels to the block defined by the
`layers` and `lines` arguments.
Parameters
----------
lbls : Label, list/tuple of Labels, or Circuit
When `layers` is a single integer, `lbls` should be a single
"layer label" of type `Label`. Otherwise, `lbls` should be
a list or tuple of `Label` objects with length equal to the
number of layers being set. A `Circuit` may also be used in this
case.
layers : int, slice, or list/tuple of ints
Which layers to set (the horizontal dimension of the destination
rectangle). Layers are always selected by index, and this
argument can be a single (integer) index or multiple indices as
given by a slice or list. Note that these indices do not need to be
contiguous. The special value `None` stands for all layers.
lines : str/int, slice, or list/tuple of strs/ints
Which lines to set (the vertical dimension of the destination
rectangle). Lines are selected by their line-labels, which can be
strings and/or integers. A single or multiple line-labels can be
specified. If the line labels are integers a slice can be used,
otherwise a list or tuple of labels is the only way to specify
multiple of them. The line-labels do not need to be contiguous.
The special value `None` stands for all lines, and in this case
new lines will be created if there are new state-space labels
in `lbls` (when `lines` is not `None` an error is raised instead).
Returns
-------
None
"""
assert(not self._static), "Cannot edit a read-only circuit!"
#Note: this means self._labels contains nested lists of simple labels
#Convert layers to a list/tuple of layer indices
all_layers = bool(layers is None) # whether we're assigning to *all* layers
int_layers = isinstance(layers, int)
layers = self._proc_layers_arg(layers)
#Convert lines to a list/tuple of line (state space) labels
all_lines = bool(lines is None) # whether we're assigning to *all* lines
lines = self._proc_lines_arg(lines)
#make lbls into either:
# 1) a single Label (possibly compound) if layers is an int
# 2) a tuple of Labels (possibly compound) otherwise
if int_layers:
if isinstance(lbls, Circuit): # special case: "box" a circuit assigned to a single layer
lbls = lbls.as_label() # converts Circuit => CircuitLabel
lbls = toLabel(lbls)
lbls_sslbls = None if (lbls.sslbls is None) else set(lbls.sslbls)
else:
if isinstance(lbls, Circuit):
lbls = lbls.tup # circuit layer labels as a tuple
assert(isinstance(lbls, (tuple, list))), \
("When assigning to a layer range (even w/len=1) `lbls` "
"must be a *list or tuple* of label-like items")
lbls = tuple(map(toLabel, lbls))
lbls_sslbls = None if any([l.sslbls is None for l in lbls]) \
else set(_itertools.chain(*[l.sslbls for l in lbls]))
if len(layers) == 0 or len(lines) == 0: return # zero-area block
#If we're assigning to multiple layers, then divide up lbls into pieces to place in each layer
if all_layers: # then we'll add new layers as needed
while len(lbls) > len(self._labels):
self._labels.append([])
elif len(layers) > 1:
assert(len(layers) == len(lbls)), \
"Block width mismatch: assigning %d layers to %d layers" % (len(lbls), len(layers))
# when processing `lbls`: if a label has sslbls == None, then applies to all
# the lines being assigned. If sslbl != None, then the labels must be
# contained within the line labels being assigned (unless we're allowed to expand)
if lbls_sslbls is not None:
new_line_labels = set(lbls_sslbls) - set(self.line_labels)
if all_lines: # then allow new lines to be added
if len(new_line_labels) > 0:
self._line_labels = self.line_labels + tuple(sorted(new_line_labels)) # sort?
else:
assert(len(new_line_labels) == 0), "Cannot add new lines %s" % str(new_line_labels)
assert(set(lbls_sslbls).issubset(lines)), \
"Unallowed state space labels: %s" % str(set(lbls_sslbls) - set(lines))
assert(set(lines).issubset(self.line_labels)), \
("Specified lines (%s) must be a subset of this circuit's lines"
" (%s).") % (str(lines), str(self.line_labels))
#remove all labels in block to be assigned
self._clear_labels(layers, lines)
def_sslbls = None if all_lines else lines
if not int_layers:
for i, lbls_comp in zip(layers, lbls):
self._labels[i].extend(_label_to_nested_lists_of_simple_labels(lbls_comp, def_sslbls))
else: # single layer using integer layer index (so lbls is a single Label)
self._labels[layers[0]].extend(_label_to_nested_lists_of_simple_labels(lbls, def_sslbls))
def insert_idling_layers(self, insertBefore, numToInsert, lines=None):
"""
Inserts into this circuit one or more idling (blank) layers.
By default, complete layer(s) are inserted. The `lines` argument
allows you to insert partial layers (on only a subset of the lines).
Parameters
----------
insertBefore : int
The layer index to insert the new layers before. Can be from 0
(insert at the beginning) to `len(self)-1` (insert at end), and
negative indexing can be used to insert relative to the last layer.
The special value `None` inserts at the end.
numToInsert : int
The number of new layers to insert.
lines : str/int, slice, or list/tuple of strs/ints, optional
Which lines should have new layers (blank circuit space)
inserted into them. A single or multiple line-labels can be
specified, similarly as in :method:`get_labels`. The default
value `None` stands for *all* lines.
Returns
-------
None
"""
assert(not self._static), "Cannot edit a read-only circuit!"
if insertBefore is None: insertBefore = len(self._labels)
elif insertBefore < 0: insertBefore = len(self._labels) + insertBefore
if lines is None: # insert complete layers
for i in range(numToInsert):
self._labels.insert(insertBefore, [])
else: # insert layers only on given lines - shift existing labels to right
for i in range(numToInsert):
self._labels.append([]) # add blank layers at end
for i in range(insertBefore, insertBefore + numToInsert):
# move labels on `lines` to layer i+numToInsert
inds_to_delete = []
for k, lbl in enumerate(self._labels[i]):
sslbls = _sslbls_of_nested_lists_of_simple_labels(lbl)
if len(sslbls.intersection(lines)) > 0: # then we need to move this label
if not sslbls.issubset(lines):
raise ValueError("Cannot shift a block that is straddled by %s!" % _Label(lbl))
#FUTURE: recover from this error gracefully so we don't leave the circuit in an intermediate
#state?
inds_to_delete.append(k) # remove it from current layer
self._labels[i + numToInsert].append(lbl) # and put it in the destination layer
for k in reversed(inds_to_delete):
del self._labels[i][k]
def append_idling_layers(self, numToInsert, lines=None):
"""
Adds one or more idling (blank) layers to the end of this circuit.
By default, complete layer(s) are appended. The `lines` argument
allows you to add partial layers (on only a subset of the lines).
Parameters
----------
numToInsert : int
The number of new layers to append.
lines : str/int, slice, or list/tuple of strs/ints, optional
Which lines should have new layers (blank circuit space)
inserted into them. A single or multiple line-labels can be
specified, similarly as in :method:`get_labels`. The default
value `None` stands for *all* lines.
Returns
-------
None
"""
self.insert_idling_layers(None, numToInsert, lines)
def insert_labels_into_layers(self, lbls, layerToInsertBefore, lines=None):
"""
Inserts into this circuit the contents of `lbls` into new full or
partial layers.
By default, complete layer(s) are inserted. The `lines` argument
allows you to insert partial layers (on only a subset of the lines).
Parameters
----------
lbls : list/tuple of Labels, or Circuit
The full or partial layer labels to insert. The length of this
list, tuple, or circuit determines the number of layers which are
inserted.
layerToInsertBefore : int
The layer index to insert `lbls` before. Can be from 0
(insert at the beginning) to `len(self)-1` (insert at end), and
negative indexing can be used to insert relative to the last layer.
The special value `None` inserts at the end.
lines : str/int, slice, or list/tuple of strs/ints, optional
Which lines should have `lbls` inserted into them. Currently
this can only be a larger set than the set of line labels present
in `lbls` (in future versions this may allow filtering of `lbls`).
value `None` stands for *all* lines.
Returns
-------
None
"""
if isinstance(lbls, Circuit): lbls = lbls.tup
# lbls is expected to be a list/tuple of Label-like items, one per inserted layer
lbls = tuple(map(toLabel, lbls))
numLayersToInsert = len(lbls)
self.insert_idling_layers(layerToInsertBefore, numLayersToInsert, lines) # make space
self.set_labels(lbls, slice(layerToInsertBefore, layerToInsertBefore + numLayersToInsert), lines)
#Note: set_labels expects lbls to be a list/tuple of Label-like items b/c it's given a layer *slice*
def insert_idling_lines(self, insertBefore, line_labels):
"""
Insert one or more idling (blank) lines into this circuit.
Parameters
----------
insertBefore : str or int
The line label to insert new lines before. The special value `None`
inserts lines at the bottom of this circuit.
line_labels : list or tuple
A list or tuple of the new line labels to insert (can be integers
and/or strings).
Returns
-------
None
"""
#assert(not self._static),"Cannot edit a read-only circuit!"
# Actually, this is OK even for static circuits because it won't affect the hashed value (labels only)
if insertBefore is None:
i = len(self.line_labels)
else:
i = self.line_labels.index(insertBefore)
self._line_labels = self.line_labels[0:i] + tuple(line_labels) + self.line_labels[i:]
def append_idling_lines(self, line_labels):
"""
Add one or more idling (blank) lines onto the bottom of this circuit.
Parameters
----------
line_labels : list or tuple
A list or tuple of the new line labels to insert (can be integers
and/or strings).
Returns
-------
None
"""
self.insert_idling_lines(None, line_labels)
def insert_labels_as_lines(self, lbls, layerToInsertBefore=None, lineToInsertBefore=None, line_labels="auto"):
"""
Inserts into this circuit the contents of `lbls` into new lines.
By default, `lbls` is inserted at the beginning of the new lines(s). The
`layerToInsertBefore` argument allows you to insert `lbls` beginning at
a layer of your choice.
Parameters
----------
lbls : list/tuple of Labels, or Circuit
A list of layer labels to insert as new lines. The state-space
(line) labels within `lbls` must not overlap with that of this
circuit or an error is raised. If `lbls` contains more layers
than this circuit currently has, new layers are added automatically.
layerToInsertBefore : int
The layer index to insert `lbls` before. Can be from 0
(insert at the beginning) to `len(self)-1` (insert at end), and
negative indexing can be used to insert relative to the last layer.
The default value of `None` inserts at the beginning.
lineToInsertBefore : str or int
The line label to insert the new lines before. The default value
of `None` inserts lines at the bottom of the circuit.
line_labels : list, tuple, or "auto"
The labels of the new lines being inserted. If `"auto"`, then
these are inferred from `lbls`.
Returns
-------
None
"""
if layerToInsertBefore is None: layerToInsertBefore = 0
elif layerToInsertBefore < 0: layerToInsertBefore = len(self._labels) + layerToInsertBefore
if isinstance(lbls, Circuit):
if line_labels == "auto": line_labels = lbls.line_labels
lbls = lbls.tup
elif line_labels == "auto":
line_labels = tuple(sorted(_accumulate_explicit_sslbls(lbls)))
existing_labels = set(line_labels).intersection(self.line_labels)
if len(existing_labels) > 0:
raise ValueError("Cannot insert line(s) labeled %s - they already exist!" % str(existing_labels))
self.insert_idling_lines(lineToInsertBefore, line_labels)
#add additional layers to end of circuit if new lines are longer than current circuit depth
numLayersToInsert = len(lbls)
if layerToInsertBefore + numLayersToInsert > len(self._labels):
self.append_idling_layers(layerToInsertBefore + numLayersToInsert - len(self._labels))
#Note: set_labels expects lbls to be a list/tuple of Label-like items b/c it's given a layer *slice*
self.set_labels(lbls, slice(layerToInsertBefore, layerToInsertBefore + numLayersToInsert), line_labels)