pyGSTi 0.9.12.1__cp39-cp39-win32.whl → 0.9.13__cp39-cp39-win32.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. pyGSTi-0.9.13.dist-info/METADATA +197 -0
  2. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/RECORD +207 -217
  3. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/WHEEL +1 -1
  4. pygsti/_version.py +2 -2
  5. pygsti/algorithms/contract.py +1 -1
  6. pygsti/algorithms/core.py +42 -28
  7. pygsti/algorithms/fiducialselection.py +17 -8
  8. pygsti/algorithms/gaugeopt.py +2 -2
  9. pygsti/algorithms/germselection.py +87 -77
  10. pygsti/algorithms/mirroring.py +0 -388
  11. pygsti/algorithms/randomcircuit.py +165 -1333
  12. pygsti/algorithms/rbfit.py +0 -234
  13. pygsti/baseobjs/basis.py +94 -396
  14. pygsti/baseobjs/errorgenbasis.py +0 -132
  15. pygsti/baseobjs/errorgenspace.py +0 -10
  16. pygsti/baseobjs/label.py +52 -168
  17. pygsti/baseobjs/opcalc/fastopcalc.cp39-win32.pyd +0 -0
  18. pygsti/baseobjs/opcalc/fastopcalc.pyx +2 -2
  19. pygsti/baseobjs/polynomial.py +13 -595
  20. pygsti/baseobjs/statespace.py +1 -0
  21. pygsti/circuits/__init__.py +1 -1
  22. pygsti/circuits/circuit.py +682 -505
  23. pygsti/circuits/circuitconstruction.py +0 -4
  24. pygsti/circuits/circuitlist.py +47 -5
  25. pygsti/circuits/circuitparser/__init__.py +8 -8
  26. pygsti/circuits/circuitparser/fastcircuitparser.cp39-win32.pyd +0 -0
  27. pygsti/circuits/circuitstructure.py +3 -3
  28. pygsti/circuits/cloudcircuitconstruction.py +1 -1
  29. pygsti/data/datacomparator.py +2 -7
  30. pygsti/data/dataset.py +46 -44
  31. pygsti/data/hypothesistest.py +0 -7
  32. pygsti/drivers/bootstrap.py +0 -49
  33. pygsti/drivers/longsequence.py +2 -1
  34. pygsti/evotypes/basereps_cython.cp39-win32.pyd +0 -0
  35. pygsti/evotypes/chp/opreps.py +0 -61
  36. pygsti/evotypes/chp/statereps.py +0 -32
  37. pygsti/evotypes/densitymx/effectcreps.cpp +9 -10
  38. pygsti/evotypes/densitymx/effectreps.cp39-win32.pyd +0 -0
  39. pygsti/evotypes/densitymx/effectreps.pyx +1 -1
  40. pygsti/evotypes/densitymx/opreps.cp39-win32.pyd +0 -0
  41. pygsti/evotypes/densitymx/opreps.pyx +2 -2
  42. pygsti/evotypes/densitymx/statereps.cp39-win32.pyd +0 -0
  43. pygsti/evotypes/densitymx/statereps.pyx +1 -1
  44. pygsti/evotypes/densitymx_slow/effectreps.py +7 -23
  45. pygsti/evotypes/densitymx_slow/opreps.py +16 -23
  46. pygsti/evotypes/densitymx_slow/statereps.py +10 -3
  47. pygsti/evotypes/evotype.py +39 -2
  48. pygsti/evotypes/stabilizer/effectreps.cp39-win32.pyd +0 -0
  49. pygsti/evotypes/stabilizer/effectreps.pyx +0 -4
  50. pygsti/evotypes/stabilizer/opreps.cp39-win32.pyd +0 -0
  51. pygsti/evotypes/stabilizer/opreps.pyx +0 -4
  52. pygsti/evotypes/stabilizer/statereps.cp39-win32.pyd +0 -0
  53. pygsti/evotypes/stabilizer/statereps.pyx +1 -5
  54. pygsti/evotypes/stabilizer/termreps.cp39-win32.pyd +0 -0
  55. pygsti/evotypes/stabilizer/termreps.pyx +0 -7
  56. pygsti/evotypes/stabilizer_slow/effectreps.py +0 -22
  57. pygsti/evotypes/stabilizer_slow/opreps.py +0 -4
  58. pygsti/evotypes/stabilizer_slow/statereps.py +0 -4
  59. pygsti/evotypes/statevec/effectreps.cp39-win32.pyd +0 -0
  60. pygsti/evotypes/statevec/effectreps.pyx +1 -1
  61. pygsti/evotypes/statevec/opreps.cp39-win32.pyd +0 -0
  62. pygsti/evotypes/statevec/opreps.pyx +2 -2
  63. pygsti/evotypes/statevec/statereps.cp39-win32.pyd +0 -0
  64. pygsti/evotypes/statevec/statereps.pyx +1 -1
  65. pygsti/evotypes/statevec/termreps.cp39-win32.pyd +0 -0
  66. pygsti/evotypes/statevec/termreps.pyx +0 -7
  67. pygsti/evotypes/statevec_slow/effectreps.py +0 -3
  68. pygsti/evotypes/statevec_slow/opreps.py +0 -5
  69. pygsti/extras/__init__.py +0 -1
  70. pygsti/extras/drift/stabilityanalyzer.py +3 -1
  71. pygsti/extras/interpygate/__init__.py +12 -0
  72. pygsti/extras/interpygate/core.py +0 -36
  73. pygsti/extras/interpygate/process_tomography.py +44 -10
  74. pygsti/extras/rpe/rpeconstruction.py +0 -2
  75. pygsti/forwardsims/__init__.py +1 -0
  76. pygsti/forwardsims/forwardsim.py +14 -55
  77. pygsti/forwardsims/mapforwardsim.py +69 -18
  78. pygsti/forwardsims/mapforwardsim_calc_densitymx.cp39-win32.pyd +0 -0
  79. pygsti/forwardsims/mapforwardsim_calc_densitymx.pyx +65 -66
  80. pygsti/forwardsims/mapforwardsim_calc_generic.py +91 -13
  81. pygsti/forwardsims/matrixforwardsim.py +63 -15
  82. pygsti/forwardsims/termforwardsim.py +8 -110
  83. pygsti/forwardsims/termforwardsim_calc_stabilizer.cp39-win32.pyd +0 -0
  84. pygsti/forwardsims/termforwardsim_calc_statevec.cp39-win32.pyd +0 -0
  85. pygsti/forwardsims/termforwardsim_calc_statevec.pyx +0 -651
  86. pygsti/forwardsims/torchfwdsim.py +265 -0
  87. pygsti/forwardsims/weakforwardsim.py +2 -2
  88. pygsti/io/__init__.py +1 -2
  89. pygsti/io/mongodb.py +0 -2
  90. pygsti/io/stdinput.py +6 -22
  91. pygsti/layouts/copalayout.py +10 -12
  92. pygsti/layouts/distlayout.py +0 -40
  93. pygsti/layouts/maplayout.py +103 -25
  94. pygsti/layouts/matrixlayout.py +99 -60
  95. pygsti/layouts/prefixtable.py +1534 -52
  96. pygsti/layouts/termlayout.py +1 -1
  97. pygsti/modelmembers/instruments/instrument.py +3 -3
  98. pygsti/modelmembers/instruments/tpinstrument.py +2 -2
  99. pygsti/modelmembers/modelmember.py +0 -17
  100. pygsti/modelmembers/operations/__init__.py +2 -4
  101. pygsti/modelmembers/operations/affineshiftop.py +1 -0
  102. pygsti/modelmembers/operations/composederrorgen.py +1 -1
  103. pygsti/modelmembers/operations/composedop.py +1 -24
  104. pygsti/modelmembers/operations/denseop.py +5 -5
  105. pygsti/modelmembers/operations/eigpdenseop.py +2 -2
  106. pygsti/modelmembers/operations/embeddederrorgen.py +1 -1
  107. pygsti/modelmembers/operations/embeddedop.py +0 -1
  108. pygsti/modelmembers/operations/experrorgenop.py +2 -2
  109. pygsti/modelmembers/operations/fullarbitraryop.py +1 -0
  110. pygsti/modelmembers/operations/fullcptpop.py +2 -2
  111. pygsti/modelmembers/operations/fulltpop.py +28 -6
  112. pygsti/modelmembers/operations/fullunitaryop.py +5 -4
  113. pygsti/modelmembers/operations/lindbladcoefficients.py +93 -78
  114. pygsti/modelmembers/operations/lindbladerrorgen.py +268 -441
  115. pygsti/modelmembers/operations/linearop.py +7 -27
  116. pygsti/modelmembers/operations/opfactory.py +1 -1
  117. pygsti/modelmembers/operations/repeatedop.py +1 -24
  118. pygsti/modelmembers/operations/staticstdop.py +1 -1
  119. pygsti/modelmembers/povms/__init__.py +3 -3
  120. pygsti/modelmembers/povms/basepovm.py +7 -36
  121. pygsti/modelmembers/povms/complementeffect.py +4 -9
  122. pygsti/modelmembers/povms/composedeffect.py +0 -320
  123. pygsti/modelmembers/povms/computationaleffect.py +1 -1
  124. pygsti/modelmembers/povms/computationalpovm.py +3 -1
  125. pygsti/modelmembers/povms/effect.py +3 -5
  126. pygsti/modelmembers/povms/marginalizedpovm.py +0 -79
  127. pygsti/modelmembers/povms/tppovm.py +74 -2
  128. pygsti/modelmembers/states/__init__.py +2 -5
  129. pygsti/modelmembers/states/composedstate.py +0 -317
  130. pygsti/modelmembers/states/computationalstate.py +3 -3
  131. pygsti/modelmembers/states/cptpstate.py +4 -4
  132. pygsti/modelmembers/states/densestate.py +6 -4
  133. pygsti/modelmembers/states/fullpurestate.py +0 -24
  134. pygsti/modelmembers/states/purestate.py +1 -1
  135. pygsti/modelmembers/states/state.py +5 -6
  136. pygsti/modelmembers/states/tpstate.py +28 -10
  137. pygsti/modelmembers/term.py +3 -6
  138. pygsti/modelmembers/torchable.py +50 -0
  139. pygsti/modelpacks/_modelpack.py +1 -1
  140. pygsti/modelpacks/smq1Q_ZN.py +3 -1
  141. pygsti/modelpacks/smq2Q_XXYYII.py +2 -1
  142. pygsti/modelpacks/smq2Q_XY.py +3 -3
  143. pygsti/modelpacks/smq2Q_XYI.py +2 -2
  144. pygsti/modelpacks/smq2Q_XYICNOT.py +3 -3
  145. pygsti/modelpacks/smq2Q_XYICPHASE.py +3 -3
  146. pygsti/modelpacks/smq2Q_XYXX.py +1 -1
  147. pygsti/modelpacks/smq2Q_XYZICNOT.py +3 -3
  148. pygsti/modelpacks/smq2Q_XYZZ.py +1 -1
  149. pygsti/modelpacks/stdtarget.py +0 -121
  150. pygsti/models/cloudnoisemodel.py +1 -2
  151. pygsti/models/explicitcalc.py +3 -3
  152. pygsti/models/explicitmodel.py +3 -13
  153. pygsti/models/fogistore.py +5 -3
  154. pygsti/models/localnoisemodel.py +1 -2
  155. pygsti/models/memberdict.py +0 -12
  156. pygsti/models/model.py +800 -65
  157. pygsti/models/modelconstruction.py +4 -4
  158. pygsti/models/modelnoise.py +2 -2
  159. pygsti/models/modelparaminterposer.py +1 -1
  160. pygsti/models/oplessmodel.py +1 -1
  161. pygsti/models/qutrit.py +15 -14
  162. pygsti/objectivefns/objectivefns.py +73 -138
  163. pygsti/objectivefns/wildcardbudget.py +2 -7
  164. pygsti/optimize/__init__.py +1 -0
  165. pygsti/optimize/arraysinterface.py +28 -0
  166. pygsti/optimize/customcg.py +0 -12
  167. pygsti/optimize/customlm.py +129 -323
  168. pygsti/optimize/customsolve.py +2 -2
  169. pygsti/optimize/optimize.py +0 -84
  170. pygsti/optimize/simplerlm.py +841 -0
  171. pygsti/optimize/wildcardopt.py +19 -598
  172. pygsti/protocols/confidenceregionfactory.py +28 -14
  173. pygsti/protocols/estimate.py +31 -14
  174. pygsti/protocols/gst.py +142 -68
  175. pygsti/protocols/modeltest.py +6 -10
  176. pygsti/protocols/protocol.py +9 -37
  177. pygsti/protocols/rb.py +450 -79
  178. pygsti/protocols/treenode.py +8 -2
  179. pygsti/protocols/vb.py +108 -206
  180. pygsti/protocols/vbdataframe.py +1 -1
  181. pygsti/report/factory.py +0 -15
  182. pygsti/report/fogidiagram.py +1 -17
  183. pygsti/report/modelfunction.py +12 -3
  184. pygsti/report/mpl_colormaps.py +1 -1
  185. pygsti/report/plothelpers.py +8 -2
  186. pygsti/report/reportables.py +41 -37
  187. pygsti/report/templates/offline/pygsti_dashboard.css +6 -0
  188. pygsti/report/templates/offline/pygsti_dashboard.js +12 -0
  189. pygsti/report/workspace.py +2 -14
  190. pygsti/report/workspaceplots.py +326 -504
  191. pygsti/tools/basistools.py +9 -36
  192. pygsti/tools/edesigntools.py +124 -96
  193. pygsti/tools/fastcalc.cp39-win32.pyd +0 -0
  194. pygsti/tools/fastcalc.pyx +35 -81
  195. pygsti/tools/internalgates.py +151 -15
  196. pygsti/tools/jamiolkowski.py +5 -5
  197. pygsti/tools/lindbladtools.py +19 -11
  198. pygsti/tools/listtools.py +0 -114
  199. pygsti/tools/matrixmod2.py +1 -1
  200. pygsti/tools/matrixtools.py +173 -339
  201. pygsti/tools/nameddict.py +1 -1
  202. pygsti/tools/optools.py +154 -88
  203. pygsti/tools/pdftools.py +0 -25
  204. pygsti/tools/rbtheory.py +3 -320
  205. pygsti/tools/slicetools.py +64 -12
  206. pyGSTi-0.9.12.1.dist-info/METADATA +0 -155
  207. pygsti/algorithms/directx.py +0 -711
  208. pygsti/evotypes/qibo/__init__.py +0 -33
  209. pygsti/evotypes/qibo/effectreps.py +0 -78
  210. pygsti/evotypes/qibo/opreps.py +0 -376
  211. pygsti/evotypes/qibo/povmreps.py +0 -98
  212. pygsti/evotypes/qibo/statereps.py +0 -174
  213. pygsti/extras/rb/__init__.py +0 -13
  214. pygsti/extras/rb/benchmarker.py +0 -957
  215. pygsti/extras/rb/dataset.py +0 -378
  216. pygsti/extras/rb/io.py +0 -814
  217. pygsti/extras/rb/simulate.py +0 -1020
  218. pygsti/io/legacyio.py +0 -385
  219. pygsti/modelmembers/povms/denseeffect.py +0 -142
  220. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/LICENSE +0 -0
  221. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/top_level.txt +0 -0
@@ -20,12 +20,7 @@ from pygsti.baseobjs.label import Label as _Label, CircuitLabel as _CircuitLabel
20
20
  from pygsti.baseobjs import outcomelabeldict as _ld, _compatibility as _compat
21
21
  from pygsti.tools import internalgates as _itgs
22
22
  from pygsti.tools import slicetools as _slct
23
-
24
-
25
- #Internally:
26
- # when static: a tuple of Label objects labelling each top-level circuit layer
27
- # when editable: a list of lists, one per top-level layer, holding just
28
- # the non-LabelTupTup (non-compound) labels.
23
+ from pygsti.tools.legacytools import deprecate as _deprecate_fn
29
24
 
30
25
  #Externally, we'd like to do thinks like:
31
26
  # c = Circuit( LabelList )
@@ -43,6 +38,11 @@ from pygsti.tools import slicetools as _slct
43
38
  # c[1:3,'Q0'] = ('Gx','Gy') # assigns to a part of the Q0 line
44
39
 
45
40
 
41
+ #Add warning filter
42
+ msg = 'Could not find matching standard gate name in provided dictionary. Falling back to try and find a'\
43
+ +' unitary from standard_gatename_unitaries which matches up to a global phase.'
44
+ _warnings.filterwarnings('module', message=msg, category=UserWarning)
45
+
46
46
  def _np_to_quil_def_str(name, input_array):
47
47
  """
48
48
  Write a DEFGATE block for RQC quil for an arbitrary one- or two-qubit unitary gate.
@@ -75,7 +75,7 @@ def _np_to_quil_def_str(name, input_array):
75
75
  def _num_to_rqc_str(num):
76
76
  """Convert float to string to be included in RQC quil DEFGATE block
77
77
  (as written by _np_to_quil_def_str)."""
78
- num = _np.complex_(_np.real_if_close(num))
78
+ num = _np.complex128(_np.real_if_close(num))
79
79
  if _np.imag(num) == 0:
80
80
  output = str(_np.real(num))
81
81
  return output
@@ -96,14 +96,13 @@ def _label_to_nested_lists_of_simple_labels(lbl, default_sslbls=None, always_ret
96
96
  """ Convert lbl into nested lists of *simple* labels """
97
97
  if not isinstance(lbl, _Label): # if not a Label, make into a label,
98
98
  lbl = _Label(lbl) # e.g. a string or list/tuple of labels, etc.
99
- if lbl.is_simple(): # a *simple* label - the elements of our lists
99
+ if lbl.IS_SIMPLE: # a *simple* label - the elements of our lists
100
100
  if lbl.sslbls is None and default_sslbls is not None:
101
101
  lbl = _Label(lbl.name, default_sslbls)
102
102
  return [lbl] if always_return_list else lbl
103
103
  return [_label_to_nested_lists_of_simple_labels(l, default_sslbls, False)
104
104
  for l in lbl.components] # a *list*
105
105
 
106
-
107
106
  def _sslbls_of_nested_lists_of_simple_labels(obj, labels_to_ignore=None):
108
107
  """ Get state space labels from a nested lists of simple (not compound) Labels. """
109
108
  if isinstance(obj, _Label):
@@ -114,7 +113,6 @@ def _sslbls_of_nested_lists_of_simple_labels(obj, labels_to_ignore=None):
114
113
  sub_sslbls = [_sslbls_of_nested_lists_of_simple_labels(sub, labels_to_ignore) for sub in obj]
115
114
  return None if (None in sub_sslbls) else set(_itertools.chain(*sub_sslbls))
116
115
 
117
-
118
116
  def _accumulate_explicit_sslbls(obj):
119
117
  """
120
118
  Get all the explicitly given state-space labels within `obj`,
@@ -122,7 +120,7 @@ def _accumulate_explicit_sslbls(obj):
122
120
  """
123
121
  ret = set()
124
122
  if isinstance(obj, _Label):
125
- if not obj.is_simple():
123
+ if not obj.IS_SIMPLE:
126
124
  for lbl in obj.components:
127
125
  ret.update(_accumulate_explicit_sslbls(lbl))
128
126
  else: # a simple label
@@ -200,87 +198,6 @@ class Circuit(object):
200
198
  construct the circuit in place, after which `done_editing()` should be
201
199
  called so that the Circuit can be properly hashed as needed.
202
200
 
203
- Parameters
204
- ----------
205
- layer_labels : iterable of Labels or str
206
- This argument provides a list of the layer labels specifying the
207
- state preparations, gates, and measurements for the circuit. This
208
- argument can also be a :class:`Circuit` or a string, in which case
209
- it is parsed as a text-formatted circuit. Internally this will
210
- eventually be converted to a list of `Label` objects, one per layer,
211
- but it may be specified using anything that can be readily converted
212
- to a Label objects. For example, any of the following are allowed:
213
-
214
- - `['Gx','Gx']` : X gate on each of 2 layers
215
- - `[Label('Gx'),Label('Gx')]` : same as above
216
- - `[('Gx',0),('Gy',0)]` : X then Y on qubit 0 (2 layers)
217
- - `[[('Gx',0),('Gx',1)],[('Gy',0),('Gy',1)]]` : parallel X then Y on qubits 0 & 1
218
-
219
- line_labels : iterable, optional
220
- The (string valued) label for each circuit line. If `'auto'`, then
221
- `line_labels` is taken to be the list of all state-space labels
222
- present within `layer_labels`. If there are no such labels (e.g.
223
- if `layer_labels` contains just gate names like `('Gx','Gy')`), then
224
- the special value `'*'` is used as a single line label.
225
-
226
- num_lines : int, optional
227
- Specify this instead of `line_labels` to set the latter to the
228
- integers between 0 and `num_lines-1`.
229
-
230
- editable : bool, optional
231
- Whether the created `Circuit` is created in able to be modified. If
232
- `True`, then you should call `done_editing()` once the circuit is
233
- completely assembled, as this makes the circuit read-only and
234
- allows it to be hashed.
235
-
236
- stringrep : string, optional
237
- A string representation for the circuit. If `None` (the default),
238
- then this will be generated automatically when needed. One
239
- reason you'd want to specify this is if you know of a nice compact
240
- string representation that you'd rather use, e.g. `"Gx^4"` instead
241
- of the automatically generated `"GxGxGxGx"`. If you want to
242
- initialize a `Circuit` entirely from a string representation you
243
- can either specify the string in as `layer_labels` or set
244
- `layer_labels` to `None` and `stringrep` to any valid (one-line)
245
- circuit string.
246
-
247
- name : str, optional
248
- A name for this circuit (useful if/when used as a block within
249
- larger circuits).
250
-
251
- check : bool, optional
252
- Whether `stringrep` should be checked against `layer_labels` to
253
- ensure they are consistent, and whether the labels in `layer_labels`
254
- are a subset of `line_labels`. The only reason you'd want to set
255
- this to `False` is if you're absolutely sure `stringrep` and
256
- `line_labels` are consistent and want to save computation time.
257
-
258
- expand_subcircuits : bool or "default"
259
- If `"default"`, then the value of `Circuit.default_expand_subcircuits`
260
- is used. If True, then any sub-circuits (e.g. anything exponentiated
261
- like "(GxGy)^4") will be expanded when it is stored within the created
262
- Circuit. If False, then such sub-circuits will be left as-is. It's
263
- typically more robust to expand sub-circuits as this facilitates
264
- comparison (e.g. so "GxGx" == "Gx^2"), but in cases when you have
265
- massive exponents (e.g. "Gx^8192") it may improve performance to
266
- set `expand_subcircuits=False`.
267
-
268
- occurrence : hashable, optional
269
- A value to set as the "occurrence id" for this circuit. This
270
- value doesn't affect the circuit an any way except by affecting
271
- it's hashing and equivalence testing. Circuits with different
272
- occurrence ids are *not* equivalent. Occurrence values effectively
273
- allow multiple copies of the same ciruit to be stored in a
274
- dictionary or :class:`DataSet`.
275
-
276
- compilable_layer_indices : tuple, optional
277
- The circuit-layer indices that may be internally altered (but retaining the
278
- same target operation) and/or combined with the following circuit layer
279
- by a hardware compiler.when executing this circuit. Layers that are
280
- not "compilable" are effectively followed by a *barrier* which prevents
281
- the hardward compiler from restructuring the circuit across the layer
282
- boundary.
283
-
284
201
  Attributes
285
202
  ----------
286
203
  default_expand_subcircuits : bool
@@ -297,6 +214,17 @@ class Circuit(object):
297
214
 
298
215
  str : str
299
216
  The Python string representation of this Circuit.
217
+
218
+ layer_labels :
219
+ When static: a tuple of Label objects labelling each top-level circuit layer
220
+ When editable: a list of lists, one per top-level layer, holding just
221
+ the non-LabelTupTup (non-compound) labels. I.e. in the static case a LabelTupTup
222
+ which specifies a complete circuit layer is assumed to contain no LabelTupTups as
223
+ sub-components. Similarly, in the editable case a nested sublist which
224
+ contains a set of Labels for a complete circuit layer is assumed to contain
225
+ no further nested sublists as elements. For more complicated nested
226
+ circuit structures, if required, circuits can contain CircuitLabel objects as elements.
227
+ see :class:pygsti.baseobjs.label.CircuitLabel.
300
228
  """
301
229
  default_expand_subcircuits = True
302
230
 
@@ -466,7 +394,6 @@ class Circuit(object):
466
394
  expand_subcircuits = Circuit.default_expand_subcircuits
467
395
  if expand_subcircuits and layer_labels is not None:
468
396
  layer_labels_objs = tuple(_itertools.chain(*[x.expand_subcircuits() for x in map(to_label, layer_labels)]))
469
- #print("DB: Layer labels = ",layer_labels_objs)
470
397
 
471
398
  #Parse stringrep if needed
472
399
  if stringrep is not None and (layer_labels is None or check):
@@ -475,7 +402,6 @@ class Circuit(object):
475
402
  chk, chk_labels, chk_occurrence, chk_compilable_inds = cparser.parse(stringrep) # tuple of Labels
476
403
  if expand_subcircuits and chk is not None:
477
404
  chk = tuple(_itertools.chain(*[x.expand_subcircuits() for x in map(to_label, chk)]))
478
- #print("DB: Check Layer labels = ",chk)
479
405
 
480
406
  if layer_labels is None:
481
407
  layer_labels = chk
@@ -483,7 +409,6 @@ class Circuit(object):
483
409
  if layer_labels_objs is None:
484
410
  layer_labels_objs = tuple(map(to_label, layer_labels))
485
411
  if layer_labels_objs != tuple(chk):
486
- #print("DB: ",layer_labels_objs,"VS",tuple(chk))
487
412
  raise ValueError(("Error intializing Circuit: "
488
413
  " `layer_labels` and `stringrep` do not match: %s != %s\n"
489
414
  "(set `layer_labels` to None to infer it from `stringrep`)")
@@ -568,51 +493,80 @@ class Circuit(object):
568
493
  if compilable_layer_indices is not None:
569
494
  max_layer_index = len(labels) - 1
570
495
  if any([(i < 0 or i > max_layer_index) for i in compilable_layer_indices]):
571
- raise ValueError("Entry our of range in `compilable_layer_indices`!")
572
- compilable_layer_indices = tuple(compilable_layer_indices)
496
+ raise ValueError("Entry out of range in `compilable_layer_indices`!")
497
+ compilable_layer_indices_tup = tuple(compilable_layer_indices)
498
+ else:
499
+ compilable_layer_indices_tup = ()
573
500
 
574
501
  #Set *all* class attributes (separated so can call bare_init separately for fast internal creation)
575
- self._bare_init(labels, my_line_labels, editable, name, stringrep, occurrence, compilable_layer_indices)
576
-
577
- # # Special case: layer_labels can be a single CircuitLabel or Circuit
578
- # # (Note: a Circuit would work just fine, as a list of layers, but this performs some extra checks)
579
- # isCircuit = isinstance(layer_labels, _Circuit)
580
- # isCircuitLabel = isinstance(layer_labels, _CircuitLabel)
581
- # if isCircuitLabel:
582
- # assert(line_labels is None or line_labels == "auto" or line_labels == expected_line_labels), \
583
- # "Given `line_labels` (%s) are inconsistent with CircuitLabel's sslbls (%s)" \
584
- # % (str(line_labels),str(layer_labels.sslbls))
585
- # assert(num_lines is None or layer_labels.sslbls == tuple(range(num_lines))), \
586
- # "Given `num_lines` (%d) is inconsistend with CircuitLabel's sslbls (%s)" \
587
- # % (num_lines,str(layer_labels.sslbls))
588
- # if name is None: name = layer_labels.name # Note: `name` can be used to rename a CircuitLabel
589
-
590
- # self._line_labels = layer_labels.sslbls
591
- # self._reps = layer_labels.reps
592
- # self._name = name
593
- # self._static = not editable
502
+ self._bare_init(labels, my_line_labels, editable, name, stringrep,
503
+ occurrence, compilable_layer_indices_tup)
504
+
594
505
 
595
506
  @classmethod
596
507
  def _fastinit(cls, labels, line_labels, editable, name='', stringrep=None, occurrence=None,
597
- compilable_layer_indices=None):
508
+ compilable_layer_indices_tup=()):
598
509
  ret = cls.__new__(cls)
599
- ret._bare_init(labels, line_labels, editable, name, stringrep, occurrence)
510
+ ret._bare_init(labels, line_labels, editable, name, stringrep, occurrence, compilable_layer_indices_tup)
600
511
  return ret
601
512
 
513
+ #Note: If editing _bare_init one should also check _copy_init in case changes must be propagated.
602
514
  def _bare_init(self, labels, line_labels, editable, name='', stringrep=None, occurrence=None,
603
- compilable_layer_indices=None):
515
+ compilable_layer_indices_tup=()):
516
+ self._labels = labels
517
+ self._line_labels = tuple(line_labels)
518
+ self._occurrence_id = occurrence
519
+ self._compilable_layer_indices_tup = compilable_layer_indices_tup # always a tuple, but can be empty.
520
+ self._static = not editable
521
+ if self._static:
522
+ self._hashable_tup = self.tup #if static precompute and cache the hashable circuit tuple.
523
+ self._hash = hash(self._hashable_tup)
524
+ self._str = stringrep
525
+ else:
526
+ self._str = None # can be None (lazy generation)
527
+ self._name = name # can be None
528
+ #self._times = None # for FUTURE expansion
529
+ self.auxinfo = {} # for FUTURE expansion / user metadata
530
+
531
+ #Note: If editing _copy_init one should also check _bare_init in case changes must be propagated.
532
+ #specialized codepath for copying
533
+ def _copy_init(self, labels, line_labels, editable, name='', stringrep=None, occurrence=None,
534
+ compilable_layer_indices_tup=(), hashable_tup=None, precomp_hash=None):
604
535
  self._labels = labels
605
536
  self._line_labels = line_labels
606
537
  self._occurrence_id = occurrence
607
- self._compilable_layer_indices_tup = ('__CMPLBL__',) + compilable_layer_indices \
608
- if (compilable_layer_indices is not None) else () # always a tuple, but can be empty.
538
+ self._compilable_layer_indices_tup = compilable_layer_indices_tup # always a tuple, but can be empty.
609
539
  self._static = not editable
610
- #self._reps = reps # repetitions: default=1, which remains unless we initialize from a CircuitLabel...
540
+ if self._static:
541
+ self._hashable_tup = hashable_tup #if static we have already precomputed and cached the hashable circuit tuple.
542
+ self._hash = precomp_hash #Same as previous comment. Only meant to be used in settings where we're explicitly checking for self._static.
543
+ self._str = stringrep
544
+ else:
545
+ self._str = None # can be None (lazy generation)
611
546
  self._name = name # can be None
612
- self._str = stringrep if self._static else None # can be None (lazy generation)
613
- self._times = None # for FUTURE expansion
547
+ #self._times = None # for FUTURE expansion
614
548
  self.auxinfo = {} # for FUTURE expansion / user metadata
615
- self._alignmarks = () # layer indices *before* which there is an alignment mark
549
+
550
+ return self
551
+
552
+ #pickle management functions
553
+ def __getstate__(self):
554
+ state_dict = self.__dict__
555
+ #if state_dict.get('_hash', None) is not None:
556
+ # del state_dict['_hash'] #don't store the hash, recompute at unpickling time
557
+ return state_dict
558
+
559
+ def __setstate__(self, state_dict):
560
+ for k, v in state_dict.items():
561
+ self.__dict__[k] = v
562
+ if self.__dict__['_static']:
563
+ #reinitialize the hash
564
+ if self.__dict__.get('_hashable_tup', None) is not None:
565
+ self._hash = hash(self._hashable_tup)
566
+ else: #legacy support
567
+ self._hashable_tup = self.tup
568
+ self._hash = hash(self._hashable_tup)
569
+
616
570
 
617
571
  def to_label(self, nreps=1):
618
572
  """
@@ -648,17 +602,15 @@ class Circuit(object):
648
602
  """
649
603
  The line labels (often qubit labels) of this circuit.
650
604
  """
605
+ assert(not self._static), \
606
+ ("Cannot edit a read-only circuit! "
607
+ "Set editable=True when calling pygsti.baseobjs.Circuit to create editable circuit.")
651
608
  if value == self._line_labels: return
652
- #added_line_labels = set(value) - set(self._line_labels) # it's always OK to add lines
653
609
  removed_line_labels = set(self._line_labels) - set(value)
654
610
  if removed_line_labels:
655
611
  idling_line_labels = set(self.idling_lines())
656
612
  removed_not_idling = removed_line_labels - idling_line_labels
657
- if removed_not_idling and self._static:
658
- raise ValueError("Cannot remove non-idling lines %s from a read-only circuit!" %
659
- str(removed_not_idling))
660
- else:
661
- self.delete_lines(tuple(removed_not_idling))
613
+ self.delete_lines(tuple(removed_not_idling))
662
614
  self._line_labels = tuple(value)
663
615
  self._str = None # regenerate string rep (it may have updated)
664
616
 
@@ -684,6 +636,9 @@ class Circuit(object):
684
636
  """
685
637
  The occurrence id of this circuit.
686
638
  """
639
+ assert(not self._static), \
640
+ ("Cannot edit a read-only circuit! "
641
+ "Set editable=True when calling pygsti.baseobjs.Circuit to create editable circuit.")
687
642
  self._occurrence_id = value
688
643
  self._str = None # regenerate string rep (it may have updated)
689
644
 
@@ -699,8 +654,8 @@ class Circuit(object):
699
654
  if self._static:
700
655
  return self._labels
701
656
  else:
702
- return tuple([to_label(layer_lbl) for layer_lbl in self._labels])
703
-
657
+ return tuple([layer_lbl if isinstance(layer_lbl, _Label)
658
+ else _Label(layer_lbl) for layer_lbl in self._labels])
704
659
  @property
705
660
  def tup(self):
706
661
  """
@@ -710,35 +665,66 @@ class Circuit(object):
710
665
  -------
711
666
  tuple
712
667
  """
668
+ comp_lbl_flag = ('__CMPLBL__',) if self._compilable_layer_indices_tup else ()
669
+ layertup = self._labels if self._static else self.layertup
670
+
713
671
  if self._occurrence_id is None:
714
672
  if self._line_labels in (('*',), ()): # No line labels
715
- return self.layertup + self._compilable_layer_indices_tup
673
+ return layertup + comp_lbl_flag + self._compilable_layer_indices_tup
716
674
  else:
717
- return self.layertup + ('@',) + self._line_labels + self._compilable_layer_indices_tup
718
- else:
719
- linelbl_tup = () if self._line_labels in (('*',), ()) else self._line_labels
720
- return self.layertup + ('@',) + linelbl_tup + ('@', self._occurrence_id) \
721
- + self._compilable_layer_indices_tup
675
+ return layertup + ('@',) + self._line_labels + comp_lbl_flag\
676
+ + self._compilable_layer_indices_tup
677
+ else:
678
+ if self._line_labels in (('*',), ()):
679
+ return layertup + ('@',) + ('@', self._occurrence_id) \
680
+ + comp_lbl_flag + self._compilable_layer_indices_tup
681
+ else:
682
+ return layertup + ('@',) + self._line_labels + ('@', self._occurrence_id) \
683
+ + comp_lbl_flag + self._compilable_layer_indices_tup
684
+ # Note: we *always* need line labels (even if they're empty) when using occurrence id
685
+
686
+ def _tup_copy(self, labels):
687
+ """
688
+ This Circuit as a standard Python tuple of layer Labels and line labels.
689
+ This version takes as input a precomputed set of static layer labels
690
+ and uses this to avoid double computing this during copy operations.
691
+ Only presently intended for expediting copy operations.
692
+ Returns
693
+ -------
694
+ tuple
695
+ """
696
+ comp_lbl_flag = ('__CMPLBL__',) if self._compilable_layer_indices_tup else ()
697
+ if self._occurrence_id is None:
698
+ if self._line_labels in (('*',), ()): # No line labels
699
+ return labels + comp_lbl_flag + self._compilable_layer_indices_tup
700
+ else:
701
+ return labels + ('@',) + self._line_labels + comp_lbl_flag + self._compilable_layer_indices_tup
702
+ else:
703
+ if self._line_labels in (('*',), ()):
704
+ return labels + ('@',) + ('@', self._occurrence_id) \
705
+ + comp_lbl_flag + self._compilable_layer_indices_tup
706
+ else:
707
+ return labels + ('@',) + self._line_labels + ('@', self._occurrence_id) \
708
+ + comp_lbl_flag + self._compilable_layer_indices_tup
722
709
  # Note: we *always* need line labels (even if they're empty) when using occurrence id
723
710
 
724
711
  @property
725
712
  def compilable_layer_indices(self):
726
713
  """ Tuple of the layer indices corresponding to "compilable" layers."""
727
- if len(self._compilable_layer_indices_tup) > 0: # then begins with __CMPLBL__
728
- return self._compilable_layer_indices_tup[1:]
729
- else:
730
- return ()
714
+ return self._compilable_layer_indices_tup
731
715
 
732
716
  @compilable_layer_indices.setter
733
717
  def compilable_layer_indices(self, val):
734
- self._compilable_layer_indices_tup = ('__CMPLBL__',) + tuple(val) \
735
- if (val is not None) else () # always a tuple, but can be empty.
718
+ assert(not self._static), \
719
+ ("Cannot edit a read-only circuit! "
720
+ "Set editable=True when calling pygsti.baseobjs.Circuit to create editable circuit.")
721
+ self._compilable_layer_indices_tup = tuple(val) if (val is not None) else () # always a tuple, but can be empty.
736
722
 
737
723
  @property
738
724
  def compilable_by_layer(self):
739
725
  """ Boolean array indicating whether each layer is "compilable" or not."""
740
726
  ret = _np.zeros(self.depth, dtype=bool)
741
- ret[list(self.compilable_layer_indices)] = True
727
+ ret[list(self._compilable_layer_indices_tup)] = True
742
728
  return ret
743
729
 
744
730
  @property
@@ -751,8 +737,8 @@ class Circuit(object):
751
737
  str
752
738
  """
753
739
  if self._str is None:
754
- generated_str = _op_seq_to_str(self._labels, self.line_labels, self._occurrence_id,
755
- self.compilable_layer_indices) # lazy generation
740
+ generated_str = _op_seq_to_str(self._labels, self._line_labels, self._occurrence_id,
741
+ self._compilable_layer_indices_tup) # lazy generation
756
742
  if self._static: # if we're read-only then cache the string one and for all,
757
743
  self._str = generated_str # otherwise keep generating it as needed (unless it's set by the user?)
758
744
  return generated_str
@@ -797,20 +783,20 @@ class Circuit(object):
797
783
  " evaluate to %s (this circuit)") %
798
784
  (value, self.str))
799
785
  if chk_labels is not None:
800
- if tuple(self.line_labels) != chk_labels:
786
+ if tuple(self._line_labels) != chk_labels:
801
787
  raise ValueError(("Cannot set .str to %s because line labels evaluate to"
802
788
  " %s which is != this circuit's line labels (%s).") %
803
- (value, chk_labels, str(self.line_labels)))
789
+ (value, chk_labels, str(self._line_labels)))
804
790
  if chk_occurrence is not None:
805
791
  if self._occurrence_id != chk_occurrence:
806
792
  raise ValueError(("Cannot set .str to %s because occurrence evaluates to"
807
793
  " %s which is != this circuit's occurrence (%s).") %
808
794
  (value, str(chk_occurrence), str(self._occurrence_id)))
809
795
  if chk_compilable_inds is not None:
810
- if self.compilable_layer_indices != chk_compilable_inds:
796
+ if self._compilable_layer_indices_tup != chk_compilable_inds:
811
797
  raise ValueError(("Cannot set .str to %s because compilable layer indices eval to"
812
798
  " %s which is != this circuit's indices (%s).") %
813
- (value, str(chk_compilable_inds), str(self.compilable_layer_indices)))
799
+ (value, str(chk_compilable_inds), str(self._compilable_layer_indices_tup)))
814
800
 
815
801
  self._str = value
816
802
 
@@ -820,11 +806,7 @@ class Circuit(object):
820
806
  " mode in order to hash it. You should call"
821
807
  " circuit.done_editing() beforehand."))
822
808
  self.done_editing()
823
- return hash(self.tup)
824
- #if self._line_labels in (('*',),()): #No line labels
825
- # return hash(self._labels)
826
- #else:
827
- # return hash(self._labels + ('@',) + self._line_labels)
809
+ return self._hash
828
810
 
829
811
  def __len__(self):
830
812
  return len(self._labels)
@@ -839,7 +821,7 @@ class Circuit(object):
839
821
  def __radd__(self, x):
840
822
  if not isinstance(x, Circuit):
841
823
  assert(all([isinstance(l, _Label) for l in x])), "Only Circuits and Label-tuples can be added to Circuits!"
842
- return Circuit._fastinit(x + self.layertup, self.line_labels, editable=False)
824
+ return Circuit._fastinit(x + self.layertup, self._line_labels, editable=False)
843
825
  return x.__add__(self)
844
826
 
845
827
  def __add__(self, x):
@@ -862,24 +844,28 @@ class Circuit(object):
862
844
 
863
845
  if not isinstance(x, Circuit):
864
846
  assert(all([isinstance(l, _Label) for l in x])), "Only Circuits and Label-tuples can be added to Circuits!"
865
- return Circuit._fastinit(self.layertup + x, self.line_labels, editable=False)
847
+ new_line_labels = set(sum([l.sslbls for l in x if l.sslbls is not None],
848
+ self._line_labels)) #trick for concatenating multiple tuples
849
+ #new_line_labels.update(self._line_labels)
850
+ new_line_labels = sorted(new_line_labels)
851
+ return Circuit._fastinit(self.layertup + x, new_line_labels, editable=False)
866
852
 
867
853
  #Add special line label handling to deal with the special global idle circuits (which have no line labels
868
854
  # associated with them typically).
869
855
  #Check if a the circuit or labels being added are all global idles, if so inherit the
870
856
  #line labels from the circuit being added to. Otherwise, enforce compatibility.
871
- layertup_x = x.layertup if isinstance(x, Circuit) else x
857
+ layertup_x = x.layertup
872
858
  gbl_idle_x= all([lbl == _Label(()) for lbl in layertup_x])
873
859
  gbl_idle_self= all([lbl == _Label(()) for lbl in self.layertup])
874
860
 
875
861
  if not (gbl_idle_x or gbl_idle_self):
876
- combined_labels = {x.line_labels, self.line_labels}
862
+ combined_labels = {x._line_labels, self._line_labels}
877
863
  elif not gbl_idle_x and gbl_idle_self:
878
- combined_labels = {x.line_labels}
864
+ combined_labels = {x._line_labels}
879
865
  elif gbl_idle_x and not gbl_idle_self:
880
- combined_labels = {self.line_labels}
866
+ combined_labels = {self._line_labels}
881
867
  else: #both are all global idles so it doesn't matter which we take.
882
- combined_labels = {self.line_labels}
868
+ combined_labels = {self._line_labels}
883
869
 
884
870
  #check that the line labels are compatible between circuits.
885
871
  #i.e. raise error if adding circuit with * line label to one with
@@ -913,7 +899,7 @@ class Circuit(object):
913
899
  #unpack all of the different sets of labels and make sure there are no duplicates
914
900
  combined_labels_unpacked = {el for tup in combined_labels for el in tup}
915
901
  try:
916
- new_line_labels = tuple(sorted(list(combined_labels_unpacked)))
902
+ new_line_labels = tuple(sorted(combined_labels_unpacked))
917
903
  except TypeError:
918
904
  new_line_labels = tuple(combined_labels_unpacked)
919
905
 
@@ -922,6 +908,33 @@ class Circuit(object):
922
908
 
923
909
  return Circuit._fastinit(self.layertup + x.layertup, new_line_labels, editable=False, name='',
924
910
  stringrep=s, occurrence=None)
911
+
912
+
913
+ def sandwich(self, x, y):
914
+ """
915
+ Method for sandwiching labels around this circuit.
916
+
917
+ Parameters
918
+ ----------
919
+ x : tuple of `Label` objects
920
+ Tuple of Labels to prepend to this
921
+ Circuit.
922
+
923
+ y: tuple of `Label` objects
924
+ Same as `x`, but appended instead.
925
+
926
+ Returns
927
+ -------
928
+ Circuit
929
+ """
930
+
931
+ assert(isinstance(x, tuple) and isinstance(y, tuple)), 'Only tuples of labels are currently supported by `sandwich` method.'
932
+ combined_sandwich_labels = x + y
933
+ assert(all([isinstance(l, _Label) for l in combined_sandwich_labels])), "Only Circuits and Label-tuples can be added to Circuits!"
934
+ new_line_labels = set(sum([l.sslbls for l in combined_sandwich_labels if l.sslbls is not None],
935
+ self._line_labels)) #trick for concatenating multiple tuples
936
+ new_line_labels = tuple(sorted(new_line_labels))
937
+ return Circuit._fastinit(x + self.layertup + y, new_line_labels, editable=False)
925
938
 
926
939
  def repeat(self, ntimes, expand="default"):
927
940
  """
@@ -948,10 +961,10 @@ class Circuit(object):
948
961
  s += "@" + mylines # add line labels
949
962
  if ntimes >= 1 and expand is False:
950
963
  reppedCircuitLbl = self.to_label(nreps=ntimes)
951
- return Circuit((reppedCircuitLbl,), self.line_labels, None, not self._static, s, check=False)
964
+ return Circuit((reppedCircuitLbl,), self._line_labels, None, not self._static, s, check=False)
952
965
  else:
953
966
  # just adds parens to string rep & copies
954
- return Circuit(self.layertup * ntimes, self.line_labels, None, not self._static, s, check=False)
967
+ return Circuit(self.layertup * ntimes, self._line_labels, None, not self._static, s, check=False)
955
968
 
956
969
  def __mul__(self, x):
957
970
  return self.repeat(x)
@@ -960,21 +973,39 @@ class Circuit(object):
960
973
  return self.__mul__(x)
961
974
 
962
975
  def __eq__(self, x):
963
- if x is None: return False
976
+
964
977
  if isinstance(x, Circuit):
965
- return self.tup.__eq__(x.tup)
978
+ if len(self) != len(x):
979
+ return False
980
+ else:
981
+ if self._static and x._static:
982
+ return self._hash == x._hash
983
+ else:
984
+ return self.tup == x.tup
985
+ elif x is None:
986
+ return False
966
987
  else:
967
- return self.layertup == tuple(x) # equality with non-circuits is just based on *labels*
988
+ tup_x = tuple(x)
989
+ if len(self.layertup) != len(tup_x):
990
+ return False
991
+ else:
992
+ return self.layertup == tup_x # equality with non-circuits is just based on *labels*
968
993
 
969
994
  def __lt__(self, x):
970
995
  if isinstance(x, Circuit):
971
- return self.tup.__lt__(x.tup)
996
+ if self._static and x._static:
997
+ return self._hashable_tup.__lt__(x._hashable_tup)
998
+ else:
999
+ return self.tup.__lt__(x.tup)
972
1000
  else:
973
1001
  return self.layertup < tuple(x) # comparison with non-circuits is just based on *labels*
974
1002
 
975
1003
  def __gt__(self, x):
976
1004
  if isinstance(x, Circuit):
977
- return self.tup.__gt__(x.tup)
1005
+ if self._static and x._static:
1006
+ return self._hashable_tup.__gt__(x._hashable_tup)
1007
+ else:
1008
+ return self.tup.__gt__(x.tup)
978
1009
  else:
979
1010
  return self.layertup > tuple(x) # comparison with non-circuits is just based on *labels*
980
1011
 
@@ -987,9 +1018,9 @@ class Circuit(object):
987
1018
  -------
988
1019
  int
989
1020
  """
990
- return len(self.line_labels)
991
-
992
- def copy(self, editable="auto"):
1021
+ return len(self._line_labels)
1022
+
1023
+ def copy(self, editable='auto'):
993
1024
  """
994
1025
  Returns a copy of the circuit.
995
1026
 
@@ -1003,9 +1034,43 @@ class Circuit(object):
1003
1034
  -------
1004
1035
  Circuit
1005
1036
  """
1006
- if editable == "auto": editable = not self._static
1007
- return Circuit(self.layertup, self.line_labels, None, editable, self._str, check=False,
1008
- occurrence=self.occurrence)
1037
+
1038
+ if editable == "auto":
1039
+ editable = not self._static
1040
+
1041
+ #inline new circuit creation.
1042
+ ret = Circuit.__new__(Circuit)
1043
+
1044
+ if editable:
1045
+ if self._static:
1046
+ #static and editable circuits have different conventions for _labels.
1047
+ editable_labels =[[lbl] if lbl.IS_SIMPLE else list(lbl.components) for lbl in self._labels]
1048
+ return ret._copy_init(editable_labels, self._line_labels, editable,
1049
+ self._name, self._str, self._occurrence_id,
1050
+ self._compilable_layer_indices_tup)
1051
+ else:
1052
+ #copy the editable labels (avoiding shallow copy issues)
1053
+ editable_labels = [sublist.copy() for sublist in self._labels]
1054
+ return ret._copy_init(editable_labels, self._line_labels, editable,
1055
+ self._name, self._str, self._occurrence_id,
1056
+ self._compilable_layer_indices_tup)
1057
+ else: #create static copy
1058
+ if self._static:
1059
+ #if presently static leverage precomputed hashable_tup and hash.
1060
+ #These values are only used by _copy_init if the circuit being
1061
+ #created is static, and are ignored otherwise.
1062
+ return ret._copy_init(self._labels, self._line_labels, editable,
1063
+ self._name, self._str, self._occurrence_id,
1064
+ self._compilable_layer_indices_tup,
1065
+ self._hashable_tup, self._hash)
1066
+ else:
1067
+ static_labels = tuple([layer_lbl if isinstance(layer_lbl, _Label) else _Label(layer_lbl)
1068
+ for layer_lbl in self._labels])
1069
+ hashable_tup = self._tup_copy(static_labels)
1070
+ return ret._copy_init(static_labels, self._line_labels,
1071
+ editable, self._name, self._str, self._occurrence_id,
1072
+ self._compilable_layer_indices_tup,
1073
+ hashable_tup, hash(hashable_tup))
1009
1074
 
1010
1075
  def clear(self):
1011
1076
  """
@@ -1034,10 +1099,10 @@ class Circuit(object):
1034
1099
  def _proc_lines_arg(self, lines):
1035
1100
  """ Pre-process the lines argument used by many methods """
1036
1101
  if lines is None:
1037
- lines = self.line_labels
1102
+ lines = self._line_labels
1038
1103
  elif isinstance(lines, slice):
1039
1104
  if lines.start is None and lines.stop is None:
1040
- lines = self.line_labels
1105
+ lines = self._line_labels
1041
1106
  else:
1042
1107
  lines = _slct.indices(lines)
1043
1108
  elif not isinstance(lines, (list, tuple)):
@@ -1047,19 +1112,18 @@ class Circuit(object):
1047
1112
  def _proc_key_arg(self, key):
1048
1113
  """ Pre-process the key argument used by many methods """
1049
1114
  if isinstance(key, tuple):
1050
- if len(key) != 2: return IndexError("Index must be of the form <layerIndex>,<lineIndex>")
1051
- layers = key[0]
1052
- lines = key[1]
1115
+ if len(key) != 2:
1116
+ return IndexError("Index must be of the form <layerIndex>,<lineIndex>")
1117
+ else:
1118
+ return key[0], key[1]
1053
1119
  else:
1054
- layers = key
1055
- lines = None
1056
- return layers, lines
1120
+ return key, None
1057
1121
 
1058
1122
  def _layer_components(self, ilayer):
1059
1123
  """ Get the components of the `ilayer`-th layer as a list/tuple. """
1060
1124
  #(works for static and non-static Circuits)
1061
1125
  if self._static:
1062
- if self._labels[ilayer].is_simple(): return [self._labels[ilayer]]
1126
+ if self._labels[ilayer].IS_SIMPLE: return [self._labels[ilayer]]
1063
1127
  else: return self._labels[ilayer].components
1064
1128
  else:
1065
1129
  return self._labels[ilayer] if isinstance(self._labels[ilayer], list) \
@@ -1143,21 +1207,41 @@ class Circuit(object):
1143
1207
  `layers` is a single integer and as a `Circuit` otherwise.
1144
1208
  Note: if you want a `Circuit` when only selecting one layer,
1145
1209
  set `layers` to a slice or tuple containing just a single index.
1210
+ Note that the returned circuit doesn't retain any original
1211
+ metadata, such as the compilable layer indices or occurence id.
1146
1212
  """
1147
1213
  nonint_layers = not isinstance(layers, int)
1148
1214
 
1149
1215
  #Shortcut for common case when lines == None and when we're only taking a layer slice/index
1150
- if lines is None:
1151
- assert(layers is not None)
1152
- if nonint_layers is False: return self.layertup[layers]
1153
- if isinstance(layers, slice) and strict is True: # if strict=False, then need to recompute line labels
1154
- return Circuit._fastinit(self._labels[layers], self.line_labels, not self._static)
1216
+ if lines is None and layers is not None:
1217
+ if self._static:
1218
+ if not nonint_layers:
1219
+ return self._labels[layers]
1220
+ if isinstance(layers, slice) and strict is True: # if strict=False, then need to recompute line labels
1221
+ #can speed this up a measurably by manually computing the new hashable tuple value and hash
1222
+ if not self._line_labels in (('*',), ()):
1223
+ new_hashable_tup = self._labels[layers] + ('@',) + self._line_labels
1224
+ else:
1225
+ new_hashable_tup = self._labels[layers]
1226
+ ret = Circuit.__new__(Circuit)
1227
+ return ret._copy_init(self._labels[layers], self._line_labels, not self._static, hashable_tup= new_hashable_tup, precomp_hash=hash(new_hashable_tup))
1228
+ else:
1229
+ if not nonint_layers:
1230
+ return self.layertup[layers]
1231
+ if isinstance(layers, slice) and strict is True: # if strict=False, then need to recompute line labels
1232
+ return Circuit._fastinit(self._labels[layers], self._line_labels, not self._static)
1233
+ #otherwise assert both are not None:
1234
+
1155
1235
 
1156
1236
  layers = self._proc_layers_arg(layers)
1157
1237
  lines = self._proc_lines_arg(lines)
1158
1238
  if len(layers) == 0 or len(lines) == 0:
1159
- return Circuit._fastinit(() if self._static else [],
1160
- lines, not self._static) if nonint_layers else None # zero-area region
1239
+ if self._static:
1240
+ return Circuit._fastinit((), tuple(lines), False) # zero-area region
1241
+ else:
1242
+ return Circuit._fastinit(() if self._static else [],
1243
+ tuple(lines) if self._static else lines,
1244
+ not self._static) # zero-area region
1161
1245
 
1162
1246
  ret = []
1163
1247
  if self._static:
@@ -1173,7 +1257,7 @@ class Circuit(object):
1173
1257
  ## add in special case of identity layer
1174
1258
  #if (isinstance(l,_Label) and l.name == self.identity): # ~ is_identity_layer(l)
1175
1259
  # ret_layer.append(l); continue
1176
- sslbls = set(self.line_labels) # otherwise, treat None sslbs as *all* labels
1260
+ sslbls = set(self._line_labels) # otherwise, treat None sslbs as *all* labels
1177
1261
  else:
1178
1262
  sslbls = set(sslbls)
1179
1263
  if (strict and sslbls.issubset(lines)) or \
@@ -1184,7 +1268,10 @@ class Circuit(object):
1184
1268
  if nonint_layers:
1185
1269
  if not strict: lines = "auto" # since we may have included lbls on other lines
1186
1270
  # don't worry about string rep for now...
1187
- return Circuit._fastinit(tuple(ret) if self._static else ret, lines, not self._static)
1271
+
1272
+ return Circuit._fastinit(tuple(ret) if self._static else ret,
1273
+ tuple(lines) if self._static else lines,
1274
+ not self._static)
1188
1275
  else:
1189
1276
  return _Label(ret[0])
1190
1277
 
@@ -1248,9 +1335,9 @@ class Circuit(object):
1248
1335
  lbls_sslbls = None if (lbls.sslbls is None) else set(lbls.sslbls)
1249
1336
  else:
1250
1337
  if isinstance(lbls, Circuit):
1251
- assert(set(lbls.line_labels).issubset(self.line_labels)), \
1338
+ assert(set(lbls._line_labels).issubset(self._line_labels)), \
1252
1339
  "Assigned circuit has lines (%s) not contained in this circuit (%s)!" \
1253
- % (str(lbls.line_labels), str(self.line_labels))
1340
+ % (str(lbls._line_labels), str(self._line_labels))
1254
1341
  lbls = lbls.layertup # circuit layer labels as a tuple
1255
1342
  assert(isinstance(lbls, (tuple, list))), \
1256
1343
  ("When assigning to a layer range (even w/len=1) `lbls` "
@@ -1273,18 +1360,18 @@ class Circuit(object):
1273
1360
  # the lines being assigned. If sslbl != None, then the labels must be
1274
1361
  # contained within the line labels being assigned (unless we're allowed to expand)
1275
1362
  if lbls_sslbls is not None:
1276
- new_line_labels = set(lbls_sslbls) - set(self.line_labels)
1363
+ new_line_labels = set(lbls_sslbls) - set(self._line_labels)
1277
1364
  if all_lines: # then allow new lines to be added
1278
1365
  if len(new_line_labels) > 0:
1279
- self._line_labels = self.line_labels + tuple(sorted(new_line_labels)) # sort?
1366
+ self._line_labels = self._line_labels + tuple(sorted(new_line_labels)) # sort?
1280
1367
  else:
1281
1368
  assert(len(new_line_labels) == 0), "Cannot add new lines %s" % str(new_line_labels)
1282
1369
  assert(set(lbls_sslbls).issubset(lines)), \
1283
1370
  "Unallowed state space labels: %s" % str(set(lbls_sslbls) - set(lines))
1284
1371
 
1285
- assert(set(lines).issubset(self.line_labels)), \
1372
+ assert(set(lines).issubset(self._line_labels)), \
1286
1373
  ("Specified lines (%s) must be a subset of this circuit's lines"
1287
- " (%s).") % (str(lines), str(self.line_labels))
1374
+ " (%s).") % (str(lines), str(self._line_labels))
1288
1375
 
1289
1376
  #remove all labels in block to be assigned
1290
1377
  self._clear_labels(layers, lines)
@@ -1367,10 +1454,10 @@ class Circuit(object):
1367
1454
  self._labels.insert(insert_before, [])
1368
1455
 
1369
1456
  #Shift compilable layer indices as needed
1370
- if len(self._compilable_layer_indices_tup) > 0: # then begins with __CMPLBL__
1457
+ if self._compilable_layer_indices_tup:
1371
1458
  shifted_inds = [i if (i < insert_before) else (i + num_to_insert)
1372
- for i in self._compilable_layer_indices_tup[1:]]
1373
- self._compilable_layer_indices_tup = ('__CMPLBL__',) + tuple(shifted_inds)
1459
+ for i in self._compilable_layer_indices_tup]
1460
+ self._compilable_layer_indices_tup = tuple(shifted_inds)
1374
1461
 
1375
1462
  else: # insert layers only on given lines - shift existing labels to right
1376
1463
  for i in range(num_to_insert):
@@ -1413,6 +1500,7 @@ class Circuit(object):
1413
1500
  -------
1414
1501
  None
1415
1502
  """
1503
+ assert(not self._static), "Cannot edit a read-only circuit!"
1416
1504
  self.insert_idling_layers_inplace(None, num_to_insert, lines)
1417
1505
 
1418
1506
  def insert_labels_into_layers(self, lbls, layer_to_insert_before, lines=None):
@@ -1481,6 +1569,7 @@ class Circuit(object):
1481
1569
  -------
1482
1570
  None
1483
1571
  """
1572
+ assert(not self._static), "Cannot edit a read-only circuit!"
1484
1573
  if isinstance(lbls, Circuit): lbls = tuple(lbls)
1485
1574
  # lbls is expected to be a list/tuple of Label-like items, one per inserted layer
1486
1575
  lbls = tuple(map(to_label, lbls))
@@ -1530,13 +1619,12 @@ class Circuit(object):
1530
1619
  -------
1531
1620
  None
1532
1621
  """
1533
- #assert(not self._static),"Cannot edit a read-only circuit!"
1534
- # Actually, this is OK even for static circuits because it won't affect the hashed value (labels only)
1622
+ assert(not self._static),"Cannot edit a read-only circuit!"
1535
1623
  if insert_before is None:
1536
- i = len(self.line_labels)
1624
+ i = len(self._line_labels)
1537
1625
  else:
1538
- i = self.line_labels.index(insert_before)
1539
- self._line_labels = self.line_labels[0:i] + tuple(line_labels) + self.line_labels[i:]
1626
+ i = self._line_labels.index(insert_before)
1627
+ self._line_labels = self._line_labels[0:i] + tuple(line_labels) + self._line_labels[i:]
1540
1628
 
1541
1629
  def _append_idling_lines(self, line_labels):
1542
1630
  """
@@ -1589,6 +1677,8 @@ class Circuit(object):
1589
1677
  -------
1590
1678
  None
1591
1679
  """
1680
+ assert(not self._static), "Cannot edit a read-only circuit!"
1681
+
1592
1682
  if layer_to_insert_before is None: layer_to_insert_before = 0
1593
1683
  elif layer_to_insert_before < 0: layer_to_insert_before = len(self._labels) + layer_to_insert_before
1594
1684
 
@@ -1598,7 +1688,7 @@ class Circuit(object):
1598
1688
  elif line_labels == "auto":
1599
1689
  line_labels = tuple(sorted(_accumulate_explicit_sslbls(lbls)))
1600
1690
 
1601
- existing_labels = set(line_labels).intersection(self.line_labels)
1691
+ existing_labels = set(line_labels).intersection(self._line_labels)
1602
1692
  if len(existing_labels) > 0:
1603
1693
  raise ValueError("Cannot insert line(s) labeled %s - they already exist!" % str(existing_labels))
1604
1694
 
@@ -1692,7 +1782,7 @@ class Circuit(object):
1692
1782
  new_layer = []
1693
1783
  for l in self._layer_components(i): # loop over labels in this layer
1694
1784
  sslbls = _sslbls_of_nested_lists_of_simple_labels(l)
1695
- sslbls = set(self.line_labels) if (sslbls is None) else set(sslbls)
1785
+ sslbls = set(self._line_labels) if (sslbls is None) else set(sslbls)
1696
1786
  if len(sslbls.intersection(lines)) == 0:
1697
1787
  new_layer.append(l)
1698
1788
  elif not clear_straddlers and not sslbls.issubset(lines):
@@ -1747,12 +1837,12 @@ class Circuit(object):
1747
1837
  del self._labels[i]
1748
1838
 
1749
1839
  #Shift compilable layer indices as needed
1750
- if len(self._compilable_layer_indices_tup) > 0: # begins with __CMPLBL__
1840
+ if self._compilable_layer_indices_tup:
1751
1841
  deleted_indices = set(layers)
1752
- new_inds = list(filter(lambda x: x not in deleted_indices, self._compilable_layer_indices_tup[1:]))
1842
+ new_inds = [x for x in self._compilable_layer_indices_tup if x not in deleted_indices]
1753
1843
  for deleted_i in reversed(sorted(deleted_indices)):
1754
1844
  new_inds = [i if (i < deleted_i) else (i - 1) for i in new_inds] # Note i never == deleted_i (filtered)
1755
- self._compilable_layer_indices_tup = ('__CMPLBL__',) + tuple(new_inds)
1845
+ self._compilable_layer_indices_tup = tuple(new_inds)
1756
1846
 
1757
1847
  def delete_lines(self, lines, delete_straddlers=False):
1758
1848
  """
@@ -1784,7 +1874,7 @@ class Circuit(object):
1784
1874
  raise ValueError(("Cannot remove a block that is straddled by "
1785
1875
  "%s when `delete_straddlers` == False!") % _Label(l))
1786
1876
  self._labels[i] = new_layer
1787
- self._line_labels = tuple([x for x in self.line_labels if x not in lines])
1877
+ self._line_labels = tuple([x for x in self._line_labels if x not in lines])
1788
1878
 
1789
1879
  def __getitem__(self, key):
1790
1880
  layers, lines = self._proc_key_arg(key)
@@ -1898,7 +1988,7 @@ class Circuit(object):
1898
1988
  if len(lbl.components) == 0: # special case of an empty-layer label,
1899
1989
  serial_lbls.append(lbl) # which we serialize as an atomic object
1900
1990
  serial_lbls.extend(list(lbl.components) * lbl.reps)
1901
- return Circuit._fastinit(tuple(serial_lbls), self.line_labels, editable=False, occurrence=self.occurrence)
1991
+ return Circuit._fastinit(tuple(serial_lbls), self._line_labels, editable=False, occurrence=self.occurrence)
1902
1992
 
1903
1993
  def parallelize(self, can_break_labels=True, adjacent_only=False):
1904
1994
  """
@@ -1984,7 +2074,7 @@ class Circuit(object):
1984
2074
 
1985
2075
  # Convert elements of `parallel_lbls` into Labels (needed b/c we use _fastinit below)
1986
2076
  parallel_lbls = [_Label(lbl_list) if len(lbl_list) != 1 else lbl_list[0] for lbl_list in parallel_lbls]
1987
- return Circuit._fastinit(tuple(parallel_lbls), self.line_labels, editable=False, occurrence=self.occurrence)
2077
+ return Circuit._fastinit(tuple(parallel_lbls), self._line_labels, editable=False, occurrence=self._occurrence_id)
1988
2078
 
1989
2079
  def expand_subcircuits_inplace(self):
1990
2080
  """
@@ -1998,25 +2088,41 @@ class Circuit(object):
1998
2088
  None
1999
2089
  """
2000
2090
  assert(not self._static), "Cannot edit a read-only circuit!"
2001
-
2002
- #Iterate in reverse so we don't have to deal with
2003
- # added layers.
2091
+
2092
+ #_subcircuits_to_expand returns list of tuples
2093
+ #with the circuits to expand. The first entry of each tuple
2094
+ #is the index of the layer, with the rest of the entries the
2095
+ #CircuitLabels to expand. And these indices are given in descending
2096
+ #order.
2097
+ subcircs_to_expand = self._subcircuits_to_expand()
2098
+ while subcircs_to_expand:
2099
+ for subcirc_tup in subcircs_to_expand:
2100
+ layer_idx = subcirc_tup[0]
2101
+ subcircs = subcirc_tup[1:]
2102
+ #want a different notion of depth than that of CircuitLabel, since that depth
2103
+ #is calculated recursively, and we're handling the recursion manually.
2104
+ length_components = [len(l.components)*l.reps for l in subcircs]
2105
+ layers_to_add = max(0, *[comp_len - 1 for comp_len in length_components])
2106
+ if layers_to_add:
2107
+ self.insert_idling_layers_inplace(layer_idx + 1, layers_to_add)
2108
+ for depth, subc in zip(length_components, subcircs):
2109
+ self.clear_labels(slice(layer_idx, layer_idx + depth), subc.sslbls) # remove the CircuitLabel
2110
+ self.set_labels(subc.components * subc.reps, slice(layer_idx, layer_idx + depth), subc.sslbls) # dump in the contents
2111
+ #loop back through the circuit and see if we need to take another pass.
2112
+ subcircs_to_expand = self._subcircuits_to_expand()
2113
+
2114
+ def _subcircuits_to_expand(self):
2115
+ #Return this as a list of sparse list of tuples, giving only the layers which
2116
+ #contain CircuitLabels to be expanded. The first entry of the tuple will be the
2117
+ #original layer index, and the will be ordered in descending value to perform
2118
+ #expansion in reverse.
2119
+ subckts_to_expand = []
2004
2120
  for i in reversed(range(len(self._labels))):
2005
- circuits_to_expand = []
2006
- layers_to_add = 0
2007
-
2008
- for l in self._layer_components(i): # loop over labels in this layer
2009
- if isinstance(l, _CircuitLabel):
2010
- circuits_to_expand.append(l)
2011
- layers_to_add = max(layers_to_add, l.depth - 1)
2012
-
2013
- if layers_to_add > 0:
2014
- self.insert_idling_layers_inplace(i + 1, layers_to_add)
2015
- for subc in circuits_to_expand:
2016
- self.clear_labels(slice(i, i + subc.depth), subc.sslbls) # remove the CircuitLabel
2017
- self.set_labels(subc.components * subc.reps, slice(i, i + subc.depth),
2018
- subc.sslbls) # dump in the contents
2019
-
2121
+ subckts_to_expand_for_layer = [l for l in self._labels[i] if isinstance(l, _CircuitLabel)]
2122
+ if subckts_to_expand_for_layer:
2123
+ subckts_to_expand.append(tuple([i]+subckts_to_expand_for_layer))
2124
+ return subckts_to_expand
2125
+
2020
2126
  def expand_subcircuits(self):
2021
2127
  """
2022
2128
  Returns a new circuit with :class:`CircuitLabel` labels expanded.
@@ -2054,13 +2160,11 @@ class Circuit(object):
2054
2160
  while iEnd < nLayers and self._labels[iStart] == self._labels[iEnd]:
2055
2161
  iEnd += 1
2056
2162
  nreps = iEnd - iStart
2057
- #print("Start,End = ",iStart,iEnd)
2058
2163
  if nreps <= 1: # just move to next layer
2059
2164
  iStart += 1; continue # nothing to do
2060
2165
 
2061
2166
  #Construct a sub-circuit label that repeats layer[iStart] nreps times
2062
2167
  # and stick it at layer iStart
2063
- #print("Constructing %d reps at %d" % (nreps, iStart))
2064
2168
  repCircuit = _CircuitLabel('', self._labels[iStart], None, nreps)
2065
2169
  self.clear_labels(iStart, None) # remove existing labels (unnecessary?)
2066
2170
  self.set_labels(repCircuit, iStart, None)
@@ -2068,7 +2172,6 @@ class Circuit(object):
2068
2172
  iStart += nreps # advance iStart to next unprocessed layer inde
2069
2173
 
2070
2174
  if len(iLayersToRemove) > 0:
2071
- #print("Removing layers: ",iLayersToRemove)
2072
2175
  self.delete_layers(iLayersToRemove)
2073
2176
 
2074
2177
  def insert_layer(self, circuit_layer, j):
@@ -2121,7 +2224,7 @@ class Circuit(object):
2121
2224
  None
2122
2225
  """
2123
2226
  assert(not self._static), "Cannot edit a read-only circuit!"
2124
- if self.line_labels is None or self.line_labels == ():
2227
+ if self._line_labels is None or self._line_labels == ():
2125
2228
  #Allow insertion of a layer into an empty circuit to update the circuit's line_labels
2126
2229
  layer_lbl = to_label(circuit_layer)
2127
2230
  self.line_labels = layer_lbl.sslbls if (layer_lbl.sslbls is not None) else ('*',)
@@ -2181,8 +2284,8 @@ class Circuit(object):
2181
2284
  """
2182
2285
  assert(not self._static), "Cannot edit a read-only circuit!"
2183
2286
  lines_to_insert = []
2184
- for line_lbl in circuit.line_labels:
2185
- if line_lbl in self.line_labels:
2287
+ for line_lbl in circuit._line_labels:
2288
+ if line_lbl in self._line_labels:
2186
2289
  lines_to_insert.append(line_lbl)
2187
2290
  else:
2188
2291
  assert(circuit._is_line_idling(line_lbl)), \
@@ -2225,6 +2328,7 @@ class Circuit(object):
2225
2328
  -------
2226
2329
  None
2227
2330
  """
2331
+ assert(not self._static), "Cannot edit a read-only circuit!"
2228
2332
  self.insert_circuit_inplace(circuit, self.num_layers)
2229
2333
 
2230
2334
  def prefix_circuit(self, circuit):
@@ -2261,6 +2365,7 @@ class Circuit(object):
2261
2365
  -------
2262
2366
  None
2263
2367
  """
2368
+ assert(not self._static), "Cannot edit a read-only circuit!"
2264
2369
  self.insert_circuit_inplace(circuit, 0)
2265
2370
 
2266
2371
  def tensor_circuit_inplace(self, circuit, line_order=None):
@@ -2290,12 +2395,12 @@ class Circuit(object):
2290
2395
  #assert(self.identity == circuit.identity), "The identity labels must be the same!"
2291
2396
 
2292
2397
  #Construct new line labels (of final circuit)
2293
- overlap = set(self.line_labels).intersection(circuit.line_labels)
2398
+ overlap = set(self._line_labels).intersection(circuit._line_labels)
2294
2399
  if len(overlap) > 0:
2295
2400
  raise ValueError(
2296
2401
  "The line labels of `circuit` and this Circuit must be distinct, but overlap = %s!" % str(overlap))
2297
2402
 
2298
- all_line_labels = set(self.line_labels + circuit.line_labels)
2403
+ all_line_labels = set(self._line_labels + circuit._line_labels)
2299
2404
  if line_order is not None:
2300
2405
  line_order_set = set(line_order)
2301
2406
  if len(line_order_set) != len(line_order):
@@ -2311,7 +2416,7 @@ class Circuit(object):
2311
2416
 
2312
2417
  new_line_labels = line_order
2313
2418
  else:
2314
- new_line_labels = self.line_labels + circuit.line_labels
2419
+ new_line_labels = self._line_labels + circuit._line_labels
2315
2420
 
2316
2421
  #Add circuit's labels into this circuit
2317
2422
  self.insert_labels_as_lines_inplace(circuit._labels, line_labels=circuit.line_labels)
@@ -2361,6 +2466,7 @@ class Circuit(object):
2361
2466
  -------
2362
2467
  None
2363
2468
  """
2469
+ assert(not self._static), "Cannot edit a read-only circuit!"
2364
2470
  del self[j]
2365
2471
  self.insert_labels_into_layers_inplace(circuit, j)
2366
2472
 
@@ -2449,7 +2555,7 @@ class Circuit(object):
2449
2555
  return cpy
2450
2556
  else: # static case: so self._labels is a tuple of Labels
2451
2557
  return Circuit([lbl.replace_name(old_gatename, new_gatename)
2452
- for lbl in self._labels], self.line_labels, occurrence=self.occurrence)
2558
+ for lbl in self._labels], self._line_labels, occurrence=self._occurrence_id)
2453
2559
 
2454
2560
  def replace_gatename_with_idle_inplace(self, gatename):
2455
2561
  """
@@ -2525,12 +2631,14 @@ class Circuit(object):
2525
2631
  #Could to this in both cases, but is slow for large static circuits
2526
2632
  cpy = self.copy(editable=False) # convert our layers to Labels
2527
2633
  return Circuit._fastinit(tuple([new_layer if lbl == old_layer else lbl
2528
- for lbl in cpy._labels]), self.line_labels, editable=False,
2529
- occurrence=self.occurrence, compilable_layer_indices=self.compilable_layer_indices)
2634
+ for lbl in cpy._labels]), self._line_labels, editable=False,
2635
+ occurrence=self._occurrence_id,
2636
+ compilable_layer_indices_tup=self._compilable_layer_indices_tup)
2530
2637
  else: # static case: so self._labels is a tuple of Labels
2531
2638
  return Circuit(tuple([new_layer if lbl == old_layer else lbl
2532
- for lbl in self._labels]), self.line_labels, editable=False,
2533
- occurrence=self.occurrence, compilable_layer_indices=self.compilable_layer_indices)
2639
+ for lbl in self._labels]), self._line_labels, editable=False,
2640
+ occurrence=self._occurrence_id,
2641
+ compilable_layer_indices=self._compilable_layer_indices_tup)
2534
2642
 
2535
2643
  def replace_layers_with_aliases(self, alias_dict):
2536
2644
  """
@@ -2557,34 +2665,7 @@ class Circuit(object):
2557
2665
  while label in layers:
2558
2666
  i = layers.index(label)
2559
2667
  layers = layers[:i] + c._labels + layers[i + 1:]
2560
- return Circuit._fastinit(layers, self.line_labels, editable=False, occurrence=self.occurrence)
2561
-
2562
- #def replace_identity(self, identity, convert_identity_gates = True): # THIS module only
2563
- # """
2564
- # Changes the *name* of the idle/identity gate in the circuit. This replaces
2565
- # the name of the identity element in the circuit by setting self.identity = identity.
2566
- # If `convert_identity_gates` is True, this also changes the names of all the gates that
2567
- # had the old self.identity name.
2568
- #
2569
- # Parameters
2570
- # ----------
2571
- # identity : string
2572
- # The new name for the identity gate.
2573
- #
2574
- # convert_identity_gates : bool, optional
2575
- # If True, all gates that had the old identity name are converted to the new identity
2576
- # name. Otherwise, they keep the old name, and the circuit nolonger considers them to
2577
- # be identity gates.
2578
- #
2579
- # Returns
2580
- # -------
2581
- # None
2582
- # """
2583
- # if convert_identity_gates:
2584
- # self.replace_gatename(self.identity, identity)
2585
- #
2586
- # self._tup_dirty = self._str_dirty = True
2587
- # self.identity = identity
2668
+ return Circuit._fastinit(layers, self._line_labels, editable=False, occurrence=self._occurrence_id)
2588
2669
 
2589
2670
  def change_gate_library(self, compilation, allowed_filter=None, allow_unchanged_gates=False, depth_compression=True,
2590
2671
  one_q_gate_relations=None):
@@ -2715,7 +2796,7 @@ class Circuit(object):
2715
2796
 
2716
2797
  def map_names(obj): # obj is either a simple label or a list
2717
2798
  if isinstance(obj, _Label):
2718
- if obj.is_simple(): # *simple* label
2799
+ if obj.IS_SIMPLE: # *simple* label
2719
2800
  new_name = mapper_func(obj.name)
2720
2801
  newobj = _Label(new_name, obj.sslbls) \
2721
2802
  if (new_name is not None) else obj
@@ -2747,7 +2828,7 @@ class Circuit(object):
2747
2828
  def mapper_func(line_label): return mapper[line_label] \
2748
2829
  if isinstance(mapper, dict) else mapper
2749
2830
 
2750
- self._line_labels = tuple((mapper_func(l) for l in self.line_labels))
2831
+ self._line_labels = tuple((mapper_func(l) for l in self._line_labels))
2751
2832
 
2752
2833
  def map_sslbls(obj): # obj is either a simple label or a list
2753
2834
  if isinstance(obj, _Label):
@@ -2776,9 +2857,9 @@ class Circuit(object):
2776
2857
  """
2777
2858
  def mapper_func(line_label): return mapper[line_label] \
2778
2859
  if isinstance(mapper, dict) else mapper(line_label)
2779
- mapped_line_labels = tuple(map(mapper_func, self.line_labels))
2860
+ mapped_line_labels = tuple(map(mapper_func, self._line_labels))
2780
2861
  return Circuit([l.map_state_space_labels(mapper_func) for l in self.layertup],
2781
- mapped_line_labels, None, not self._static, occurrence=self.occurrence)
2862
+ mapped_line_labels, None, not self._static, occurrence=self._occurrence_id)
2782
2863
 
2783
2864
  def reorder_lines_inplace(self, order):
2784
2865
  """
@@ -2797,7 +2878,7 @@ class Circuit(object):
2797
2878
  None
2798
2879
  """
2799
2880
  assert(not self._static), "Cannot edit a read-only circuit!"
2800
- assert(set(order) == set(self.line_labels)), "The line labels must be the same!"
2881
+ assert(set(order) == set(self._line_labels)), "The line labels must be the same!"
2801
2882
  self._line_labels = tuple(order)
2802
2883
 
2803
2884
  def reorder_lines(self, order):
@@ -2842,10 +2923,9 @@ class Circuit(object):
2842
2923
  True if the line is idling. False otherwise.
2843
2924
  """
2844
2925
  if self._static:
2845
- layers = list(filter(lambda x: x not in idle_layer_labels, self._labels)) \
2846
- if idle_layer_labels else self._labels
2926
+ layers = [x for x in self._labels if x not in idle_layer_labels] if idle_layer_labels else self._labels
2847
2927
  all_sslbls = None if any([layer.sslbls is None for layer in layers]) \
2848
- else set(_itertools.chain(*[layer.sslbls for layer in layers]))
2928
+ else set([sslbl for layer in layers for sslbl in layer.sslbls])
2849
2929
  else:
2850
2930
  all_sslbls = _sslbls_of_nested_lists_of_simple_labels(self._labels, idle_layer_labels) # None or a set
2851
2931
 
@@ -2870,17 +2950,16 @@ class Circuit(object):
2870
2950
  tuple
2871
2951
  """
2872
2952
  if self._static:
2873
- layers = list(filter(lambda x: x not in idle_layer_labels, self._labels)) \
2874
- if idle_layer_labels else self._labels
2953
+ layers = [x for x in self._labels if x not in idle_layer_labels] if idle_layer_labels else self._labels
2875
2954
  all_sslbls = None if any([layer.sslbls is None for layer in layers]) \
2876
- else set(_itertools.chain(*[layer.sslbls for layer in layers]))
2955
+ else set([sslbl for layer in layers for sslbl in layer.sslbls])
2877
2956
  else:
2878
2957
  all_sslbls = _sslbls_of_nested_lists_of_simple_labels(self._labels, idle_layer_labels) # None or a set
2879
2958
 
2880
2959
  if all_sslbls is None:
2881
2960
  return ()
2882
2961
  else:
2883
- return tuple([x for x in self.line_labels
2962
+ return tuple([x for x in self._line_labels
2884
2963
  if x not in all_sslbls]) # preserve order
2885
2964
 
2886
2965
  def delete_idling_lines_inplace(self, idle_layer_labels=None):
@@ -2899,17 +2978,15 @@ class Circuit(object):
2899
2978
  -------
2900
2979
  None
2901
2980
  """
2902
- #assert(not self._static),"Cannot edit a read-only circuit!"
2903
- # Actually, this is OK even for static circuits because it won't affect the hashed value (labels only)
2981
+ assert(not self._static),"Cannot edit a read-only circuit!"
2904
2982
 
2905
2983
  if idle_layer_labels:
2906
2984
  assert(all([to_label(x).sslbls is None for x in idle_layer_labels])), "Idle layer labels must be *global*"
2907
2985
 
2908
2986
  if self._static:
2909
- layers = list(filter(lambda x: x not in idle_layer_labels, self._labels)) \
2910
- if idle_layer_labels else self._labels
2987
+ layers = [x for x in self._labels if x not in idle_layer_labels] if idle_layer_labels else self._labels
2911
2988
  all_sslbls = None if any([layer.sslbls is None for layer in layers]) \
2912
- else set(_itertools.chain(*[layer.sslbls for layer in layers]))
2989
+ else set([sslbl for layer in layers for sslbl in layer.sslbls])
2913
2990
  else:
2914
2991
  all_sslbls = _sslbls_of_nested_lists_of_simple_labels(self._labels, idle_layer_labels) # None or a set
2915
2992
 
@@ -2918,7 +2995,7 @@ class Circuit(object):
2918
2995
 
2919
2996
  #All we need to do is update line_labels since there aren't any labels
2920
2997
  # to remove in self._labels (as all the lines are idling)
2921
- self._line_labels = tuple([x for x in self.line_labels
2998
+ self._line_labels = tuple([x for x in self._line_labels
2922
2999
  if x in all_sslbls]) # preserve order
2923
3000
 
2924
3001
  def delete_idling_lines(self, idle_layer_labels=None):
@@ -2964,6 +3041,7 @@ class Circuit(object):
2964
3041
  -------
2965
3042
  None
2966
3043
  """
3044
+ assert(not self._static), "Cannot edit a read-only circuit!"
2967
3045
  self.clear_labels(lines=line_label, clear_straddlers=clear_straddlers)
2968
3046
 
2969
3047
  def reverse_inplace(self):
@@ -2978,10 +3056,10 @@ class Circuit(object):
2978
3056
  self._labels = list(reversed(self._labels)) # reverses the layer order
2979
3057
  #FUTURE: would need to reverse_inplace each layer too, if layer can have *sublayers*
2980
3058
 
2981
- if len(self._compilable_layer_indices_tup) > 0: # begins with __CMPLBL__
3059
+ if self._compilable_layer_indices_tup:
2982
3060
  depth = len(self._labels)
2983
- self._compilable_layer_indices_tup = ('__CMPLBL__',) \
2984
- + tuple([(depth - 1 - i) for i in self._compilable_layer_indices_tup[1:]])
3061
+ self._compilable_layer_indices_tup = \
3062
+ tuple([(depth - 1 - i) for i in self._compilable_layer_indices_tup])
2985
3063
 
2986
3064
  def _combine_one_q_gates_inplace(self, one_q_gate_relations):
2987
3065
  """
@@ -3027,7 +3105,6 @@ class Circuit(object):
3027
3105
  productive = True
3028
3106
 
3029
3107
  while productive: # keep iterating
3030
- #print("BEGIN ITER")
3031
3108
  productive = False
3032
3109
  # Loop through all the qubits, to try and compress squences of 1-qubit gates on the qubit in question.
3033
3110
  for ilayer in range(0, len(self._labels) - 1):
@@ -3042,7 +3119,6 @@ class Circuit(object):
3042
3119
  for b, lblB in enumerate(layerB_comps):
3043
3120
  if isinstance(lblB, _Label) and lblB.sslbls == lblA.sslbls:
3044
3121
  #queue an apply rule if one exists
3045
- #print("CHECK for: ", (lblA.name,lblB.name))
3046
3122
  if (lblA.name, lblB.name) in one_q_gate_relations:
3047
3123
  new_Aname = one_q_gate_relations[lblA.name, lblB.name]
3048
3124
  applies.append((a, b, new_Aname, lblA.sslbls))
@@ -3095,17 +3171,14 @@ class Circuit(object):
3095
3171
  # Keeps track of whether any changes have been made to the circuit.
3096
3172
  compression_implemented = False
3097
3173
 
3098
- #print("BEGIN")
3099
3174
  used_lines = {}
3100
3175
  for icurlayer in range(len(self._labels)):
3101
- #print("LAYER ",icurlayer)
3102
3176
  #Slide labels in current layer to left ("forward")
3103
3177
  icomps_to_remove = []; used_lines[icurlayer] = set()
3104
3178
  for icomp, lbl in enumerate(self._layer_components(icurlayer)):
3105
3179
  #see if we can move this label forward
3106
- #print("COMP%d: %s" % (icomp,str(lbl)))
3107
3180
  sslbls = _sslbls_of_nested_lists_of_simple_labels(lbl)
3108
- if sslbls is None: sslbls = self.line_labels
3181
+ if sslbls is None: sslbls = self._line_labels
3109
3182
 
3110
3183
  dest_layer = icurlayer
3111
3184
  while dest_layer > 0 and len(used_lines[dest_layer - 1].intersection(sslbls)) == 0:
@@ -3114,14 +3187,11 @@ class Circuit(object):
3114
3187
  icomps_to_remove.append(icomp) # remove this label from current layer
3115
3188
  self._append_layer_component(dest_layer, lbl) # add it to the destination layer
3116
3189
  used_lines[dest_layer].update(sslbls) # update used_lines at dest layer
3117
- #print(" <-- layer %d (used=%s)" % (dest_layer,str(used_lines[dest_layer])))
3118
3190
  else:
3119
3191
  #can't move this label forward - update used_lines of current layer
3120
3192
  used_lines[icurlayer].update(sslbls) # update used_lines at dest layer
3121
- #print(" can't move: (cur layer used=%s)" % (str(used_lines[icurlayer])))
3122
-
3193
+
3123
3194
  #Remove components in current layer which were pushed forward
3124
- #print("Removing ",icomps_to_remove," from layer ",icurlayer)
3125
3195
  for icomp in reversed(icomps_to_remove):
3126
3196
  self._remove_layer_component(icurlayer, icomp)
3127
3197
 
@@ -3291,11 +3361,11 @@ class Circuit(object):
3291
3361
  layer_lbl = self.layer_label(j) # (a Label)
3292
3362
  if layer_lbl.sslbls is None:
3293
3363
  if layer_lbl == (): # special case - the completely empty layer: sslbls=None but needs padding
3294
- return _Label([_Label(idle_gate_name, line_lbl) for line_lbl in self.line_labels])
3364
+ return _Label([_Label(idle_gate_name, line_lbl) for line_lbl in self._line_labels])
3295
3365
  return layer_lbl # all qubits used - no idles to pad
3296
3366
 
3297
3367
  components = list(layer_lbl.components)
3298
- for line_lbl in self.line_labels:
3368
+ for line_lbl in self._line_labels:
3299
3369
  if line_lbl not in layer_lbl.sslbls:
3300
3370
  components.append(_Label(idle_gate_name, line_lbl))
3301
3371
  return _Label(components)
@@ -3346,7 +3416,7 @@ class Circuit(object):
3346
3416
  -------
3347
3417
  int
3348
3418
  """
3349
- return len(self.line_labels)
3419
+ return len(self._line_labels)
3350
3420
 
3351
3421
  @property
3352
3422
  def size(self):
@@ -3365,14 +3435,14 @@ class Circuit(object):
3365
3435
  #TODO HERE -update from here down b/c of sub-circuit blocks
3366
3436
  if self._static:
3367
3437
  def size(lbl): # obj a Label, perhaps compound
3368
- if lbl.is_simple(): # a simple label
3369
- return len(lbl.sslbls) if (lbl.sslbls is not None) else len(self.line_labels)
3438
+ if lbl.IS_SIMPLE: # a simple label
3439
+ return len(lbl.sslbls) if (lbl.sslbls is not None) else len(self._line_labels)
3370
3440
  else:
3371
3441
  return sum([size(sublbl) for sublbl in lbl.components])
3372
3442
  else:
3373
3443
  def size(obj): # obj is either a simple label or a list
3374
- if isinstance(obj, _Label): # all Labels are simple labels
3375
- return len(obj.sslbls) if (obj.sslbls is not None) else len(self.line_labels)
3444
+ if isinstance(obj, _Label): # all Labels in editable format are simple labels
3445
+ return len(obj.sslbls) if (obj.sslbls is not None) else len(self._line_labels)
3376
3446
  else:
3377
3447
  return sum([size(sub) for sub in obj])
3378
3448
 
@@ -3400,6 +3470,30 @@ class Circuit(object):
3400
3470
  """
3401
3471
  return self.num_nq_gates(2)
3402
3472
 
3473
+ @property
3474
+ def num_gates(self):
3475
+ """
3476
+ The number of gates in the circuit.
3477
+
3478
+ Returns
3479
+ -------
3480
+ int
3481
+ """
3482
+ if self._static:
3483
+ def cnt(lbl): # obj a Label, perhaps compound
3484
+ if lbl.IS_SIMPLE: # a simple label
3485
+ return 1 if (lbl.sslbls is not None) else 0
3486
+ else:
3487
+ return sum([cnt(sublbl) for sublbl in lbl.components])
3488
+ else:
3489
+ def cnt(obj): # obj is either a simple label or a list
3490
+ if isinstance(obj, _Label): # all Labels are simple labels
3491
+ return 1 if (obj.sslbls is not None) else 0
3492
+ else:
3493
+ return sum([cnt(sub) for sub in obj])
3494
+
3495
+ return sum([cnt(layer_lbl) for layer_lbl in self._labels])
3496
+
3403
3497
  def num_nq_gates(self, nq):
3404
3498
  """
3405
3499
  The number of `nq`-qubit gates in the circuit.
@@ -3420,7 +3514,7 @@ class Circuit(object):
3420
3514
  """
3421
3515
  if self._static:
3422
3516
  def cnt(lbl): # obj a Label, perhaps compound
3423
- if lbl.is_simple(): # a simple label
3517
+ if lbl.IS_SIMPLE: # a simple label
3424
3518
  return 1 if (lbl.sslbls is not None) and (len(lbl.sslbls) == nq) else 0
3425
3519
  else:
3426
3520
  return sum([cnt(sublbl) for sublbl in lbl.components])
@@ -3448,7 +3542,7 @@ class Circuit(object):
3448
3542
  """
3449
3543
  if self._static:
3450
3544
  def cnt(lbl): # obj a Label, perhaps compound
3451
- if lbl.is_simple(): # a simple label
3545
+ if lbl.IS_SIMPLE: # a simple label
3452
3546
  return 1 if (lbl.sslbls is not None) and (len(lbl.sslbls) >= 2) else 0
3453
3547
  else:
3454
3548
  return sum([cnt(sublbl) for sublbl in lbl.components])
@@ -3460,52 +3554,17 @@ class Circuit(object):
3460
3554
  return sum([cnt(sub) for sub in obj])
3461
3555
 
3462
3556
  return sum([cnt(layer_lbl) for layer_lbl in self._labels])
3463
-
3464
- # UNUSED
3465
- #def predicted_error_probability(self, gate_error_probabilities):
3466
- # """
3467
- # Predicts the probability that one or more errors occur in the circuit
3468
- # if the gates have the error probabilities specified by in the input
3469
- # dictionary. Given correct error rates for the gates and stochastic errors,
3470
- # this is predictive of the probability of an error in the circuit. But note
3471
- # that that is generally *not* the same as the probability that the circuit
3472
- # implemented is incorrect (e.g., stochastic errors can cancel).
3473
- #
3474
- # Parameters
3475
- # ----------
3476
- # gate_error_probabilities : dict
3477
- # A dictionary where the keys are the labels that appear in the circuit, and
3478
- # the value is the error probability for that gate.
3479
- #
3480
- # Returns
3481
- # -------
3482
- # float
3483
- # The probability that there is one or more errors in the circuit.
3484
- # """
3485
- # f = 1.
3486
- # depth = self.num_layers
3487
- # for i in range(0,self.num_lines):
3488
- # for j in range(0,depth):
3489
- # gatelbl = self.line_items[i][j]
3490
- #
3491
- # # So that we don't include multi-qubit gates more than once.
3492
- # if gatelbl.qubits is None:
3493
- # if i == 0:
3494
- # f = f*(1-gate_error_probabilities[gatelbl])
3495
- # elif gatelbl.qubits[0] == self.line_labels[i]:
3496
- # f = f*(1-gate_error_probabilities[gatelbl])
3497
- # return 1 - f
3498
-
3557
+
3499
3558
  def _togrid(self, identity_name):
3500
3559
  """ return a list-of-lists rep? """
3501
3560
  d = self.num_layers
3502
- line_items = [[_Label(identity_name, ll)] * d for ll in self.line_labels]
3561
+ line_items = [[_Label(identity_name, ll)] * d for ll in self._line_labels]
3503
3562
 
3504
3563
  for ilayer in range(len(self._labels)):
3505
3564
  for layercomp in self._layer_components(ilayer):
3506
3565
  if isinstance(layercomp, _Label):
3507
3566
  comp_label = layercomp
3508
- if layercomp.is_simple():
3567
+ if layercomp.IS_SIMPLE:
3509
3568
  comp_sslbls = layercomp.sslbls
3510
3569
  else:
3511
3570
  #We can't intelligently flatten compound labels that occur within a layer-label yet...
@@ -3513,9 +3572,9 @@ class Circuit(object):
3513
3572
  else: # layercomp must be a list (and _static == False)
3514
3573
  comp_label = _Label(layercomp)
3515
3574
  comp_sslbls = _sslbls_of_nested_lists_of_simple_labels(layercomp)
3516
- if comp_sslbls is None: comp_sslbls = self.line_labels
3575
+ if comp_sslbls is None: comp_sslbls = self._line_labels
3517
3576
  for sslbl in comp_sslbls:
3518
- lineIndx = self.line_labels.index(sslbl) # replace w/dict for speed...
3577
+ lineIndx = self._line_labels.index(sslbl) # replace w/dict for speed...
3519
3578
  line_items[lineIndx][ilayer] = comp_label
3520
3579
  return line_items
3521
3580
 
@@ -3534,7 +3593,7 @@ class Circuit(object):
3534
3593
 
3535
3594
  def abbrev(lbl, k): # assumes a simple label w/ name & qubits
3536
3595
  """ Returns what to print on line 'k' for label 'lbl' """
3537
- lbl_qubits = lbl.qubits if (lbl.qubits is not None) else self.line_labels
3596
+ lbl_qubits = lbl.qubits if (lbl.qubits is not None) else self._line_labels
3538
3597
  nqubits = len(lbl_qubits)
3539
3598
  if nqubits == 1 and lbl.name is not None:
3540
3599
  if isinstance(lbl, _CircuitLabel): # HACK
@@ -3544,12 +3603,12 @@ class Circuit(object):
3544
3603
  else:
3545
3604
  return lbl.name
3546
3605
  elif lbl.name in ('CNOT', 'Gcnot') and nqubits == 2: # qubit indices = (control,target)
3547
- if k == self.line_labels.index(lbl_qubits[0]):
3606
+ if k == self._line_labels.index(lbl_qubits[0]):
3548
3607
  return Ctxt + str(lbl_qubits[1])
3549
3608
  else:
3550
3609
  return Ttxt + str(lbl_qubits[0])
3551
3610
  elif lbl.name in ('CPHASE', 'Gcphase') and nqubits == 2:
3552
- if k == self.line_labels.index(lbl_qubits[0]):
3611
+ if k == self._line_labels.index(lbl_qubits[0]):
3553
3612
  otherqubit = lbl_qubits[1]
3554
3613
  else:
3555
3614
  otherqubit = lbl_qubits[0]
@@ -3564,11 +3623,11 @@ class Circuit(object):
3564
3623
  for i in range(0, self.num_lines)])
3565
3624
  for j in range(0, self.num_layers)]
3566
3625
 
3567
- max_linelabellen = max([len(str(llabel)) for llabel in self.line_labels])
3626
+ max_linelabellen = max([len(str(llabel)) for llabel in self._line_labels])
3568
3627
 
3569
3628
  for i in range(self.num_lines):
3570
- s += 'Qubit {} '.format(self.line_labels[i]) + ' ' * \
3571
- (max_linelabellen - len(str(self.line_labels[i]))) + '---'
3629
+ s += 'Qubit {} '.format(self._line_labels[i]) + ' ' * \
3630
+ (max_linelabellen - len(str(self._line_labels[i]))) + '---'
3572
3631
  for j, maxlbllen in enumerate(max_labellen):
3573
3632
  if line_items[i][j].name == identityName:
3574
3633
  # Replace with special idle print at some point
@@ -3677,7 +3736,7 @@ class Circuit(object):
3677
3736
  # The quantum wire for qubit q
3678
3737
  circuit_for_q = self.line_items[q]
3679
3738
  for gate in circuit_for_q:
3680
- gate_qubits = gate.qubits if (gate.qubits is not None) else self.line_labels
3739
+ gate_qubits = gate.qubits if (gate.qubits is not None) else self._line_labels
3681
3740
  nqubits = len(gate_qubits)
3682
3741
  if gate.name == self.identity:
3683
3742
  qstring += r' \qw &'
@@ -3744,6 +3803,12 @@ class Circuit(object):
3744
3803
  gatename_conversion = _itgs.standard_gatenames_cirq_conversions()
3745
3804
  if wait_duration is not None:
3746
3805
  gatename_conversion[idle_gate_name] = cirq.WaitGate(wait_duration)
3806
+ #conversion does not work is the line labels are none, or the line labels are not a subset
3807
+ #of the keys for qubit_conversion (indicating there isn't a corresponding mapping into cirq objects).
3808
+ msg1 = 'Conversion to cirq does not work with circuits w/placeholder * line label.'
3809
+ msg2 = 'Missing qubit conversions, some line labels have no corresponding cirq conversion in qubit_conversions.'
3810
+ assert self._line_labels != ('*',), msg1
3811
+ assert set(self._line_labels).issubset(set(qubit_conversion.keys())), msg2
3747
3812
 
3748
3813
  moments = []
3749
3814
  for i in range(self.num_layers):
@@ -3751,15 +3816,195 @@ class Circuit(object):
3751
3816
  operations = []
3752
3817
  for gate in layer:
3753
3818
  operation = gatename_conversion[gate.name]
3754
- if operation is None:
3755
- # This happens if no idle gate it specified because
3756
- # standard_gatenames_cirq_conversions maps 'Gi' to `None`
3757
- continue
3758
3819
  qubits = map(qubit_conversion.get, gate.qubits)
3759
3820
  operations.append(operation.on(*qubits))
3760
3821
  moments.append(cirq.Moment(operations))
3761
3822
 
3762
3823
  return cirq.Circuit(moments)
3824
+
3825
+ @classmethod
3826
+ def from_cirq(cls, circuit, qubit_conversion=None, cirq_gate_conversion= None,
3827
+ remove_implied_idles = True, global_idle_replacement_label = 'auto'):
3828
+ """
3829
+ Converts and instantiates a pyGSTi Circuit object from a Cirq Circuit object.
3830
+
3831
+ Parameters
3832
+ ----------
3833
+ circuit : cirq Circuit
3834
+ The cirq Circuit object to parse into a pyGSTi circuit.
3835
+
3836
+ qubit_conversion : dict, optional (default None)
3837
+ A dictionary specifying a mapping between cirq qubit objects and
3838
+ pyGSTi qubit labels (either integers or strings).
3839
+ If None, then a default mapping is created.
3840
+
3841
+ cirq_gate_conversion : dict, optional (default None)
3842
+ If specified a dictionary with keys given by cirq gate objects,
3843
+ and values given by pygsti gate names which overrides the built-in
3844
+ conversion dictionary used by default.
3845
+
3846
+ remove_implied_idles : bool, optional (default True)
3847
+ A flag indicating whether to remove explicit idles
3848
+ that are part of a circuit layer containing
3849
+ other explicitly specified gates
3850
+ (i.e., whether to abide by the normal pyGSTi implicit idle convention).
3851
+
3852
+ global_idle_replacement_label : string or Label or None, optional (default 'auto')
3853
+ An option specified for the handling of global idle layers.
3854
+ If None, no replacement of global idle layers is performed and a verbatim
3855
+ conversion from the cirq layer is performed.
3856
+ If the string 'auto', then the behavior is to replace global idle layers with
3857
+ the gate label Label(()), which is the special syntax for the global
3858
+ idle layer, stylized typically as '[]'. If another string then replace with a
3859
+ gate label with the specified name acting on all of the qubits
3860
+ appearing in the cirq circuit. If a Label object, use this directly,
3861
+ this does not check for compatibility so it is up to the user to ensure
3862
+ the labels are compatible.
3863
+
3864
+ Returns
3865
+ -------
3866
+ pygsti_circuit
3867
+ A pyGSTi Circuit instance equivalent to the specified Cirq one.
3868
+ """
3869
+
3870
+ try:
3871
+ import cirq
3872
+ except ImportError:
3873
+ raise ImportError("Cirq is required for this operation, and it does not appear to be installed.")
3874
+
3875
+ #mapping between cirq gates and pygsti gate names:
3876
+ if cirq_gate_conversion is not None:
3877
+ cirq_to_gate_name_mapping = cirq_gate_conversion
3878
+ else:
3879
+ cirq_to_gate_name_mapping = _itgs.cirq_gatenames_standard_conversions()
3880
+
3881
+ #get all of the qubits in the cirq Circuit
3882
+ all_cirq_qubits = circuit.all_qubits()
3883
+
3884
+ #ensure all of these have a conversion available.
3885
+ if qubit_conversion is not None:
3886
+ assert set(all_cirq_qubits).issubset(set(qubit_conversion.keys())), 'Missing cirq to pygsti conversions for some qubit label(s).'
3887
+ #if it is None, build a default mapping.
3888
+ else:
3889
+ #default mapping is currently hardcoded for the conventions of either cirwq's
3890
+ #NamedQubit, LineQubit or GridQubit classes, other types will raise an error.
3891
+ qubit_conversion = {}
3892
+ for qubit in all_cirq_qubits:
3893
+ if isinstance(qubit, cirq.NamedQubit):
3894
+ qubit_conversion[qubit] = f'Q{qubit.name}'
3895
+ elif isinstance(qubit, cirq.LineQubit):
3896
+ qubit_conversion[qubit] = f'Q{qubit.x}'
3897
+ elif isinstance(qubit, cirq.GridQubit):
3898
+ qubit_conversion[qubit] = f'Q{qubit.row}_{qubit.col}'
3899
+ else:
3900
+ msg = 'Unsupported cirq qubit type. Currently only support for automatically creating'\
3901
+ +'a default cirq qubit to pygsti qubit label mapping for NamedQubit, LineQubit and GridQubit.'
3902
+ raise ValueError(msg)
3903
+
3904
+ #In cirq the equivalent concept to a layer in a pygsti circuit is a Moment.
3905
+ #Circuits consist of ordered lists of moments corresponding to a set of
3906
+ #operations applied at that abstract time slice.
3907
+ #cirq Circuits can be sliced and iterated over. Iterating returns each contained
3908
+ #Moment in sequence. Slicing returns a new circuit corresponding to the
3909
+ #selected layers.
3910
+
3911
+ #initialize empty list of pygsti circuit layers
3912
+ circuit_layers = []
3913
+
3914
+ #initialize a flag for indicating that we've seen a global idle to use later.
3915
+ seen_global_idle = False
3916
+
3917
+ #Iterate through each of the moments and build up layers Moment by Moment.
3918
+ for moment in circuit:
3919
+ #if the length of the tuple of operations for this moment in
3920
+ #moment.operations is length 1, then we'll add the operation to
3921
+ #the pygsti circuit as a bare gate label (i.e. not wrapped in a layer label
3922
+ #indicating parallel gates). Otherwise, we'll iterate through and add them
3923
+ #as a layer label.
3924
+ if len(moment.operations) == 1:
3925
+ op = moment.operations[0]
3926
+ try:
3927
+ name = cirq_to_gate_name_mapping[op.gate]
3928
+ except KeyError:
3929
+ msg = 'Could not find matching standard gate name in provided dictionary. Falling back to try and find a'\
3930
+ +' unitary from standard_gatename_unitaries which matches up to a global phase.'
3931
+ _warnings.warn(msg)
3932
+ name = _itgs.unitary_to_standard_gatename(op.gate._unitary_(), up_to_phase=True)
3933
+ assert name is not None, 'Could not find a matching standard gate name for conversion.'
3934
+ sslbls = tuple(qubit_conversion[qubit] for qubit in op.qubits)
3935
+ #global idle handling:
3936
+ if name == 'Gi' and global_idle_replacement_label:
3937
+ #set a flag indicating that we've seen a global idle to use later.
3938
+ seen_global_idle = True
3939
+ if isinstance(global_idle_replacement_label, str):
3940
+ if global_idle_replacement_label == 'auto':
3941
+ #append the default.
3942
+ circuit_layers.append(_Label(()))
3943
+ else:
3944
+ circuit_layers.append(_Label(global_idle_replacement_label,
3945
+ tuple(sorted([qubit_conversion[qubit] for qubit in all_cirq_qubits]))))
3946
+ elif isinstance(global_idle_replacement_label, _Label):
3947
+ circuit_layers.append(global_idle_replacement_label)
3948
+ else:
3949
+ circuit_layers.append(_Label(name, state_space_labels = sslbls))
3950
+
3951
+ else:
3952
+ #initialize sublist for layer label elements
3953
+ layer_label_elems = []
3954
+ #iterate through each of the operations in this moment
3955
+ for op in moment.operations:
3956
+ try:
3957
+ name = cirq_to_gate_name_mapping[op.gate]
3958
+ except KeyError:
3959
+ msg = 'Could not find matching standard gate name in provided dictionary. Falling back to try and find a'\
3960
+ +' unitary from standard_gatename_unitaries which matches up to a global phase.'
3961
+ _warnings.warn(msg)
3962
+ name = _itgs.unitary_to_standard_gatename(op.gate._unitary_(), up_to_phase=True)
3963
+ assert name is not None, 'Could not find a matching standard gate name for conversion.'
3964
+ sslbls = tuple(qubit_conversion[qubit] for qubit in op.qubits)
3965
+ layer_label_elems.append(_Label(name, state_space_labels = sslbls))
3966
+
3967
+ #add special handling for global idle circuits and implied idels based on flags.
3968
+ layer_label_elem_names = [elem.name for elem in layer_label_elems]
3969
+ all_idles = all([name == 'Gi' for name in layer_label_elem_names])
3970
+
3971
+ if global_idle_replacement_label and all_idles:
3972
+ #set a flag indicating that we've seen a global idle to use later.
3973
+ seen_global_idle = True
3974
+ #if global idle is a string, replace this layer with the user specified one:
3975
+ if isinstance(global_idle_replacement_label, str):
3976
+ if global_idle_replacement_label == 'auto':
3977
+ #append the default.
3978
+ circuit_layers.append(_Label(()))
3979
+ else:
3980
+ circuit_layers.append(_Label(global_idle_replacement_label,
3981
+ tuple(sorted([qubit_conversion[qubit] for qubit in all_cirq_qubits]))))
3982
+ elif isinstance(global_idle_replacement_label, _Label):
3983
+ circuit_layers.append(global_idle_replacement_label)
3984
+ #check whether any of the elements are implied idles, and if so use flag
3985
+ #to determine whether to include them. We have already checked if this layer
3986
+ #is a global idle, so if not then we only need to check if any of the layer
3987
+ #elements are implied idles.
3988
+ elif remove_implied_idles and 'Gi' in layer_label_elem_names and not all_idles:
3989
+ stripped_layer_label_elems = [elem for elem in layer_label_elems
3990
+ if not elem.name == 'Gi']
3991
+ #if this is length one then add this to the circuit as a bare label, otherwise
3992
+ #add as a layer label.
3993
+ if len(stripped_layer_label_elems)==1:
3994
+ circuit_layers.append(stripped_layer_label_elems[0])
3995
+ else:
3996
+ circuit_layers.append(_Label(stripped_layer_label_elems))
3997
+ #otherwise, just add this layer as-is.
3998
+ else:
3999
+ circuit_layers.append(_Label(layer_label_elems))
4000
+
4001
+ #if any of the circuit layers are global idles, then we'll force the circuit line
4002
+ #labels to include all of the qubits appearing in the cirq circuit, otherwise
4003
+ #we'll let the Circuit constructor figure this out.
4004
+ if seen_global_idle:
4005
+ return cls(circuit_layers, line_labels = tuple(sorted([qubit_conversion[qubit] for qubit in all_cirq_qubits])))
4006
+ else:
4007
+ return cls(circuit_layers)
3763
4008
 
3764
4009
  def convert_to_quil(self,
3765
4010
  num_qubits=None,
@@ -3826,19 +4071,19 @@ class Circuit(object):
3826
4071
  # To tell us whether we have found a standard qubit labelling type.
3827
4072
  standardtype = False
3828
4073
  # Must first check they are strings, because cannot query q[0] for int q.
3829
- if all([isinstance(q, str) for q in self.line_labels]):
3830
- if all([q[0] == 'Q' for q in self.line_labels]):
4074
+ if all([isinstance(q, str) for q in self._line_labels]):
4075
+ if all([q[0] == 'Q' for q in self._line_labels]):
3831
4076
  standardtype = True
3832
- qubit_conversion = {llabel: int(llabel[1:]) for llabel in self.line_labels}
3833
- if all([isinstance(q, int) for q in self.line_labels]):
3834
- qubit_conversion = {q: q for q in self.line_labels}
4077
+ qubit_conversion = {llabel: int(llabel[1:]) for llabel in self._line_labels}
4078
+ if all([isinstance(q, int) for q in self._line_labels]):
4079
+ qubit_conversion = {q: q for q in self._line_labels}
3835
4080
  standardtype = True
3836
4081
  if not standardtype:
3837
4082
  raise ValueError(
3838
4083
  "No standard qubit labelling conversion is available! Please provide `qubit_conversion`.")
3839
4084
 
3840
4085
  if num_qubits is None:
3841
- num_qubits = len(self.line_labels)
4086
+ num_qubits = len(self._line_labels)
3842
4087
 
3843
4088
  # Init the quil string.
3844
4089
  quil = ''
@@ -3866,7 +4111,7 @@ class Circuit(object):
3866
4111
 
3867
4112
  # Go through the (non-self.identity) gates in the layer and convert them to quil
3868
4113
  for gate in layer.components:
3869
- gate_qubits = gate.qubits if (gate.qubits is not None) else self.line_labels
4114
+ gate_qubits = gate.qubits if (gate.qubits is not None) else self._line_labels
3870
4115
  assert(len(gate_qubits) <= 2 or gate.qubits is None), \
3871
4116
  'Gate on more than 2 qubits given; this is currently not supported!'
3872
4117
 
@@ -3904,7 +4149,7 @@ class Circuit(object):
3904
4149
  qubits_used.extend(gate_qubits)
3905
4150
 
3906
4151
  # All gates that don't have a non-idle gate acting on them get an idle in the layer.
3907
- for q in self.line_labels:
4152
+ for q in self._line_labels:
3908
4153
  if q not in qubits_used:
3909
4154
  quil += 'I' + ' ' + str(qubit_conversion[q]) + '\n'
3910
4155
 
@@ -3919,11 +4164,11 @@ class Circuit(object):
3919
4164
 
3920
4165
  # Add in a measurement at the end.
3921
4166
  if readout_conversion is None:
3922
- for q in self.line_labels:
4167
+ for q in self._line_labels:
3923
4168
  # quil += "MEASURE {0} [{1}]\n".format(str(qubit_conversion[q]),str(qubit_conversion[q]))
3924
4169
  quil += "MEASURE {0} ro[{1}]\n".format(str(qubit_conversion[q]), str(qubit_conversion[q]))
3925
4170
  else:
3926
- for q in self.line_labels:
4171
+ for q in self._line_labels:
3927
4172
  quil += "MEASURE {0} ro[{1}]\n".format(str(qubit_conversion[q]), str(readout_conversion[q]))
3928
4173
 
3929
4174
  return quil
@@ -4003,22 +4248,19 @@ class Circuit(object):
4003
4248
  # To tell us whether we have found a standard qubit labelling type.
4004
4249
  standardtype = False
4005
4250
  # Must first check they are strings, because cannot query q[0] for int q.
4006
- if all([isinstance(q, str) for q in self.line_labels]):
4007
- if all([q[0] == 'Q' for q in self.line_labels]):
4251
+ if all([isinstance(q, str) for q in self._line_labels]):
4252
+ if all([q[0] == 'Q' for q in self._line_labels]):
4008
4253
  standardtype = True
4009
- qubit_conversion = {llabel: int(llabel[1:]) for llabel in self.line_labels}
4010
- if all([isinstance(q, int) for q in self.line_labels]):
4011
- qubit_conversion = {q: q for q in self.line_labels}
4254
+ qubit_conversion = {llabel: int(llabel[1:]) for llabel in self._line_labels}
4255
+ if all([isinstance(q, int) for q in self._line_labels]):
4256
+ qubit_conversion = {q: q for q in self._line_labels}
4012
4257
  standardtype = True
4013
4258
  if not standardtype:
4014
4259
  raise ValueError(
4015
4260
  "No standard qubit labelling conversion is available! Please provide `qubit_conversion`.")
4016
4261
 
4017
4262
  if num_qubits is None:
4018
- num_qubits = len(self.line_labels)
4019
-
4020
- # if gateargs_map is None:
4021
- # gateargs_map = {}
4263
+ num_qubits = len(self._line_labels)
4022
4264
 
4023
4265
  #Currently only using 'Iz' as valid intermediate measurement ('IM') label.
4024
4266
  #Todo: Expand to all intermediate measurements.
@@ -4037,6 +4279,9 @@ class Circuit(object):
4037
4279
  # Include a delay instruction
4038
4280
  openqasm += 'opaque delay(t) q;\n\n'
4039
4281
 
4282
+ # Add a template for ECR commands that we will replace/remove later
4283
+ openqasm += "ECRPLACEHOLDER"
4284
+
4040
4285
  openqasm += 'qreg q[{0}];\n'.format(str(num_qubits))
4041
4286
  # openqasm += 'creg cr[{0}];\n'.format(str(num_qubits))
4042
4287
  openqasm += 'creg cr[{0}];\n'.format(str(num_qubits + num_IMs))
@@ -4054,7 +4299,7 @@ class Circuit(object):
4054
4299
 
4055
4300
  # Go through the (non-self.identity) gates in the layer and convert them to openqasm
4056
4301
  for gate in layer.components:
4057
- gate_qubits = gate.qubits if (gate.qubits is not None) else self.line_labels
4302
+ gate_qubits = gate.qubits if (gate.qubits is not None) else self._line_labels
4058
4303
  assert(len(gate_qubits) <= 2), 'Gates on more than 2 qubits given; this is currently not supported!'
4059
4304
 
4060
4305
  # Find the openqasm for the gate.
@@ -4077,9 +4322,9 @@ class Circuit(object):
4077
4322
  openqasm_for_gate += subopenqasm_for_gate + ' q[' + str(qubit_conversion[q]) + '];\n'
4078
4323
  if block_between_gates:
4079
4324
  openqasm_for_gate += 'barrier '
4080
- for q in self.line_labels[:-1]:
4325
+ for q in self._line_labels[:-1]:
4081
4326
  openqasm_for_gate += 'q[{0}], '.format(str(qubit_conversion[q]))
4082
- openqasm_for_gate += 'q[{0}];\n'.format(str(qubit_conversion[self.line_labels[-1]]))
4327
+ openqasm_for_gate += 'q[{0}];\n'.format(str(qubit_conversion[self._line_labels[-1]]))
4083
4328
 
4084
4329
  else:
4085
4330
  openqasm_for_gate += subopenqasm_for_gate
@@ -4090,9 +4335,9 @@ class Circuit(object):
4090
4335
  openqasm_for_gate += ';\n'
4091
4336
  if block_between_gates:
4092
4337
  openqasm_for_gate += 'barrier '
4093
- for q in self.line_labels[:-1]:
4338
+ for q in self._line_labels[:-1]:
4094
4339
  openqasm_for_gate += 'q[{0}], '.format(str(qubit_conversion[q]))
4095
- openqasm_for_gate += 'q[{0}];\n'.format(str(qubit_conversion[self.line_labels[-1]]))
4340
+ openqasm_for_gate += 'q[{0}];\n'.format(str(qubit_conversion[self._line_labels[-1]]))
4096
4341
 
4097
4342
  else:
4098
4343
  assert len(gate.qubits) == 1
@@ -4112,7 +4357,7 @@ class Circuit(object):
4112
4357
 
4113
4358
  # All gates that don't have a non-idle gate acting on them get an idle in the layer.
4114
4359
  if not block_between_gates and include_delay_on_idle:
4115
- for q in self.line_labels:
4360
+ for q in self._line_labels:
4116
4361
  if q not in qubits_used:
4117
4362
  # Delay 0 works because of the barrier
4118
4363
  # In OpenQASM3, this should probably be a stretch instead
@@ -4126,19 +4371,27 @@ class Circuit(object):
4126
4371
  # where pragma blocks should be.
4127
4372
  if block_between_layers:
4128
4373
  openqasm += 'barrier '
4129
- for q in self.line_labels[:-1]:
4374
+ for q in self._line_labels[:-1]:
4130
4375
  openqasm += 'q[{0}], '.format(str(qubit_conversion[q]))
4131
- openqasm += 'q[{0}];\n'.format(str(qubit_conversion[self.line_labels[-1]]))
4376
+ openqasm += 'q[{0}];\n'.format(str(qubit_conversion[self._line_labels[-1]]))
4132
4377
  # openqasm += ';'
4133
4378
 
4134
4379
  # Add in a measurement at the end.
4135
- for q in self.line_labels:
4380
+ for q in self._line_labels:
4136
4381
  # openqasm += "measure q[{0}] -> cr[{1}];\n".format(str(qubit_conversion[q]), str(qubit_conversion[q]))
4137
4382
  openqasm += "measure q[{0}] -> cr[{1}];\n".format(str(qubit_conversion[q]),
4138
4383
  str(num_IMs_used + qubit_conversion[q]))
4384
+
4385
+ # Replace ECR placeholder
4386
+ ecr_replace_str = ""
4387
+ if 'ecr' in openqasm:
4388
+ ecr_replace_str = "gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(param0) q1; cx q0,q1; h q1; }\n"
4389
+ ecr_replace_str += "gate ecr q0,q1 { rzx(pi/4) q0,q1; x q0; rzx(-pi/4) q0,q1; }\n\n"
4390
+ openqasm = openqasm.replace("ECRPLACEHOLDER", ecr_replace_str)
4139
4391
 
4140
4392
  return openqasm
4141
-
4393
+
4394
+ @_deprecate_fn('Model.probabilites or Model.sim.probs')
4142
4395
  def simulate(self, model, return_all_outcomes=False):
4143
4396
  """
4144
4397
  Compute the outcome probabilities of this Circuit using `model` as a model for the gates.
@@ -4202,108 +4455,10 @@ class Circuit(object):
4202
4455
  """
4203
4456
  if not self._static:
4204
4457
  self._static = True
4205
- self._labels = tuple([_Label(layer_lbl) for layer_lbl in self._labels])
4206
-
4207
- def expand_instruments_and_separate_povm(self, model, observed_outcomes=None):
4208
- """
4209
- Creates a dictionary of :class:`SeparatePOVMCircuit` objects from expanding the instruments of this circuit.
4210
-
4211
- Each key of the returned dictionary replaces the instruments in this circuit with a selection
4212
- of their members. (The size of the resulting dictionary is the product of the sizes of
4213
- each instrument appearing in this circuit when `observed_outcomes is None`). Keys are stored
4214
- as :class:`SeparatePOVMCircuit` objects so it's easy to keep track of which POVM outcomes (effects)
4215
- correspond to observed data. This function is, for the most part, used internally to process
4216
- a circuit before computing its outcome probabilities.
4217
-
4218
- Parameters
4219
- ----------
4220
- model : Model
4221
- The model used to provide necessary details regarding the expansion, including:
4222
-
4223
- - default SPAM layers
4224
- - definitions of instrument-containing layers
4225
- - expansions of individual instruments and POVMs
4226
-
4227
- Returns
4228
- -------
4229
- OrderedDict
4230
- A dict whose keys are :class:`SeparatePOVMCircuit` objects and whose
4231
- values are tuples of the outcome labels corresponding to this circuit,
4232
- one per POVM effect held in the key.
4233
- """
4234
- complete_circuit = model.complete_circuit(self)
4235
- expanded_circuit_outcomes = _collections.OrderedDict()
4236
- povm_lbl = complete_circuit[-1] # "complete" circuits always end with a POVM label
4237
- circuit_without_povm = complete_circuit[0:len(complete_circuit) - 1]
4238
-
4239
- def create_tree(lst):
4240
- subs = _collections.OrderedDict()
4241
- for el in lst:
4242
- if len(el) > 0:
4243
- if el[0] not in subs: subs[el[0]] = []
4244
- subs[el[0]].append(el[1:])
4245
- return _collections.OrderedDict([(k, create_tree(sub_lst)) for k, sub_lst in subs.items()])
4246
-
4247
- def add_expanded_circuit_outcomes(circuit, running_outcomes, ootree, start):
4248
- """
4249
- """
4250
- cir = circuit if start == 0 else circuit[start:] # for performance, avoid uneeded slicing
4251
- for k, layer_label in enumerate(cir, start=start):
4252
- components = layer_label.components
4253
- #instrument_inds = _np.nonzero([model._is_primitive_instrument_layer_lbl(component)
4254
- # for component in components])[0] # SLOWER than statement below
4255
- instrument_inds = _np.array([i for i, component in enumerate(components)
4256
- if model._is_primitive_instrument_layer_lbl(component)])
4257
- if instrument_inds.size > 0:
4258
- # This layer contains at least one instrument => recurse with instrument(s) replaced with
4259
- # all combinations of their members.
4260
- component_lookup = {i: comp for i, comp in enumerate(components)}
4261
- instrument_members = [model._member_labels_for_instrument(components[i])
4262
- for i in instrument_inds] # also components of outcome labels
4263
- for selected_instrmt_members in _itertools.product(*instrument_members):
4264
- expanded_layer_lbl = component_lookup.copy()
4265
- expanded_layer_lbl.update({i: components[i] + "_" + sel
4266
- for i, sel in zip(instrument_inds, selected_instrmt_members)})
4267
- expanded_layer_lbl = _Label([expanded_layer_lbl[i] for i in range(len(components))])
4268
-
4269
- if ootree is not None:
4270
- new_ootree = ootree
4271
- for sel in selected_instrmt_members:
4272
- new_ootree = new_ootree.get(sel, {})
4273
- if len(new_ootree) == 0: continue # no observed outcomes along this outcome-tree path
4274
- else:
4275
- new_ootree = None
4276
-
4277
- add_expanded_circuit_outcomes(circuit[0:k] + Circuit((expanded_layer_lbl,)) + circuit[k + 1:],
4278
- running_outcomes + selected_instrmt_members, new_ootree, k + 1)
4279
- break
4280
-
4281
- else: # no more instruments to process: `cir` contains no instruments => add an expanded circuit
4282
- assert(circuit not in expanded_circuit_outcomes) # shouldn't be possible to generate duplicates...
4283
- elabels = model._effect_labels_for_povm(povm_lbl) if (observed_outcomes is None) \
4284
- else tuple(ootree.keys())
4285
- outcomes = tuple((running_outcomes + (elabel,) for elabel in elabels))
4286
- expanded_circuit_outcomes[SeparatePOVMCircuit(circuit, povm_lbl, elabels)] = outcomes
4287
-
4288
- ootree = create_tree(observed_outcomes) if observed_outcomes is not None else None # tree of observed outcomes
4289
- # e.g. [('0','00'), ('0','01'), ('1','10')] ==> {'0': {'00': {}, '01': {}}, '1': {'10': {}}}
4290
-
4291
- if model._has_instruments():
4292
- add_expanded_circuit_outcomes(circuit_without_povm, (), ootree, start=0)
4293
- else:
4294
- # It may be helpful to cache the set of elabels for a POVM (maybe within the model?) because
4295
- # currently the call to _effect_labels_for_povm may be a bottleneck. It's needed, even when we have
4296
- # observed outcomes, because there may be some observed outcomes that aren't modeled (e.g. leakage states)
4297
- if observed_outcomes is None:
4298
- elabels = model._effect_labels_for_povm(povm_lbl)
4299
- else:
4300
- possible_lbls = set(model._effect_labels_for_povm(povm_lbl))
4301
- elabels = tuple([oo for oo in ootree.keys() if oo in possible_lbls])
4302
- outcomes = tuple(((elabel,) for elabel in elabels))
4303
- expanded_circuit_outcomes[SeparatePOVMCircuit(circuit_without_povm, povm_lbl, elabels)] = outcomes
4304
-
4305
- return expanded_circuit_outcomes
4306
-
4458
+ self._labels = tuple([layer_lbl if isinstance(layer_lbl, _Label)
4459
+ else _Label(layer_lbl) for layer_lbl in self._labels])
4460
+ self._hashable_tup = self.tup
4461
+ self._hash = hash(self._hashable_tup)
4307
4462
 
4308
4463
  class CompressedCircuit(object):
4309
4464
  """
@@ -4358,7 +4513,7 @@ class CompressedCircuit(object):
4358
4513
  self._tup = CompressedCircuit.compress_op_label_tuple(
4359
4514
  circuit.layertup, min_len_to_compress, max_period_to_look_for)
4360
4515
  self._str = circuit.str
4361
- self._line_labels = circuit.line_labels
4516
+ self._line_labels = circuit._line_labels
4362
4517
  self._occurrence_id = circuit.occurrence
4363
4518
 
4364
4519
  def __getstate__(self):
@@ -4492,12 +4647,35 @@ class SeparatePOVMCircuit(object):
4492
4647
  """
4493
4648
  def __init__(self, circuit_without_povm, povm_label, effect_labels):
4494
4649
  self.circuit_without_povm = circuit_without_povm
4495
- self.povm_label = povm_label
4496
- self.effect_labels = effect_labels
4650
+ self._povm_label = povm_label
4651
+ self._effect_labels = effect_labels
4652
+ self._full_effect_labels = tuple([(self.povm_label + "_" + el) for el in self._effect_labels])
4497
4653
 
4498
4654
  @property
4499
4655
  def full_effect_labels(self):
4500
- return [(self.povm_label + "_" + el) for el in self.effect_labels]
4656
+ return self._full_effect_labels
4657
+
4658
+ @property
4659
+ def effect_labels(self):
4660
+ return self._effect_labels
4661
+
4662
+ @property
4663
+ def povm_label(self):
4664
+ return self._povm_label
4665
+
4666
+ @effect_labels.setter
4667
+ def effect_labels(self, value):
4668
+ self._effect_labels = value
4669
+ self._full_effect_labels = tuple([(self._povm_label + "_" + el) for el in value])
4670
+
4671
+ @povm_label.setter
4672
+ def povm_label(self, value):
4673
+ self._povm_label = value
4674
+ self._full_effect_labels = tuple([(value + "_" + el) for el in self._effect_labels])
4675
+
4676
+ @full_effect_labels.setter
4677
+ def full_effect_labels(self, value):
4678
+ self._full_effect_labels = value
4501
4679
 
4502
4680
  def __len__(self):
4503
4681
  return len(self.circuit_without_povm) # don't count POVM in length, so slicing works as expected
@@ -4512,4 +4690,3 @@ class SeparatePOVMCircuit(object):
4512
4690
  return "SeparatePOVM(" + self.circuit_without_povm.str + "," \
4513
4691
  + str(self.povm_label) + "," + str(self.effect_labels) + ")"
4514
4692
 
4515
- #LATER: add a method for getting the "POVM_effect" labels?