pyGSTi 0.9.12__cp310-cp310-win_amd64.whl → 0.9.13__cp310-cp310-win_amd64.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (225) hide show
  1. pyGSTi-0.9.13.dist-info/METADATA +197 -0
  2. {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/RECORD +211 -220
  3. {pyGSTi-0.9.12.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 +62 -35
  7. pygsti/algorithms/fiducialpairreduction.py +95 -110
  8. pygsti/algorithms/fiducialselection.py +17 -8
  9. pygsti/algorithms/gaugeopt.py +2 -2
  10. pygsti/algorithms/germselection.py +87 -77
  11. pygsti/algorithms/mirroring.py +0 -388
  12. pygsti/algorithms/randomcircuit.py +165 -1333
  13. pygsti/algorithms/rbfit.py +0 -234
  14. pygsti/baseobjs/basis.py +94 -396
  15. pygsti/baseobjs/errorgenbasis.py +0 -132
  16. pygsti/baseobjs/errorgenspace.py +0 -10
  17. pygsti/baseobjs/label.py +52 -168
  18. pygsti/baseobjs/opcalc/fastopcalc.cp310-win_amd64.pyd +0 -0
  19. pygsti/baseobjs/opcalc/fastopcalc.pyx +2 -2
  20. pygsti/baseobjs/polynomial.py +13 -595
  21. pygsti/baseobjs/protectedarray.py +72 -132
  22. pygsti/baseobjs/statespace.py +1 -0
  23. pygsti/circuits/__init__.py +1 -1
  24. pygsti/circuits/circuit.py +753 -504
  25. pygsti/circuits/circuitconstruction.py +0 -4
  26. pygsti/circuits/circuitlist.py +47 -5
  27. pygsti/circuits/circuitparser/__init__.py +8 -8
  28. pygsti/circuits/circuitparser/fastcircuitparser.cp310-win_amd64.pyd +0 -0
  29. pygsti/circuits/circuitstructure.py +3 -3
  30. pygsti/circuits/cloudcircuitconstruction.py +27 -14
  31. pygsti/data/datacomparator.py +4 -9
  32. pygsti/data/dataset.py +51 -46
  33. pygsti/data/hypothesistest.py +0 -7
  34. pygsti/drivers/bootstrap.py +0 -49
  35. pygsti/drivers/longsequence.py +46 -10
  36. pygsti/evotypes/basereps_cython.cp310-win_amd64.pyd +0 -0
  37. pygsti/evotypes/chp/opreps.py +0 -61
  38. pygsti/evotypes/chp/statereps.py +0 -32
  39. pygsti/evotypes/densitymx/effectcreps.cpp +9 -10
  40. pygsti/evotypes/densitymx/effectreps.cp310-win_amd64.pyd +0 -0
  41. pygsti/evotypes/densitymx/effectreps.pyx +1 -1
  42. pygsti/evotypes/densitymx/opreps.cp310-win_amd64.pyd +0 -0
  43. pygsti/evotypes/densitymx/opreps.pyx +2 -2
  44. pygsti/evotypes/densitymx/statereps.cp310-win_amd64.pyd +0 -0
  45. pygsti/evotypes/densitymx/statereps.pyx +1 -1
  46. pygsti/evotypes/densitymx_slow/effectreps.py +7 -23
  47. pygsti/evotypes/densitymx_slow/opreps.py +16 -23
  48. pygsti/evotypes/densitymx_slow/statereps.py +10 -3
  49. pygsti/evotypes/evotype.py +39 -2
  50. pygsti/evotypes/stabilizer/effectreps.cp310-win_amd64.pyd +0 -0
  51. pygsti/evotypes/stabilizer/effectreps.pyx +0 -4
  52. pygsti/evotypes/stabilizer/opreps.cp310-win_amd64.pyd +0 -0
  53. pygsti/evotypes/stabilizer/opreps.pyx +0 -4
  54. pygsti/evotypes/stabilizer/statereps.cp310-win_amd64.pyd +0 -0
  55. pygsti/evotypes/stabilizer/statereps.pyx +1 -5
  56. pygsti/evotypes/stabilizer/termreps.cp310-win_amd64.pyd +0 -0
  57. pygsti/evotypes/stabilizer/termreps.pyx +0 -7
  58. pygsti/evotypes/stabilizer_slow/effectreps.py +0 -22
  59. pygsti/evotypes/stabilizer_slow/opreps.py +0 -4
  60. pygsti/evotypes/stabilizer_slow/statereps.py +0 -4
  61. pygsti/evotypes/statevec/effectreps.cp310-win_amd64.pyd +0 -0
  62. pygsti/evotypes/statevec/effectreps.pyx +1 -1
  63. pygsti/evotypes/statevec/opreps.cp310-win_amd64.pyd +0 -0
  64. pygsti/evotypes/statevec/opreps.pyx +2 -2
  65. pygsti/evotypes/statevec/statereps.cp310-win_amd64.pyd +0 -0
  66. pygsti/evotypes/statevec/statereps.pyx +1 -1
  67. pygsti/evotypes/statevec/termreps.cp310-win_amd64.pyd +0 -0
  68. pygsti/evotypes/statevec/termreps.pyx +0 -7
  69. pygsti/evotypes/statevec_slow/effectreps.py +0 -3
  70. pygsti/evotypes/statevec_slow/opreps.py +0 -5
  71. pygsti/extras/__init__.py +0 -1
  72. pygsti/extras/drift/signal.py +1 -1
  73. pygsti/extras/drift/stabilityanalyzer.py +3 -1
  74. pygsti/extras/interpygate/__init__.py +12 -0
  75. pygsti/extras/interpygate/core.py +0 -36
  76. pygsti/extras/interpygate/process_tomography.py +44 -10
  77. pygsti/extras/rpe/rpeconstruction.py +0 -2
  78. pygsti/forwardsims/__init__.py +1 -0
  79. pygsti/forwardsims/forwardsim.py +50 -93
  80. pygsti/forwardsims/mapforwardsim.py +78 -20
  81. pygsti/forwardsims/mapforwardsim_calc_densitymx.cp310-win_amd64.pyd +0 -0
  82. pygsti/forwardsims/mapforwardsim_calc_densitymx.pyx +65 -66
  83. pygsti/forwardsims/mapforwardsim_calc_generic.py +91 -13
  84. pygsti/forwardsims/matrixforwardsim.py +72 -17
  85. pygsti/forwardsims/termforwardsim.py +9 -111
  86. pygsti/forwardsims/termforwardsim_calc_stabilizer.cp310-win_amd64.pyd +0 -0
  87. pygsti/forwardsims/termforwardsim_calc_statevec.cp310-win_amd64.pyd +0 -0
  88. pygsti/forwardsims/termforwardsim_calc_statevec.pyx +0 -651
  89. pygsti/forwardsims/torchfwdsim.py +265 -0
  90. pygsti/forwardsims/weakforwardsim.py +2 -2
  91. pygsti/io/__init__.py +1 -2
  92. pygsti/io/mongodb.py +0 -2
  93. pygsti/io/stdinput.py +6 -22
  94. pygsti/layouts/copalayout.py +10 -12
  95. pygsti/layouts/distlayout.py +0 -40
  96. pygsti/layouts/maplayout.py +103 -25
  97. pygsti/layouts/matrixlayout.py +99 -60
  98. pygsti/layouts/prefixtable.py +1534 -52
  99. pygsti/layouts/termlayout.py +1 -1
  100. pygsti/modelmembers/instruments/instrument.py +3 -3
  101. pygsti/modelmembers/instruments/tpinstrument.py +2 -2
  102. pygsti/modelmembers/modelmember.py +0 -17
  103. pygsti/modelmembers/operations/__init__.py +3 -4
  104. pygsti/modelmembers/operations/affineshiftop.py +206 -0
  105. pygsti/modelmembers/operations/composederrorgen.py +1 -1
  106. pygsti/modelmembers/operations/composedop.py +1 -24
  107. pygsti/modelmembers/operations/denseop.py +5 -5
  108. pygsti/modelmembers/operations/eigpdenseop.py +2 -2
  109. pygsti/modelmembers/operations/embeddederrorgen.py +1 -1
  110. pygsti/modelmembers/operations/embeddedop.py +0 -1
  111. pygsti/modelmembers/operations/experrorgenop.py +5 -2
  112. pygsti/modelmembers/operations/fullarbitraryop.py +1 -0
  113. pygsti/modelmembers/operations/fullcptpop.py +2 -2
  114. pygsti/modelmembers/operations/fulltpop.py +28 -6
  115. pygsti/modelmembers/operations/fullunitaryop.py +5 -4
  116. pygsti/modelmembers/operations/lindbladcoefficients.py +93 -78
  117. pygsti/modelmembers/operations/lindbladerrorgen.py +268 -441
  118. pygsti/modelmembers/operations/linearop.py +7 -27
  119. pygsti/modelmembers/operations/opfactory.py +1 -1
  120. pygsti/modelmembers/operations/repeatedop.py +1 -24
  121. pygsti/modelmembers/operations/staticstdop.py +1 -1
  122. pygsti/modelmembers/povms/__init__.py +3 -3
  123. pygsti/modelmembers/povms/basepovm.py +7 -36
  124. pygsti/modelmembers/povms/complementeffect.py +4 -9
  125. pygsti/modelmembers/povms/composedeffect.py +0 -320
  126. pygsti/modelmembers/povms/computationaleffect.py +1 -1
  127. pygsti/modelmembers/povms/computationalpovm.py +3 -1
  128. pygsti/modelmembers/povms/effect.py +3 -5
  129. pygsti/modelmembers/povms/marginalizedpovm.py +3 -81
  130. pygsti/modelmembers/povms/tppovm.py +74 -2
  131. pygsti/modelmembers/states/__init__.py +2 -5
  132. pygsti/modelmembers/states/composedstate.py +0 -317
  133. pygsti/modelmembers/states/computationalstate.py +3 -3
  134. pygsti/modelmembers/states/cptpstate.py +4 -4
  135. pygsti/modelmembers/states/densestate.py +10 -8
  136. pygsti/modelmembers/states/fullpurestate.py +0 -24
  137. pygsti/modelmembers/states/purestate.py +1 -1
  138. pygsti/modelmembers/states/state.py +5 -6
  139. pygsti/modelmembers/states/tpstate.py +28 -10
  140. pygsti/modelmembers/term.py +3 -6
  141. pygsti/modelmembers/torchable.py +50 -0
  142. pygsti/modelpacks/_modelpack.py +1 -1
  143. pygsti/modelpacks/smq1Q_ZN.py +3 -1
  144. pygsti/modelpacks/smq2Q_XXYYII.py +2 -1
  145. pygsti/modelpacks/smq2Q_XY.py +3 -3
  146. pygsti/modelpacks/smq2Q_XYI.py +2 -2
  147. pygsti/modelpacks/smq2Q_XYICNOT.py +3 -3
  148. pygsti/modelpacks/smq2Q_XYICPHASE.py +3 -3
  149. pygsti/modelpacks/smq2Q_XYXX.py +1 -1
  150. pygsti/modelpacks/smq2Q_XYZICNOT.py +3 -3
  151. pygsti/modelpacks/smq2Q_XYZZ.py +1 -1
  152. pygsti/modelpacks/stdtarget.py +0 -121
  153. pygsti/models/cloudnoisemodel.py +1 -2
  154. pygsti/models/explicitcalc.py +3 -3
  155. pygsti/models/explicitmodel.py +3 -13
  156. pygsti/models/fogistore.py +5 -3
  157. pygsti/models/localnoisemodel.py +1 -2
  158. pygsti/models/memberdict.py +0 -12
  159. pygsti/models/model.py +801 -68
  160. pygsti/models/modelconstruction.py +4 -4
  161. pygsti/models/modelnoise.py +2 -2
  162. pygsti/models/modelparaminterposer.py +1 -1
  163. pygsti/models/oplessmodel.py +1 -1
  164. pygsti/models/qutrit.py +15 -14
  165. pygsti/objectivefns/objectivefns.py +75 -140
  166. pygsti/objectivefns/wildcardbudget.py +2 -7
  167. pygsti/optimize/__init__.py +1 -0
  168. pygsti/optimize/arraysinterface.py +28 -0
  169. pygsti/optimize/customcg.py +0 -12
  170. pygsti/optimize/customlm.py +129 -323
  171. pygsti/optimize/customsolve.py +2 -2
  172. pygsti/optimize/optimize.py +0 -84
  173. pygsti/optimize/simplerlm.py +841 -0
  174. pygsti/optimize/wildcardopt.py +19 -598
  175. pygsti/protocols/confidenceregionfactory.py +28 -14
  176. pygsti/protocols/estimate.py +31 -14
  177. pygsti/protocols/gst.py +238 -142
  178. pygsti/protocols/modeltest.py +19 -12
  179. pygsti/protocols/protocol.py +9 -37
  180. pygsti/protocols/rb.py +450 -79
  181. pygsti/protocols/treenode.py +8 -2
  182. pygsti/protocols/vb.py +108 -206
  183. pygsti/protocols/vbdataframe.py +1 -1
  184. pygsti/report/factory.py +0 -15
  185. pygsti/report/fogidiagram.py +1 -17
  186. pygsti/report/modelfunction.py +12 -3
  187. pygsti/report/mpl_colormaps.py +1 -1
  188. pygsti/report/plothelpers.py +11 -3
  189. pygsti/report/report.py +16 -0
  190. pygsti/report/reportables.py +41 -37
  191. pygsti/report/templates/offline/pygsti_dashboard.css +6 -0
  192. pygsti/report/templates/offline/pygsti_dashboard.js +12 -0
  193. pygsti/report/workspace.py +2 -14
  194. pygsti/report/workspaceplots.py +328 -505
  195. pygsti/tools/basistools.py +9 -36
  196. pygsti/tools/edesigntools.py +124 -96
  197. pygsti/tools/fastcalc.cp310-win_amd64.pyd +0 -0
  198. pygsti/tools/fastcalc.pyx +35 -81
  199. pygsti/tools/internalgates.py +151 -15
  200. pygsti/tools/jamiolkowski.py +5 -5
  201. pygsti/tools/lindbladtools.py +19 -11
  202. pygsti/tools/listtools.py +0 -114
  203. pygsti/tools/matrixmod2.py +1 -1
  204. pygsti/tools/matrixtools.py +173 -339
  205. pygsti/tools/nameddict.py +1 -1
  206. pygsti/tools/optools.py +154 -88
  207. pygsti/tools/pdftools.py +0 -25
  208. pygsti/tools/rbtheory.py +3 -320
  209. pygsti/tools/slicetools.py +64 -12
  210. pyGSTi-0.9.12.dist-info/METADATA +0 -157
  211. pygsti/algorithms/directx.py +0 -711
  212. pygsti/evotypes/qibo/__init__.py +0 -33
  213. pygsti/evotypes/qibo/effectreps.py +0 -78
  214. pygsti/evotypes/qibo/opreps.py +0 -376
  215. pygsti/evotypes/qibo/povmreps.py +0 -98
  216. pygsti/evotypes/qibo/statereps.py +0 -174
  217. pygsti/extras/rb/__init__.py +0 -13
  218. pygsti/extras/rb/benchmarker.py +0 -957
  219. pygsti/extras/rb/dataset.py +0 -378
  220. pygsti/extras/rb/io.py +0 -814
  221. pygsti/extras/rb/simulate.py +0 -1020
  222. pygsti/io/legacyio.py +0 -385
  223. pygsti/modelmembers/povms/denseeffect.py +0 -142
  224. {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/LICENSE +0 -0
  225. {pyGSTi-0.9.12.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,13 +821,65 @@ 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):
828
+ """
829
+ Method for adding circuits, or labels to circuits.
830
+
831
+ Parameters
832
+ ----------
833
+ x : `Circuit` or tuple of `Label` objects
834
+ `Circuit` to add to this `Circuit`, or a tuple of Labels to add to this
835
+ Circuit. Note: If `x` is a `Circuit` it must have line labels that are
836
+ compatible with this it is being added to. In other words, if `x` uses
837
+ the default '*' placeholder as its line label and this Circuit does not,
838
+ and vice versa, a ValueError will be raised.
839
+
840
+ Returns
841
+ -------
842
+ Circuit
843
+ """
844
+
846
845
  if not isinstance(x, Circuit):
847
846
  assert(all([isinstance(l, _Label) for l in x])), "Only Circuits and Label-tuples can be added to Circuits!"
848
- 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)
852
+
853
+ #Add special line label handling to deal with the special global idle circuits (which have no line labels
854
+ # associated with them typically).
855
+ #Check if a the circuit or labels being added are all global idles, if so inherit the
856
+ #line labels from the circuit being added to. Otherwise, enforce compatibility.
857
+ layertup_x = x.layertup
858
+ gbl_idle_x= all([lbl == _Label(()) for lbl in layertup_x])
859
+ gbl_idle_self= all([lbl == _Label(()) for lbl in self.layertup])
860
+
861
+ if not (gbl_idle_x or gbl_idle_self):
862
+ combined_labels = {x._line_labels, self._line_labels}
863
+ elif not gbl_idle_x and gbl_idle_self:
864
+ combined_labels = {x._line_labels}
865
+ elif gbl_idle_x and not gbl_idle_self:
866
+ combined_labels = {self._line_labels}
867
+ else: #both are all global idles so it doesn't matter which we take.
868
+ combined_labels = {self._line_labels}
869
+
870
+ #check that the line labels are compatible between circuits.
871
+ #i.e. raise error if adding circuit with * line label to one with
872
+ #standard line labels.
873
+ if ('*',) in combined_labels and len(combined_labels) > 1:
874
+ # raise the error
875
+ msg = f"Adding circuits with incompatible line labels: {combined_labels}." \
876
+ +" The problem is that one of these labels uses the placeholder value of '*', while the other label does not."\
877
+ +" The placeholder value arises when when a Circuit is initialized without specifying the line labels,"\
878
+ +" either explicitly by setting the line_labels or by num_lines kwarg, or implicitly from specifying"\
879
+ +" layer labels with non-None state-space labels. Circuits with '*' line labels can be used, but"\
880
+ +" only in conjunction with other circuits with '*' line labels (and vice-versa for circuits with"\
881
+ +" standard line labels)."
882
+ raise ValueError(msg)
849
883
 
850
884
  if self._str is None or x._str is None:
851
885
  s = None
@@ -857,13 +891,50 @@ class Circuit(object):
857
891
  s = (mystr + xstr) if xstr != "{}" else mystr
858
892
  else: s = xstr
859
893
 
860
- added_labels = tuple([l for l in x.line_labels if l not in self.line_labels])
861
- new_line_labels = self.line_labels + added_labels
894
+ #try to return the line labels as the contents of combined labels in
895
+ #sorted order. If there is a TypeError raised this is probably because
896
+ #we're mixing integer and string labels, in which case we'll just return
897
+ #the new labels in whatever arbirary order is obtained by casting a set to
898
+ #a tuple.
899
+ #unpack all of the different sets of labels and make sure there are no duplicates
900
+ combined_labels_unpacked = {el for tup in combined_labels for el in tup}
901
+ try:
902
+ new_line_labels = tuple(sorted(combined_labels_unpacked))
903
+ except TypeError:
904
+ new_line_labels = tuple(combined_labels_unpacked)
905
+
862
906
  if s is not None:
863
907
  s += _op_seq_str_suffix(new_line_labels, occurrence_id=None) # don't maintain occurrence_id
864
908
 
865
909
  return Circuit._fastinit(self.layertup + x.layertup, new_line_labels, editable=False, name='',
866
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)
867
938
 
868
939
  def repeat(self, ntimes, expand="default"):
869
940
  """
@@ -890,10 +961,10 @@ class Circuit(object):
890
961
  s += "@" + mylines # add line labels
891
962
  if ntimes >= 1 and expand is False:
892
963
  reppedCircuitLbl = self.to_label(nreps=ntimes)
893
- 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)
894
965
  else:
895
966
  # just adds parens to string rep & copies
896
- 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)
897
968
 
898
969
  def __mul__(self, x):
899
970
  return self.repeat(x)
@@ -902,21 +973,39 @@ class Circuit(object):
902
973
  return self.__mul__(x)
903
974
 
904
975
  def __eq__(self, x):
905
- if x is None: return False
976
+
906
977
  if isinstance(x, Circuit):
907
- 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
908
987
  else:
909
- 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*
910
993
 
911
994
  def __lt__(self, x):
912
995
  if isinstance(x, Circuit):
913
- 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)
914
1000
  else:
915
1001
  return self.layertup < tuple(x) # comparison with non-circuits is just based on *labels*
916
1002
 
917
1003
  def __gt__(self, x):
918
1004
  if isinstance(x, Circuit):
919
- 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)
920
1009
  else:
921
1010
  return self.layertup > tuple(x) # comparison with non-circuits is just based on *labels*
922
1011
 
@@ -929,9 +1018,9 @@ class Circuit(object):
929
1018
  -------
930
1019
  int
931
1020
  """
932
- return len(self.line_labels)
933
-
934
- def copy(self, editable="auto"):
1021
+ return len(self._line_labels)
1022
+
1023
+ def copy(self, editable='auto'):
935
1024
  """
936
1025
  Returns a copy of the circuit.
937
1026
 
@@ -945,9 +1034,43 @@ class Circuit(object):
945
1034
  -------
946
1035
  Circuit
947
1036
  """
948
- if editable == "auto": editable = not self._static
949
- return Circuit(self.layertup, self.line_labels, None, editable, self._str, check=False,
950
- 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))
951
1074
 
952
1075
  def clear(self):
953
1076
  """
@@ -976,10 +1099,10 @@ class Circuit(object):
976
1099
  def _proc_lines_arg(self, lines):
977
1100
  """ Pre-process the lines argument used by many methods """
978
1101
  if lines is None:
979
- lines = self.line_labels
1102
+ lines = self._line_labels
980
1103
  elif isinstance(lines, slice):
981
1104
  if lines.start is None and lines.stop is None:
982
- lines = self.line_labels
1105
+ lines = self._line_labels
983
1106
  else:
984
1107
  lines = _slct.indices(lines)
985
1108
  elif not isinstance(lines, (list, tuple)):
@@ -989,19 +1112,18 @@ class Circuit(object):
989
1112
  def _proc_key_arg(self, key):
990
1113
  """ Pre-process the key argument used by many methods """
991
1114
  if isinstance(key, tuple):
992
- if len(key) != 2: return IndexError("Index must be of the form <layerIndex>,<lineIndex>")
993
- layers = key[0]
994
- 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]
995
1119
  else:
996
- layers = key
997
- lines = None
998
- return layers, lines
1120
+ return key, None
999
1121
 
1000
1122
  def _layer_components(self, ilayer):
1001
1123
  """ Get the components of the `ilayer`-th layer as a list/tuple. """
1002
1124
  #(works for static and non-static Circuits)
1003
1125
  if self._static:
1004
- if self._labels[ilayer].is_simple(): return [self._labels[ilayer]]
1126
+ if self._labels[ilayer].IS_SIMPLE: return [self._labels[ilayer]]
1005
1127
  else: return self._labels[ilayer].components
1006
1128
  else:
1007
1129
  return self._labels[ilayer] if isinstance(self._labels[ilayer], list) \
@@ -1085,21 +1207,41 @@ class Circuit(object):
1085
1207
  `layers` is a single integer and as a `Circuit` otherwise.
1086
1208
  Note: if you want a `Circuit` when only selecting one layer,
1087
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.
1088
1212
  """
1089
1213
  nonint_layers = not isinstance(layers, int)
1090
1214
 
1091
1215
  #Shortcut for common case when lines == None and when we're only taking a layer slice/index
1092
- if lines is None:
1093
- assert(layers is not None)
1094
- if nonint_layers is False: return self.layertup[layers]
1095
- if isinstance(layers, slice) and strict is True: # if strict=False, then need to recompute line labels
1096
- 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
+
1097
1235
 
1098
1236
  layers = self._proc_layers_arg(layers)
1099
1237
  lines = self._proc_lines_arg(lines)
1100
1238
  if len(layers) == 0 or len(lines) == 0:
1101
- return Circuit._fastinit(() if self._static else [],
1102
- 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
1103
1245
 
1104
1246
  ret = []
1105
1247
  if self._static:
@@ -1115,7 +1257,7 @@ class Circuit(object):
1115
1257
  ## add in special case of identity layer
1116
1258
  #if (isinstance(l,_Label) and l.name == self.identity): # ~ is_identity_layer(l)
1117
1259
  # ret_layer.append(l); continue
1118
- 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
1119
1261
  else:
1120
1262
  sslbls = set(sslbls)
1121
1263
  if (strict and sslbls.issubset(lines)) or \
@@ -1126,7 +1268,10 @@ class Circuit(object):
1126
1268
  if nonint_layers:
1127
1269
  if not strict: lines = "auto" # since we may have included lbls on other lines
1128
1270
  # don't worry about string rep for now...
1129
- 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)
1130
1275
  else:
1131
1276
  return _Label(ret[0])
1132
1277
 
@@ -1190,9 +1335,9 @@ class Circuit(object):
1190
1335
  lbls_sslbls = None if (lbls.sslbls is None) else set(lbls.sslbls)
1191
1336
  else:
1192
1337
  if isinstance(lbls, Circuit):
1193
- assert(set(lbls.line_labels).issubset(self.line_labels)), \
1338
+ assert(set(lbls._line_labels).issubset(self._line_labels)), \
1194
1339
  "Assigned circuit has lines (%s) not contained in this circuit (%s)!" \
1195
- % (str(lbls.line_labels), str(self.line_labels))
1340
+ % (str(lbls._line_labels), str(self._line_labels))
1196
1341
  lbls = lbls.layertup # circuit layer labels as a tuple
1197
1342
  assert(isinstance(lbls, (tuple, list))), \
1198
1343
  ("When assigning to a layer range (even w/len=1) `lbls` "
@@ -1215,18 +1360,18 @@ class Circuit(object):
1215
1360
  # the lines being assigned. If sslbl != None, then the labels must be
1216
1361
  # contained within the line labels being assigned (unless we're allowed to expand)
1217
1362
  if lbls_sslbls is not None:
1218
- new_line_labels = set(lbls_sslbls) - set(self.line_labels)
1363
+ new_line_labels = set(lbls_sslbls) - set(self._line_labels)
1219
1364
  if all_lines: # then allow new lines to be added
1220
1365
  if len(new_line_labels) > 0:
1221
- 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?
1222
1367
  else:
1223
1368
  assert(len(new_line_labels) == 0), "Cannot add new lines %s" % str(new_line_labels)
1224
1369
  assert(set(lbls_sslbls).issubset(lines)), \
1225
1370
  "Unallowed state space labels: %s" % str(set(lbls_sslbls) - set(lines))
1226
1371
 
1227
- assert(set(lines).issubset(self.line_labels)), \
1372
+ assert(set(lines).issubset(self._line_labels)), \
1228
1373
  ("Specified lines (%s) must be a subset of this circuit's lines"
1229
- " (%s).") % (str(lines), str(self.line_labels))
1374
+ " (%s).") % (str(lines), str(self._line_labels))
1230
1375
 
1231
1376
  #remove all labels in block to be assigned
1232
1377
  self._clear_labels(layers, lines)
@@ -1309,10 +1454,10 @@ class Circuit(object):
1309
1454
  self._labels.insert(insert_before, [])
1310
1455
 
1311
1456
  #Shift compilable layer indices as needed
1312
- if len(self._compilable_layer_indices_tup) > 0: # then begins with __CMPLBL__
1457
+ if self._compilable_layer_indices_tup:
1313
1458
  shifted_inds = [i if (i < insert_before) else (i + num_to_insert)
1314
- for i in self._compilable_layer_indices_tup[1:]]
1315
- 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)
1316
1461
 
1317
1462
  else: # insert layers only on given lines - shift existing labels to right
1318
1463
  for i in range(num_to_insert):
@@ -1355,6 +1500,7 @@ class Circuit(object):
1355
1500
  -------
1356
1501
  None
1357
1502
  """
1503
+ assert(not self._static), "Cannot edit a read-only circuit!"
1358
1504
  self.insert_idling_layers_inplace(None, num_to_insert, lines)
1359
1505
 
1360
1506
  def insert_labels_into_layers(self, lbls, layer_to_insert_before, lines=None):
@@ -1423,6 +1569,7 @@ class Circuit(object):
1423
1569
  -------
1424
1570
  None
1425
1571
  """
1572
+ assert(not self._static), "Cannot edit a read-only circuit!"
1426
1573
  if isinstance(lbls, Circuit): lbls = tuple(lbls)
1427
1574
  # lbls is expected to be a list/tuple of Label-like items, one per inserted layer
1428
1575
  lbls = tuple(map(to_label, lbls))
@@ -1472,13 +1619,12 @@ class Circuit(object):
1472
1619
  -------
1473
1620
  None
1474
1621
  """
1475
- #assert(not self._static),"Cannot edit a read-only circuit!"
1476
- # 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!"
1477
1623
  if insert_before is None:
1478
- i = len(self.line_labels)
1624
+ i = len(self._line_labels)
1479
1625
  else:
1480
- i = self.line_labels.index(insert_before)
1481
- 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:]
1482
1628
 
1483
1629
  def _append_idling_lines(self, line_labels):
1484
1630
  """
@@ -1531,6 +1677,8 @@ class Circuit(object):
1531
1677
  -------
1532
1678
  None
1533
1679
  """
1680
+ assert(not self._static), "Cannot edit a read-only circuit!"
1681
+
1534
1682
  if layer_to_insert_before is None: layer_to_insert_before = 0
1535
1683
  elif layer_to_insert_before < 0: layer_to_insert_before = len(self._labels) + layer_to_insert_before
1536
1684
 
@@ -1540,7 +1688,7 @@ class Circuit(object):
1540
1688
  elif line_labels == "auto":
1541
1689
  line_labels = tuple(sorted(_accumulate_explicit_sslbls(lbls)))
1542
1690
 
1543
- existing_labels = set(line_labels).intersection(self.line_labels)
1691
+ existing_labels = set(line_labels).intersection(self._line_labels)
1544
1692
  if len(existing_labels) > 0:
1545
1693
  raise ValueError("Cannot insert line(s) labeled %s - they already exist!" % str(existing_labels))
1546
1694
 
@@ -1634,7 +1782,7 @@ class Circuit(object):
1634
1782
  new_layer = []
1635
1783
  for l in self._layer_components(i): # loop over labels in this layer
1636
1784
  sslbls = _sslbls_of_nested_lists_of_simple_labels(l)
1637
- 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)
1638
1786
  if len(sslbls.intersection(lines)) == 0:
1639
1787
  new_layer.append(l)
1640
1788
  elif not clear_straddlers and not sslbls.issubset(lines):
@@ -1689,12 +1837,12 @@ class Circuit(object):
1689
1837
  del self._labels[i]
1690
1838
 
1691
1839
  #Shift compilable layer indices as needed
1692
- if len(self._compilable_layer_indices_tup) > 0: # begins with __CMPLBL__
1840
+ if self._compilable_layer_indices_tup:
1693
1841
  deleted_indices = set(layers)
1694
- 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]
1695
1843
  for deleted_i in reversed(sorted(deleted_indices)):
1696
1844
  new_inds = [i if (i < deleted_i) else (i - 1) for i in new_inds] # Note i never == deleted_i (filtered)
1697
- self._compilable_layer_indices_tup = ('__CMPLBL__',) + tuple(new_inds)
1845
+ self._compilable_layer_indices_tup = tuple(new_inds)
1698
1846
 
1699
1847
  def delete_lines(self, lines, delete_straddlers=False):
1700
1848
  """
@@ -1726,7 +1874,7 @@ class Circuit(object):
1726
1874
  raise ValueError(("Cannot remove a block that is straddled by "
1727
1875
  "%s when `delete_straddlers` == False!") % _Label(l))
1728
1876
  self._labels[i] = new_layer
1729
- 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])
1730
1878
 
1731
1879
  def __getitem__(self, key):
1732
1880
  layers, lines = self._proc_key_arg(key)
@@ -1840,7 +1988,7 @@ class Circuit(object):
1840
1988
  if len(lbl.components) == 0: # special case of an empty-layer label,
1841
1989
  serial_lbls.append(lbl) # which we serialize as an atomic object
1842
1990
  serial_lbls.extend(list(lbl.components) * lbl.reps)
1843
- 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)
1844
1992
 
1845
1993
  def parallelize(self, can_break_labels=True, adjacent_only=False):
1846
1994
  """
@@ -1926,7 +2074,7 @@ class Circuit(object):
1926
2074
 
1927
2075
  # Convert elements of `parallel_lbls` into Labels (needed b/c we use _fastinit below)
1928
2076
  parallel_lbls = [_Label(lbl_list) if len(lbl_list) != 1 else lbl_list[0] for lbl_list in parallel_lbls]
1929
- 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)
1930
2078
 
1931
2079
  def expand_subcircuits_inplace(self):
1932
2080
  """
@@ -1940,25 +2088,41 @@ class Circuit(object):
1940
2088
  None
1941
2089
  """
1942
2090
  assert(not self._static), "Cannot edit a read-only circuit!"
1943
-
1944
- #Iterate in reverse so we don't have to deal with
1945
- # 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 = []
1946
2120
  for i in reversed(range(len(self._labels))):
1947
- circuits_to_expand = []
1948
- layers_to_add = 0
1949
-
1950
- for l in self._layer_components(i): # loop over labels in this layer
1951
- if isinstance(l, _CircuitLabel):
1952
- circuits_to_expand.append(l)
1953
- layers_to_add = max(layers_to_add, l.depth - 1)
1954
-
1955
- if layers_to_add > 0:
1956
- self.insert_idling_layers_inplace(i + 1, layers_to_add)
1957
- for subc in circuits_to_expand:
1958
- self.clear_labels(slice(i, i + subc.depth), subc.sslbls) # remove the CircuitLabel
1959
- self.set_labels(subc.components * subc.reps, slice(i, i + subc.depth),
1960
- subc.sslbls) # dump in the contents
1961
-
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
+
1962
2126
  def expand_subcircuits(self):
1963
2127
  """
1964
2128
  Returns a new circuit with :class:`CircuitLabel` labels expanded.
@@ -1996,13 +2160,11 @@ class Circuit(object):
1996
2160
  while iEnd < nLayers and self._labels[iStart] == self._labels[iEnd]:
1997
2161
  iEnd += 1
1998
2162
  nreps = iEnd - iStart
1999
- #print("Start,End = ",iStart,iEnd)
2000
2163
  if nreps <= 1: # just move to next layer
2001
2164
  iStart += 1; continue # nothing to do
2002
2165
 
2003
2166
  #Construct a sub-circuit label that repeats layer[iStart] nreps times
2004
2167
  # and stick it at layer iStart
2005
- #print("Constructing %d reps at %d" % (nreps, iStart))
2006
2168
  repCircuit = _CircuitLabel('', self._labels[iStart], None, nreps)
2007
2169
  self.clear_labels(iStart, None) # remove existing labels (unnecessary?)
2008
2170
  self.set_labels(repCircuit, iStart, None)
@@ -2010,7 +2172,6 @@ class Circuit(object):
2010
2172
  iStart += nreps # advance iStart to next unprocessed layer inde
2011
2173
 
2012
2174
  if len(iLayersToRemove) > 0:
2013
- #print("Removing layers: ",iLayersToRemove)
2014
2175
  self.delete_layers(iLayersToRemove)
2015
2176
 
2016
2177
  def insert_layer(self, circuit_layer, j):
@@ -2063,7 +2224,7 @@ class Circuit(object):
2063
2224
  None
2064
2225
  """
2065
2226
  assert(not self._static), "Cannot edit a read-only circuit!"
2066
- if self.line_labels is None or self.line_labels == ():
2227
+ if self._line_labels is None or self._line_labels == ():
2067
2228
  #Allow insertion of a layer into an empty circuit to update the circuit's line_labels
2068
2229
  layer_lbl = to_label(circuit_layer)
2069
2230
  self.line_labels = layer_lbl.sslbls if (layer_lbl.sslbls is not None) else ('*',)
@@ -2123,8 +2284,8 @@ class Circuit(object):
2123
2284
  """
2124
2285
  assert(not self._static), "Cannot edit a read-only circuit!"
2125
2286
  lines_to_insert = []
2126
- for line_lbl in circuit.line_labels:
2127
- if line_lbl in self.line_labels:
2287
+ for line_lbl in circuit._line_labels:
2288
+ if line_lbl in self._line_labels:
2128
2289
  lines_to_insert.append(line_lbl)
2129
2290
  else:
2130
2291
  assert(circuit._is_line_idling(line_lbl)), \
@@ -2167,6 +2328,7 @@ class Circuit(object):
2167
2328
  -------
2168
2329
  None
2169
2330
  """
2331
+ assert(not self._static), "Cannot edit a read-only circuit!"
2170
2332
  self.insert_circuit_inplace(circuit, self.num_layers)
2171
2333
 
2172
2334
  def prefix_circuit(self, circuit):
@@ -2203,6 +2365,7 @@ class Circuit(object):
2203
2365
  -------
2204
2366
  None
2205
2367
  """
2368
+ assert(not self._static), "Cannot edit a read-only circuit!"
2206
2369
  self.insert_circuit_inplace(circuit, 0)
2207
2370
 
2208
2371
  def tensor_circuit_inplace(self, circuit, line_order=None):
@@ -2232,12 +2395,12 @@ class Circuit(object):
2232
2395
  #assert(self.identity == circuit.identity), "The identity labels must be the same!"
2233
2396
 
2234
2397
  #Construct new line labels (of final circuit)
2235
- overlap = set(self.line_labels).intersection(circuit.line_labels)
2398
+ overlap = set(self._line_labels).intersection(circuit._line_labels)
2236
2399
  if len(overlap) > 0:
2237
2400
  raise ValueError(
2238
2401
  "The line labels of `circuit` and this Circuit must be distinct, but overlap = %s!" % str(overlap))
2239
2402
 
2240
- all_line_labels = set(self.line_labels + circuit.line_labels)
2403
+ all_line_labels = set(self._line_labels + circuit._line_labels)
2241
2404
  if line_order is not None:
2242
2405
  line_order_set = set(line_order)
2243
2406
  if len(line_order_set) != len(line_order):
@@ -2253,7 +2416,7 @@ class Circuit(object):
2253
2416
 
2254
2417
  new_line_labels = line_order
2255
2418
  else:
2256
- new_line_labels = self.line_labels + circuit.line_labels
2419
+ new_line_labels = self._line_labels + circuit._line_labels
2257
2420
 
2258
2421
  #Add circuit's labels into this circuit
2259
2422
  self.insert_labels_as_lines_inplace(circuit._labels, line_labels=circuit.line_labels)
@@ -2303,6 +2466,7 @@ class Circuit(object):
2303
2466
  -------
2304
2467
  None
2305
2468
  """
2469
+ assert(not self._static), "Cannot edit a read-only circuit!"
2306
2470
  del self[j]
2307
2471
  self.insert_labels_into_layers_inplace(circuit, j)
2308
2472
 
@@ -2391,7 +2555,7 @@ class Circuit(object):
2391
2555
  return cpy
2392
2556
  else: # static case: so self._labels is a tuple of Labels
2393
2557
  return Circuit([lbl.replace_name(old_gatename, new_gatename)
2394
- for lbl in self._labels], self.line_labels, occurrence=self.occurrence)
2558
+ for lbl in self._labels], self._line_labels, occurrence=self._occurrence_id)
2395
2559
 
2396
2560
  def replace_gatename_with_idle_inplace(self, gatename):
2397
2561
  """
@@ -2467,12 +2631,14 @@ class Circuit(object):
2467
2631
  #Could to this in both cases, but is slow for large static circuits
2468
2632
  cpy = self.copy(editable=False) # convert our layers to Labels
2469
2633
  return Circuit._fastinit(tuple([new_layer if lbl == old_layer else lbl
2470
- for lbl in cpy._labels]), self.line_labels, editable=False,
2471
- 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)
2472
2637
  else: # static case: so self._labels is a tuple of Labels
2473
2638
  return Circuit(tuple([new_layer if lbl == old_layer else lbl
2474
- for lbl in self._labels]), self.line_labels, editable=False,
2475
- 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)
2476
2642
 
2477
2643
  def replace_layers_with_aliases(self, alias_dict):
2478
2644
  """
@@ -2499,34 +2665,7 @@ class Circuit(object):
2499
2665
  while label in layers:
2500
2666
  i = layers.index(label)
2501
2667
  layers = layers[:i] + c._labels + layers[i + 1:]
2502
- return Circuit._fastinit(layers, self.line_labels, editable=False, occurrence=self.occurrence)
2503
-
2504
- #def replace_identity(self, identity, convert_identity_gates = True): # THIS module only
2505
- # """
2506
- # Changes the *name* of the idle/identity gate in the circuit. This replaces
2507
- # the name of the identity element in the circuit by setting self.identity = identity.
2508
- # If `convert_identity_gates` is True, this also changes the names of all the gates that
2509
- # had the old self.identity name.
2510
- #
2511
- # Parameters
2512
- # ----------
2513
- # identity : string
2514
- # The new name for the identity gate.
2515
- #
2516
- # convert_identity_gates : bool, optional
2517
- # If True, all gates that had the old identity name are converted to the new identity
2518
- # name. Otherwise, they keep the old name, and the circuit nolonger considers them to
2519
- # be identity gates.
2520
- #
2521
- # Returns
2522
- # -------
2523
- # None
2524
- # """
2525
- # if convert_identity_gates:
2526
- # self.replace_gatename(self.identity, identity)
2527
- #
2528
- # self._tup_dirty = self._str_dirty = True
2529
- # self.identity = identity
2668
+ return Circuit._fastinit(layers, self._line_labels, editable=False, occurrence=self._occurrence_id)
2530
2669
 
2531
2670
  def change_gate_library(self, compilation, allowed_filter=None, allow_unchanged_gates=False, depth_compression=True,
2532
2671
  one_q_gate_relations=None):
@@ -2657,7 +2796,7 @@ class Circuit(object):
2657
2796
 
2658
2797
  def map_names(obj): # obj is either a simple label or a list
2659
2798
  if isinstance(obj, _Label):
2660
- if obj.is_simple(): # *simple* label
2799
+ if obj.IS_SIMPLE: # *simple* label
2661
2800
  new_name = mapper_func(obj.name)
2662
2801
  newobj = _Label(new_name, obj.sslbls) \
2663
2802
  if (new_name is not None) else obj
@@ -2689,7 +2828,7 @@ class Circuit(object):
2689
2828
  def mapper_func(line_label): return mapper[line_label] \
2690
2829
  if isinstance(mapper, dict) else mapper
2691
2830
 
2692
- 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))
2693
2832
 
2694
2833
  def map_sslbls(obj): # obj is either a simple label or a list
2695
2834
  if isinstance(obj, _Label):
@@ -2718,9 +2857,9 @@ class Circuit(object):
2718
2857
  """
2719
2858
  def mapper_func(line_label): return mapper[line_label] \
2720
2859
  if isinstance(mapper, dict) else mapper(line_label)
2721
- mapped_line_labels = tuple(map(mapper_func, self.line_labels))
2860
+ mapped_line_labels = tuple(map(mapper_func, self._line_labels))
2722
2861
  return Circuit([l.map_state_space_labels(mapper_func) for l in self.layertup],
2723
- mapped_line_labels, None, not self._static, occurrence=self.occurrence)
2862
+ mapped_line_labels, None, not self._static, occurrence=self._occurrence_id)
2724
2863
 
2725
2864
  def reorder_lines_inplace(self, order):
2726
2865
  """
@@ -2739,7 +2878,7 @@ class Circuit(object):
2739
2878
  None
2740
2879
  """
2741
2880
  assert(not self._static), "Cannot edit a read-only circuit!"
2742
- 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!"
2743
2882
  self._line_labels = tuple(order)
2744
2883
 
2745
2884
  def reorder_lines(self, order):
@@ -2784,10 +2923,9 @@ class Circuit(object):
2784
2923
  True if the line is idling. False otherwise.
2785
2924
  """
2786
2925
  if self._static:
2787
- layers = list(filter(lambda x: x not in idle_layer_labels, self._labels)) \
2788
- 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
2789
2927
  all_sslbls = None if any([layer.sslbls is None for layer in layers]) \
2790
- else set(_itertools.chain(*[layer.sslbls for layer in layers]))
2928
+ else set([sslbl for layer in layers for sslbl in layer.sslbls])
2791
2929
  else:
2792
2930
  all_sslbls = _sslbls_of_nested_lists_of_simple_labels(self._labels, idle_layer_labels) # None or a set
2793
2931
 
@@ -2812,17 +2950,16 @@ class Circuit(object):
2812
2950
  tuple
2813
2951
  """
2814
2952
  if self._static:
2815
- layers = list(filter(lambda x: x not in idle_layer_labels, self._labels)) \
2816
- 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
2817
2954
  all_sslbls = None if any([layer.sslbls is None for layer in layers]) \
2818
- else set(_itertools.chain(*[layer.sslbls for layer in layers]))
2955
+ else set([sslbl for layer in layers for sslbl in layer.sslbls])
2819
2956
  else:
2820
2957
  all_sslbls = _sslbls_of_nested_lists_of_simple_labels(self._labels, idle_layer_labels) # None or a set
2821
2958
 
2822
2959
  if all_sslbls is None:
2823
2960
  return ()
2824
2961
  else:
2825
- return tuple([x for x in self.line_labels
2962
+ return tuple([x for x in self._line_labels
2826
2963
  if x not in all_sslbls]) # preserve order
2827
2964
 
2828
2965
  def delete_idling_lines_inplace(self, idle_layer_labels=None):
@@ -2841,17 +2978,15 @@ class Circuit(object):
2841
2978
  -------
2842
2979
  None
2843
2980
  """
2844
- #assert(not self._static),"Cannot edit a read-only circuit!"
2845
- # 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!"
2846
2982
 
2847
2983
  if idle_layer_labels:
2848
2984
  assert(all([to_label(x).sslbls is None for x in idle_layer_labels])), "Idle layer labels must be *global*"
2849
2985
 
2850
2986
  if self._static:
2851
- layers = list(filter(lambda x: x not in idle_layer_labels, self._labels)) \
2852
- 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
2853
2988
  all_sslbls = None if any([layer.sslbls is None for layer in layers]) \
2854
- else set(_itertools.chain(*[layer.sslbls for layer in layers]))
2989
+ else set([sslbl for layer in layers for sslbl in layer.sslbls])
2855
2990
  else:
2856
2991
  all_sslbls = _sslbls_of_nested_lists_of_simple_labels(self._labels, idle_layer_labels) # None or a set
2857
2992
 
@@ -2860,7 +2995,7 @@ class Circuit(object):
2860
2995
 
2861
2996
  #All we need to do is update line_labels since there aren't any labels
2862
2997
  # to remove in self._labels (as all the lines are idling)
2863
- self._line_labels = tuple([x for x in self.line_labels
2998
+ self._line_labels = tuple([x for x in self._line_labels
2864
2999
  if x in all_sslbls]) # preserve order
2865
3000
 
2866
3001
  def delete_idling_lines(self, idle_layer_labels=None):
@@ -2906,6 +3041,7 @@ class Circuit(object):
2906
3041
  -------
2907
3042
  None
2908
3043
  """
3044
+ assert(not self._static), "Cannot edit a read-only circuit!"
2909
3045
  self.clear_labels(lines=line_label, clear_straddlers=clear_straddlers)
2910
3046
 
2911
3047
  def reverse_inplace(self):
@@ -2920,10 +3056,10 @@ class Circuit(object):
2920
3056
  self._labels = list(reversed(self._labels)) # reverses the layer order
2921
3057
  #FUTURE: would need to reverse_inplace each layer too, if layer can have *sublayers*
2922
3058
 
2923
- if len(self._compilable_layer_indices_tup) > 0: # begins with __CMPLBL__
3059
+ if self._compilable_layer_indices_tup:
2924
3060
  depth = len(self._labels)
2925
- self._compilable_layer_indices_tup = ('__CMPLBL__',) \
2926
- + 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])
2927
3063
 
2928
3064
  def _combine_one_q_gates_inplace(self, one_q_gate_relations):
2929
3065
  """
@@ -2969,7 +3105,6 @@ class Circuit(object):
2969
3105
  productive = True
2970
3106
 
2971
3107
  while productive: # keep iterating
2972
- #print("BEGIN ITER")
2973
3108
  productive = False
2974
3109
  # Loop through all the qubits, to try and compress squences of 1-qubit gates on the qubit in question.
2975
3110
  for ilayer in range(0, len(self._labels) - 1):
@@ -2984,7 +3119,6 @@ class Circuit(object):
2984
3119
  for b, lblB in enumerate(layerB_comps):
2985
3120
  if isinstance(lblB, _Label) and lblB.sslbls == lblA.sslbls:
2986
3121
  #queue an apply rule if one exists
2987
- #print("CHECK for: ", (lblA.name,lblB.name))
2988
3122
  if (lblA.name, lblB.name) in one_q_gate_relations:
2989
3123
  new_Aname = one_q_gate_relations[lblA.name, lblB.name]
2990
3124
  applies.append((a, b, new_Aname, lblA.sslbls))
@@ -3037,17 +3171,14 @@ class Circuit(object):
3037
3171
  # Keeps track of whether any changes have been made to the circuit.
3038
3172
  compression_implemented = False
3039
3173
 
3040
- #print("BEGIN")
3041
3174
  used_lines = {}
3042
3175
  for icurlayer in range(len(self._labels)):
3043
- #print("LAYER ",icurlayer)
3044
3176
  #Slide labels in current layer to left ("forward")
3045
3177
  icomps_to_remove = []; used_lines[icurlayer] = set()
3046
3178
  for icomp, lbl in enumerate(self._layer_components(icurlayer)):
3047
3179
  #see if we can move this label forward
3048
- #print("COMP%d: %s" % (icomp,str(lbl)))
3049
3180
  sslbls = _sslbls_of_nested_lists_of_simple_labels(lbl)
3050
- if sslbls is None: sslbls = self.line_labels
3181
+ if sslbls is None: sslbls = self._line_labels
3051
3182
 
3052
3183
  dest_layer = icurlayer
3053
3184
  while dest_layer > 0 and len(used_lines[dest_layer - 1].intersection(sslbls)) == 0:
@@ -3056,14 +3187,11 @@ class Circuit(object):
3056
3187
  icomps_to_remove.append(icomp) # remove this label from current layer
3057
3188
  self._append_layer_component(dest_layer, lbl) # add it to the destination layer
3058
3189
  used_lines[dest_layer].update(sslbls) # update used_lines at dest layer
3059
- #print(" <-- layer %d (used=%s)" % (dest_layer,str(used_lines[dest_layer])))
3060
3190
  else:
3061
3191
  #can't move this label forward - update used_lines of current layer
3062
3192
  used_lines[icurlayer].update(sslbls) # update used_lines at dest layer
3063
- #print(" can't move: (cur layer used=%s)" % (str(used_lines[icurlayer])))
3064
-
3193
+
3065
3194
  #Remove components in current layer which were pushed forward
3066
- #print("Removing ",icomps_to_remove," from layer ",icurlayer)
3067
3195
  for icomp in reversed(icomps_to_remove):
3068
3196
  self._remove_layer_component(icurlayer, icomp)
3069
3197
 
@@ -3233,11 +3361,11 @@ class Circuit(object):
3233
3361
  layer_lbl = self.layer_label(j) # (a Label)
3234
3362
  if layer_lbl.sslbls is None:
3235
3363
  if layer_lbl == (): # special case - the completely empty layer: sslbls=None but needs padding
3236
- 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])
3237
3365
  return layer_lbl # all qubits used - no idles to pad
3238
3366
 
3239
3367
  components = list(layer_lbl.components)
3240
- for line_lbl in self.line_labels:
3368
+ for line_lbl in self._line_labels:
3241
3369
  if line_lbl not in layer_lbl.sslbls:
3242
3370
  components.append(_Label(idle_gate_name, line_lbl))
3243
3371
  return _Label(components)
@@ -3288,7 +3416,7 @@ class Circuit(object):
3288
3416
  -------
3289
3417
  int
3290
3418
  """
3291
- return len(self.line_labels)
3419
+ return len(self._line_labels)
3292
3420
 
3293
3421
  @property
3294
3422
  def size(self):
@@ -3307,14 +3435,14 @@ class Circuit(object):
3307
3435
  #TODO HERE -update from here down b/c of sub-circuit blocks
3308
3436
  if self._static:
3309
3437
  def size(lbl): # obj a Label, perhaps compound
3310
- if lbl.is_simple(): # a simple label
3311
- 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)
3312
3440
  else:
3313
3441
  return sum([size(sublbl) for sublbl in lbl.components])
3314
3442
  else:
3315
3443
  def size(obj): # obj is either a simple label or a list
3316
- if isinstance(obj, _Label): # all Labels are simple labels
3317
- 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)
3318
3446
  else:
3319
3447
  return sum([size(sub) for sub in obj])
3320
3448
 
@@ -3342,6 +3470,30 @@ class Circuit(object):
3342
3470
  """
3343
3471
  return self.num_nq_gates(2)
3344
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
+
3345
3497
  def num_nq_gates(self, nq):
3346
3498
  """
3347
3499
  The number of `nq`-qubit gates in the circuit.
@@ -3362,7 +3514,7 @@ class Circuit(object):
3362
3514
  """
3363
3515
  if self._static:
3364
3516
  def cnt(lbl): # obj a Label, perhaps compound
3365
- if lbl.is_simple(): # a simple label
3517
+ if lbl.IS_SIMPLE: # a simple label
3366
3518
  return 1 if (lbl.sslbls is not None) and (len(lbl.sslbls) == nq) else 0
3367
3519
  else:
3368
3520
  return sum([cnt(sublbl) for sublbl in lbl.components])
@@ -3390,7 +3542,7 @@ class Circuit(object):
3390
3542
  """
3391
3543
  if self._static:
3392
3544
  def cnt(lbl): # obj a Label, perhaps compound
3393
- if lbl.is_simple(): # a simple label
3545
+ if lbl.IS_SIMPLE: # a simple label
3394
3546
  return 1 if (lbl.sslbls is not None) and (len(lbl.sslbls) >= 2) else 0
3395
3547
  else:
3396
3548
  return sum([cnt(sublbl) for sublbl in lbl.components])
@@ -3402,52 +3554,17 @@ class Circuit(object):
3402
3554
  return sum([cnt(sub) for sub in obj])
3403
3555
 
3404
3556
  return sum([cnt(layer_lbl) for layer_lbl in self._labels])
3405
-
3406
- # UNUSED
3407
- #def predicted_error_probability(self, gate_error_probabilities):
3408
- # """
3409
- # Predicts the probability that one or more errors occur in the circuit
3410
- # if the gates have the error probabilities specified by in the input
3411
- # dictionary. Given correct error rates for the gates and stochastic errors,
3412
- # this is predictive of the probability of an error in the circuit. But note
3413
- # that that is generally *not* the same as the probability that the circuit
3414
- # implemented is incorrect (e.g., stochastic errors can cancel).
3415
- #
3416
- # Parameters
3417
- # ----------
3418
- # gate_error_probabilities : dict
3419
- # A dictionary where the keys are the labels that appear in the circuit, and
3420
- # the value is the error probability for that gate.
3421
- #
3422
- # Returns
3423
- # -------
3424
- # float
3425
- # The probability that there is one or more errors in the circuit.
3426
- # """
3427
- # f = 1.
3428
- # depth = self.num_layers
3429
- # for i in range(0,self.num_lines):
3430
- # for j in range(0,depth):
3431
- # gatelbl = self.line_items[i][j]
3432
- #
3433
- # # So that we don't include multi-qubit gates more than once.
3434
- # if gatelbl.qubits is None:
3435
- # if i == 0:
3436
- # f = f*(1-gate_error_probabilities[gatelbl])
3437
- # elif gatelbl.qubits[0] == self.line_labels[i]:
3438
- # f = f*(1-gate_error_probabilities[gatelbl])
3439
- # return 1 - f
3440
-
3557
+
3441
3558
  def _togrid(self, identity_name):
3442
3559
  """ return a list-of-lists rep? """
3443
3560
  d = self.num_layers
3444
- 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]
3445
3562
 
3446
3563
  for ilayer in range(len(self._labels)):
3447
3564
  for layercomp in self._layer_components(ilayer):
3448
3565
  if isinstance(layercomp, _Label):
3449
3566
  comp_label = layercomp
3450
- if layercomp.is_simple():
3567
+ if layercomp.IS_SIMPLE:
3451
3568
  comp_sslbls = layercomp.sslbls
3452
3569
  else:
3453
3570
  #We can't intelligently flatten compound labels that occur within a layer-label yet...
@@ -3455,9 +3572,9 @@ class Circuit(object):
3455
3572
  else: # layercomp must be a list (and _static == False)
3456
3573
  comp_label = _Label(layercomp)
3457
3574
  comp_sslbls = _sslbls_of_nested_lists_of_simple_labels(layercomp)
3458
- if comp_sslbls is None: comp_sslbls = self.line_labels
3575
+ if comp_sslbls is None: comp_sslbls = self._line_labels
3459
3576
  for sslbl in comp_sslbls:
3460
- lineIndx = self.line_labels.index(sslbl) # replace w/dict for speed...
3577
+ lineIndx = self._line_labels.index(sslbl) # replace w/dict for speed...
3461
3578
  line_items[lineIndx][ilayer] = comp_label
3462
3579
  return line_items
3463
3580
 
@@ -3476,7 +3593,7 @@ class Circuit(object):
3476
3593
 
3477
3594
  def abbrev(lbl, k): # assumes a simple label w/ name & qubits
3478
3595
  """ Returns what to print on line 'k' for label 'lbl' """
3479
- 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
3480
3597
  nqubits = len(lbl_qubits)
3481
3598
  if nqubits == 1 and lbl.name is not None:
3482
3599
  if isinstance(lbl, _CircuitLabel): # HACK
@@ -3486,12 +3603,12 @@ class Circuit(object):
3486
3603
  else:
3487
3604
  return lbl.name
3488
3605
  elif lbl.name in ('CNOT', 'Gcnot') and nqubits == 2: # qubit indices = (control,target)
3489
- if k == self.line_labels.index(lbl_qubits[0]):
3606
+ if k == self._line_labels.index(lbl_qubits[0]):
3490
3607
  return Ctxt + str(lbl_qubits[1])
3491
3608
  else:
3492
3609
  return Ttxt + str(lbl_qubits[0])
3493
3610
  elif lbl.name in ('CPHASE', 'Gcphase') and nqubits == 2:
3494
- if k == self.line_labels.index(lbl_qubits[0]):
3611
+ if k == self._line_labels.index(lbl_qubits[0]):
3495
3612
  otherqubit = lbl_qubits[1]
3496
3613
  else:
3497
3614
  otherqubit = lbl_qubits[0]
@@ -3506,11 +3623,11 @@ class Circuit(object):
3506
3623
  for i in range(0, self.num_lines)])
3507
3624
  for j in range(0, self.num_layers)]
3508
3625
 
3509
- 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])
3510
3627
 
3511
3628
  for i in range(self.num_lines):
3512
- s += 'Qubit {} '.format(self.line_labels[i]) + ' ' * \
3513
- (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]))) + '---'
3514
3631
  for j, maxlbllen in enumerate(max_labellen):
3515
3632
  if line_items[i][j].name == identityName:
3516
3633
  # Replace with special idle print at some point
@@ -3619,7 +3736,7 @@ class Circuit(object):
3619
3736
  # The quantum wire for qubit q
3620
3737
  circuit_for_q = self.line_items[q]
3621
3738
  for gate in circuit_for_q:
3622
- 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
3623
3740
  nqubits = len(gate_qubits)
3624
3741
  if gate.name == self.identity:
3625
3742
  qstring += r' \qw &'
@@ -3686,6 +3803,12 @@ class Circuit(object):
3686
3803
  gatename_conversion = _itgs.standard_gatenames_cirq_conversions()
3687
3804
  if wait_duration is not None:
3688
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
3689
3812
 
3690
3813
  moments = []
3691
3814
  for i in range(self.num_layers):
@@ -3693,15 +3816,195 @@ class Circuit(object):
3693
3816
  operations = []
3694
3817
  for gate in layer:
3695
3818
  operation = gatename_conversion[gate.name]
3696
- if operation is None:
3697
- # This happens if no idle gate it specified because
3698
- # standard_gatenames_cirq_conversions maps 'Gi' to `None`
3699
- continue
3700
3819
  qubits = map(qubit_conversion.get, gate.qubits)
3701
3820
  operations.append(operation.on(*qubits))
3702
3821
  moments.append(cirq.Moment(operations))
3703
3822
 
3704
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)
3705
4008
 
3706
4009
  def convert_to_quil(self,
3707
4010
  num_qubits=None,
@@ -3768,19 +4071,19 @@ class Circuit(object):
3768
4071
  # To tell us whether we have found a standard qubit labelling type.
3769
4072
  standardtype = False
3770
4073
  # Must first check they are strings, because cannot query q[0] for int q.
3771
- if all([isinstance(q, str) for q in self.line_labels]):
3772
- 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]):
3773
4076
  standardtype = True
3774
- qubit_conversion = {llabel: int(llabel[1:]) for llabel in self.line_labels}
3775
- if all([isinstance(q, int) for q in self.line_labels]):
3776
- 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}
3777
4080
  standardtype = True
3778
4081
  if not standardtype:
3779
4082
  raise ValueError(
3780
4083
  "No standard qubit labelling conversion is available! Please provide `qubit_conversion`.")
3781
4084
 
3782
4085
  if num_qubits is None:
3783
- num_qubits = len(self.line_labels)
4086
+ num_qubits = len(self._line_labels)
3784
4087
 
3785
4088
  # Init the quil string.
3786
4089
  quil = ''
@@ -3808,7 +4111,7 @@ class Circuit(object):
3808
4111
 
3809
4112
  # Go through the (non-self.identity) gates in the layer and convert them to quil
3810
4113
  for gate in layer.components:
3811
- 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
3812
4115
  assert(len(gate_qubits) <= 2 or gate.qubits is None), \
3813
4116
  'Gate on more than 2 qubits given; this is currently not supported!'
3814
4117
 
@@ -3846,7 +4149,7 @@ class Circuit(object):
3846
4149
  qubits_used.extend(gate_qubits)
3847
4150
 
3848
4151
  # All gates that don't have a non-idle gate acting on them get an idle in the layer.
3849
- for q in self.line_labels:
4152
+ for q in self._line_labels:
3850
4153
  if q not in qubits_used:
3851
4154
  quil += 'I' + ' ' + str(qubit_conversion[q]) + '\n'
3852
4155
 
@@ -3861,11 +4164,11 @@ class Circuit(object):
3861
4164
 
3862
4165
  # Add in a measurement at the end.
3863
4166
  if readout_conversion is None:
3864
- for q in self.line_labels:
4167
+ for q in self._line_labels:
3865
4168
  # quil += "MEASURE {0} [{1}]\n".format(str(qubit_conversion[q]),str(qubit_conversion[q]))
3866
4169
  quil += "MEASURE {0} ro[{1}]\n".format(str(qubit_conversion[q]), str(qubit_conversion[q]))
3867
4170
  else:
3868
- for q in self.line_labels:
4171
+ for q in self._line_labels:
3869
4172
  quil += "MEASURE {0} ro[{1}]\n".format(str(qubit_conversion[q]), str(readout_conversion[q]))
3870
4173
 
3871
4174
  return quil
@@ -3875,6 +4178,7 @@ class Circuit(object):
3875
4178
  gatename_conversion=None, qubit_conversion=None,
3876
4179
  block_between_layers=True,
3877
4180
  block_between_gates=False,
4181
+ include_delay_on_idle=True,
3878
4182
  gateargs_map=None): # TODO
3879
4183
  """
3880
4184
  Converts this circuit to an openqasm string.
@@ -3911,6 +4215,17 @@ class Circuit(object):
3911
4215
  When `True`, add in a barrier after every circuit layer. Including such barriers
3912
4216
  can be important for QCVV testing, as this can help reduce the "behind-the-scenes"
3913
4217
  compilation (beyond necessary conversion to native instructions) experience by the circuit.
4218
+
4219
+ block_between_gates: bool, optional
4220
+ When `True`, add in a barrier after every gate (effectively serializing the circuit).
4221
+ Defaults to False.
4222
+
4223
+ include_delay_on_idle: bool, optional
4224
+ When `True`, includes a delay operation on implicit idles in each layer, as per
4225
+ Qiskit's OpenQASM 2.0 convention after the deprecation of the id operation.
4226
+ Defaults to True, which is commensurate with legacy usage of this function.
4227
+ However, this can now be set to False to avoid this behaviour if generating
4228
+ actually valid OpenQASM (with no opaque delay instruction) is desired.
3914
4229
 
3915
4230
  gateargs_map : dict, optional
3916
4231
  If not None, a dict that maps strings (representing pyGSTi standard gate names) to
@@ -3933,22 +4248,19 @@ class Circuit(object):
3933
4248
  # To tell us whether we have found a standard qubit labelling type.
3934
4249
  standardtype = False
3935
4250
  # Must first check they are strings, because cannot query q[0] for int q.
3936
- if all([isinstance(q, str) for q in self.line_labels]):
3937
- 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]):
3938
4253
  standardtype = True
3939
- qubit_conversion = {llabel: int(llabel[1:]) for llabel in self.line_labels}
3940
- if all([isinstance(q, int) for q in self.line_labels]):
3941
- 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}
3942
4257
  standardtype = True
3943
4258
  if not standardtype:
3944
4259
  raise ValueError(
3945
4260
  "No standard qubit labelling conversion is available! Please provide `qubit_conversion`.")
3946
4261
 
3947
4262
  if num_qubits is None:
3948
- num_qubits = len(self.line_labels)
3949
-
3950
- # if gateargs_map is None:
3951
- # gateargs_map = {}
4263
+ num_qubits = len(self._line_labels)
3952
4264
 
3953
4265
  #Currently only using 'Iz' as valid intermediate measurement ('IM') label.
3954
4266
  #Todo: Expand to all intermediate measurements.
@@ -3962,8 +4274,13 @@ class Circuit(object):
3962
4274
 
3963
4275
  # Init the openqasm string.
3964
4276
  openqasm = 'OPENQASM 2.0;\ninclude "qelib1.inc";\n\n'
3965
- # Include a delay instruction
3966
- openqasm += 'opaque delay(t) q;\n\n'
4277
+
4278
+ if include_delay_on_idle:
4279
+ # Include a delay instruction
4280
+ openqasm += 'opaque delay(t) q;\n\n'
4281
+
4282
+ # Add a template for ECR commands that we will replace/remove later
4283
+ openqasm += "ECRPLACEHOLDER"
3967
4284
 
3968
4285
  openqasm += 'qreg q[{0}];\n'.format(str(num_qubits))
3969
4286
  # openqasm += 'creg cr[{0}];\n'.format(str(num_qubits))
@@ -3982,7 +4299,7 @@ class Circuit(object):
3982
4299
 
3983
4300
  # Go through the (non-self.identity) gates in the layer and convert them to openqasm
3984
4301
  for gate in layer.components:
3985
- 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
3986
4303
  assert(len(gate_qubits) <= 2), 'Gates on more than 2 qubits given; this is currently not supported!'
3987
4304
 
3988
4305
  # Find the openqasm for the gate.
@@ -4005,9 +4322,9 @@ class Circuit(object):
4005
4322
  openqasm_for_gate += subopenqasm_for_gate + ' q[' + str(qubit_conversion[q]) + '];\n'
4006
4323
  if block_between_gates:
4007
4324
  openqasm_for_gate += 'barrier '
4008
- for q in self.line_labels[:-1]:
4325
+ for q in self._line_labels[:-1]:
4009
4326
  openqasm_for_gate += 'q[{0}], '.format(str(qubit_conversion[q]))
4010
- 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]]))
4011
4328
 
4012
4329
  else:
4013
4330
  openqasm_for_gate += subopenqasm_for_gate
@@ -4018,9 +4335,9 @@ class Circuit(object):
4018
4335
  openqasm_for_gate += ';\n'
4019
4336
  if block_between_gates:
4020
4337
  openqasm_for_gate += 'barrier '
4021
- for q in self.line_labels[:-1]:
4338
+ for q in self._line_labels[:-1]:
4022
4339
  openqasm_for_gate += 'q[{0}], '.format(str(qubit_conversion[q]))
4023
- 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]]))
4024
4341
 
4025
4342
  else:
4026
4343
  assert len(gate.qubits) == 1
@@ -4039,8 +4356,8 @@ class Circuit(object):
4039
4356
  qubits_used.extend(gate_qubits)
4040
4357
 
4041
4358
  # All gates that don't have a non-idle gate acting on them get an idle in the layer.
4042
- if not block_between_gates:
4043
- for q in self.line_labels:
4359
+ if not block_between_gates and include_delay_on_idle:
4360
+ for q in self._line_labels:
4044
4361
  if q not in qubits_used:
4045
4362
  # Delay 0 works because of the barrier
4046
4363
  # In OpenQASM3, this should probably be a stretch instead
@@ -4054,19 +4371,27 @@ class Circuit(object):
4054
4371
  # where pragma blocks should be.
4055
4372
  if block_between_layers:
4056
4373
  openqasm += 'barrier '
4057
- for q in self.line_labels[:-1]:
4374
+ for q in self._line_labels[:-1]:
4058
4375
  openqasm += 'q[{0}], '.format(str(qubit_conversion[q]))
4059
- 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]]))
4060
4377
  # openqasm += ';'
4061
4378
 
4062
4379
  # Add in a measurement at the end.
4063
- for q in self.line_labels:
4380
+ for q in self._line_labels:
4064
4381
  # openqasm += "measure q[{0}] -> cr[{1}];\n".format(str(qubit_conversion[q]), str(qubit_conversion[q]))
4065
4382
  openqasm += "measure q[{0}] -> cr[{1}];\n".format(str(qubit_conversion[q]),
4066
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)
4067
4391
 
4068
4392
  return openqasm
4069
-
4393
+
4394
+ @_deprecate_fn('Model.probabilites or Model.sim.probs')
4070
4395
  def simulate(self, model, return_all_outcomes=False):
4071
4396
  """
4072
4397
  Compute the outcome probabilities of this Circuit using `model` as a model for the gates.
@@ -4130,108 +4455,10 @@ class Circuit(object):
4130
4455
  """
4131
4456
  if not self._static:
4132
4457
  self._static = True
4133
- self._labels = tuple([_Label(layer_lbl) for layer_lbl in self._labels])
4134
-
4135
- def expand_instruments_and_separate_povm(self, model, observed_outcomes=None):
4136
- """
4137
- Creates a dictionary of :class:`SeparatePOVMCircuit` objects from expanding the instruments of this circuit.
4138
-
4139
- Each key of the returned dictionary replaces the instruments in this circuit with a selection
4140
- of their members. (The size of the resulting dictionary is the product of the sizes of
4141
- each instrument appearing in this circuit when `observed_outcomes is None`). Keys are stored
4142
- as :class:`SeparatePOVMCircuit` objects so it's easy to keep track of which POVM outcomes (effects)
4143
- correspond to observed data. This function is, for the most part, used internally to process
4144
- a circuit before computing its outcome probabilities.
4145
-
4146
- Parameters
4147
- ----------
4148
- model : Model
4149
- The model used to provide necessary details regarding the expansion, including:
4150
-
4151
- - default SPAM layers
4152
- - definitions of instrument-containing layers
4153
- - expansions of individual instruments and POVMs
4154
-
4155
- Returns
4156
- -------
4157
- OrderedDict
4158
- A dict whose keys are :class:`SeparatePOVMCircuit` objects and whose
4159
- values are tuples of the outcome labels corresponding to this circuit,
4160
- one per POVM effect held in the key.
4161
- """
4162
- complete_circuit = model.complete_circuit(self)
4163
- expanded_circuit_outcomes = _collections.OrderedDict()
4164
- povm_lbl = complete_circuit[-1] # "complete" circuits always end with a POVM label
4165
- circuit_without_povm = complete_circuit[0:len(complete_circuit) - 1]
4166
-
4167
- def create_tree(lst):
4168
- subs = _collections.OrderedDict()
4169
- for el in lst:
4170
- if len(el) > 0:
4171
- if el[0] not in subs: subs[el[0]] = []
4172
- subs[el[0]].append(el[1:])
4173
- return _collections.OrderedDict([(k, create_tree(sub_lst)) for k, sub_lst in subs.items()])
4174
-
4175
- def add_expanded_circuit_outcomes(circuit, running_outcomes, ootree, start):
4176
- """
4177
- """
4178
- cir = circuit if start == 0 else circuit[start:] # for performance, avoid uneeded slicing
4179
- for k, layer_label in enumerate(cir, start=start):
4180
- components = layer_label.components
4181
- #instrument_inds = _np.nonzero([model._is_primitive_instrument_layer_lbl(component)
4182
- # for component in components])[0] # SLOWER than statement below
4183
- instrument_inds = _np.array([i for i, component in enumerate(components)
4184
- if model._is_primitive_instrument_layer_lbl(component)])
4185
- if instrument_inds.size > 0:
4186
- # This layer contains at least one instrument => recurse with instrument(s) replaced with
4187
- # all combinations of their members.
4188
- component_lookup = {i: comp for i, comp in enumerate(components)}
4189
- instrument_members = [model._member_labels_for_instrument(components[i])
4190
- for i in instrument_inds] # also components of outcome labels
4191
- for selected_instrmt_members in _itertools.product(*instrument_members):
4192
- expanded_layer_lbl = component_lookup.copy()
4193
- expanded_layer_lbl.update({i: components[i] + "_" + sel
4194
- for i, sel in zip(instrument_inds, selected_instrmt_members)})
4195
- expanded_layer_lbl = _Label([expanded_layer_lbl[i] for i in range(len(components))])
4196
-
4197
- if ootree is not None:
4198
- new_ootree = ootree
4199
- for sel in selected_instrmt_members:
4200
- new_ootree = new_ootree.get(sel, {})
4201
- if len(new_ootree) == 0: continue # no observed outcomes along this outcome-tree path
4202
- else:
4203
- new_ootree = None
4204
-
4205
- add_expanded_circuit_outcomes(circuit[0:k] + Circuit((expanded_layer_lbl,)) + circuit[k + 1:],
4206
- running_outcomes + selected_instrmt_members, new_ootree, k + 1)
4207
- break
4208
-
4209
- else: # no more instruments to process: `cir` contains no instruments => add an expanded circuit
4210
- assert(circuit not in expanded_circuit_outcomes) # shouldn't be possible to generate duplicates...
4211
- elabels = model._effect_labels_for_povm(povm_lbl) if (observed_outcomes is None) \
4212
- else tuple(ootree.keys())
4213
- outcomes = tuple((running_outcomes + (elabel,) for elabel in elabels))
4214
- expanded_circuit_outcomes[SeparatePOVMCircuit(circuit, povm_lbl, elabels)] = outcomes
4215
-
4216
- ootree = create_tree(observed_outcomes) if observed_outcomes is not None else None # tree of observed outcomes
4217
- # e.g. [('0','00'), ('0','01'), ('1','10')] ==> {'0': {'00': {}, '01': {}}, '1': {'10': {}}}
4218
-
4219
- if model._has_instruments():
4220
- add_expanded_circuit_outcomes(circuit_without_povm, (), ootree, start=0)
4221
- else:
4222
- # It may be helpful to cache the set of elabels for a POVM (maybe within the model?) because
4223
- # currently the call to _effect_labels_for_povm may be a bottleneck. It's needed, even when we have
4224
- # observed outcomes, because there may be some observed outcomes that aren't modeled (e.g. leakage states)
4225
- if observed_outcomes is None:
4226
- elabels = model._effect_labels_for_povm(povm_lbl)
4227
- else:
4228
- possible_lbls = set(model._effect_labels_for_povm(povm_lbl))
4229
- elabels = tuple([oo for oo in ootree.keys() if oo in possible_lbls])
4230
- outcomes = tuple(((elabel,) for elabel in elabels))
4231
- expanded_circuit_outcomes[SeparatePOVMCircuit(circuit_without_povm, povm_lbl, elabels)] = outcomes
4232
-
4233
- return expanded_circuit_outcomes
4234
-
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)
4235
4462
 
4236
4463
  class CompressedCircuit(object):
4237
4464
  """
@@ -4286,7 +4513,7 @@ class CompressedCircuit(object):
4286
4513
  self._tup = CompressedCircuit.compress_op_label_tuple(
4287
4514
  circuit.layertup, min_len_to_compress, max_period_to_look_for)
4288
4515
  self._str = circuit.str
4289
- self._line_labels = circuit.line_labels
4516
+ self._line_labels = circuit._line_labels
4290
4517
  self._occurrence_id = circuit.occurrence
4291
4518
 
4292
4519
  def __getstate__(self):
@@ -4420,12 +4647,35 @@ class SeparatePOVMCircuit(object):
4420
4647
  """
4421
4648
  def __init__(self, circuit_without_povm, povm_label, effect_labels):
4422
4649
  self.circuit_without_povm = circuit_without_povm
4423
- self.povm_label = povm_label
4424
- 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])
4425
4653
 
4426
4654
  @property
4427
4655
  def full_effect_labels(self):
4428
- 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
4429
4679
 
4430
4680
  def __len__(self):
4431
4681
  return len(self.circuit_without_povm) # don't count POVM in length, so slicing works as expected
@@ -4440,4 +4690,3 @@ class SeparatePOVMCircuit(object):
4440
4690
  return "SeparatePOVM(" + self.circuit_without_povm.str + "," \
4441
4691
  + str(self.povm_label) + "," + str(self.effect_labels) + ")"
4442
4692
 
4443
- #LATER: add a method for getting the "POVM_effect" labels?