pyGSTi 0.9.12__cp38-cp38-win32.whl → 0.9.13__cp38-cp38-win32.whl
Sign up to get free protection for your applications and to get access to all the features.
- pyGSTi-0.9.13.dist-info/METADATA +185 -0
- {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/RECORD +211 -220
- {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/WHEEL +1 -1
- pygsti/_version.py +2 -2
- pygsti/algorithms/contract.py +1 -1
- pygsti/algorithms/core.py +62 -35
- pygsti/algorithms/fiducialpairreduction.py +95 -110
- pygsti/algorithms/fiducialselection.py +17 -8
- pygsti/algorithms/gaugeopt.py +2 -2
- pygsti/algorithms/germselection.py +87 -77
- pygsti/algorithms/mirroring.py +0 -388
- pygsti/algorithms/randomcircuit.py +165 -1333
- pygsti/algorithms/rbfit.py +0 -234
- pygsti/baseobjs/basis.py +94 -396
- pygsti/baseobjs/errorgenbasis.py +0 -132
- pygsti/baseobjs/errorgenspace.py +0 -10
- pygsti/baseobjs/label.py +52 -168
- pygsti/baseobjs/opcalc/fastopcalc.cp38-win32.pyd +0 -0
- pygsti/baseobjs/opcalc/fastopcalc.pyx +2 -2
- pygsti/baseobjs/polynomial.py +13 -595
- pygsti/baseobjs/protectedarray.py +72 -132
- pygsti/baseobjs/statespace.py +1 -0
- pygsti/circuits/__init__.py +1 -1
- pygsti/circuits/circuit.py +753 -504
- pygsti/circuits/circuitconstruction.py +0 -4
- pygsti/circuits/circuitlist.py +47 -5
- pygsti/circuits/circuitparser/__init__.py +8 -8
- pygsti/circuits/circuitparser/fastcircuitparser.cp38-win32.pyd +0 -0
- pygsti/circuits/circuitstructure.py +3 -3
- pygsti/circuits/cloudcircuitconstruction.py +27 -14
- pygsti/data/datacomparator.py +4 -9
- pygsti/data/dataset.py +51 -46
- pygsti/data/hypothesistest.py +0 -7
- pygsti/drivers/bootstrap.py +0 -49
- pygsti/drivers/longsequence.py +46 -10
- pygsti/evotypes/basereps_cython.cp38-win32.pyd +0 -0
- pygsti/evotypes/chp/opreps.py +0 -61
- pygsti/evotypes/chp/statereps.py +0 -32
- pygsti/evotypes/densitymx/effectcreps.cpp +9 -10
- pygsti/evotypes/densitymx/effectreps.cp38-win32.pyd +0 -0
- pygsti/evotypes/densitymx/effectreps.pyx +1 -1
- pygsti/evotypes/densitymx/opreps.cp38-win32.pyd +0 -0
- pygsti/evotypes/densitymx/opreps.pyx +2 -2
- pygsti/evotypes/densitymx/statereps.cp38-win32.pyd +0 -0
- pygsti/evotypes/densitymx/statereps.pyx +1 -1
- pygsti/evotypes/densitymx_slow/effectreps.py +7 -23
- pygsti/evotypes/densitymx_slow/opreps.py +16 -23
- pygsti/evotypes/densitymx_slow/statereps.py +10 -3
- pygsti/evotypes/evotype.py +39 -2
- pygsti/evotypes/stabilizer/effectreps.cp38-win32.pyd +0 -0
- pygsti/evotypes/stabilizer/effectreps.pyx +0 -4
- pygsti/evotypes/stabilizer/opreps.cp38-win32.pyd +0 -0
- pygsti/evotypes/stabilizer/opreps.pyx +0 -4
- pygsti/evotypes/stabilizer/statereps.cp38-win32.pyd +0 -0
- pygsti/evotypes/stabilizer/statereps.pyx +1 -5
- pygsti/evotypes/stabilizer/termreps.cp38-win32.pyd +0 -0
- pygsti/evotypes/stabilizer/termreps.pyx +0 -7
- pygsti/evotypes/stabilizer_slow/effectreps.py +0 -22
- pygsti/evotypes/stabilizer_slow/opreps.py +0 -4
- pygsti/evotypes/stabilizer_slow/statereps.py +0 -4
- pygsti/evotypes/statevec/effectreps.cp38-win32.pyd +0 -0
- pygsti/evotypes/statevec/effectreps.pyx +1 -1
- pygsti/evotypes/statevec/opreps.cp38-win32.pyd +0 -0
- pygsti/evotypes/statevec/opreps.pyx +2 -2
- pygsti/evotypes/statevec/statereps.cp38-win32.pyd +0 -0
- pygsti/evotypes/statevec/statereps.pyx +1 -1
- pygsti/evotypes/statevec/termreps.cp38-win32.pyd +0 -0
- pygsti/evotypes/statevec/termreps.pyx +0 -7
- pygsti/evotypes/statevec_slow/effectreps.py +0 -3
- pygsti/evotypes/statevec_slow/opreps.py +0 -5
- pygsti/extras/__init__.py +0 -1
- pygsti/extras/drift/signal.py +1 -1
- pygsti/extras/drift/stabilityanalyzer.py +3 -1
- pygsti/extras/interpygate/__init__.py +12 -0
- pygsti/extras/interpygate/core.py +0 -36
- pygsti/extras/interpygate/process_tomography.py +44 -10
- pygsti/extras/rpe/rpeconstruction.py +0 -2
- pygsti/forwardsims/__init__.py +1 -0
- pygsti/forwardsims/forwardsim.py +50 -93
- pygsti/forwardsims/mapforwardsim.py +78 -20
- pygsti/forwardsims/mapforwardsim_calc_densitymx.cp38-win32.pyd +0 -0
- pygsti/forwardsims/mapforwardsim_calc_densitymx.pyx +65 -66
- pygsti/forwardsims/mapforwardsim_calc_generic.py +91 -13
- pygsti/forwardsims/matrixforwardsim.py +72 -17
- pygsti/forwardsims/termforwardsim.py +9 -111
- pygsti/forwardsims/termforwardsim_calc_stabilizer.cp38-win32.pyd +0 -0
- pygsti/forwardsims/termforwardsim_calc_statevec.cp38-win32.pyd +0 -0
- pygsti/forwardsims/termforwardsim_calc_statevec.pyx +0 -651
- pygsti/forwardsims/torchfwdsim.py +265 -0
- pygsti/forwardsims/weakforwardsim.py +2 -2
- pygsti/io/__init__.py +1 -2
- pygsti/io/mongodb.py +0 -2
- pygsti/io/stdinput.py +6 -22
- pygsti/layouts/copalayout.py +10 -12
- pygsti/layouts/distlayout.py +0 -40
- pygsti/layouts/maplayout.py +103 -25
- pygsti/layouts/matrixlayout.py +99 -60
- pygsti/layouts/prefixtable.py +1534 -52
- pygsti/layouts/termlayout.py +1 -1
- pygsti/modelmembers/instruments/instrument.py +3 -3
- pygsti/modelmembers/instruments/tpinstrument.py +2 -2
- pygsti/modelmembers/modelmember.py +0 -17
- pygsti/modelmembers/operations/__init__.py +3 -4
- pygsti/modelmembers/operations/affineshiftop.py +206 -0
- pygsti/modelmembers/operations/composederrorgen.py +1 -1
- pygsti/modelmembers/operations/composedop.py +1 -24
- pygsti/modelmembers/operations/denseop.py +5 -5
- pygsti/modelmembers/operations/eigpdenseop.py +2 -2
- pygsti/modelmembers/operations/embeddederrorgen.py +1 -1
- pygsti/modelmembers/operations/embeddedop.py +0 -1
- pygsti/modelmembers/operations/experrorgenop.py +5 -2
- pygsti/modelmembers/operations/fullarbitraryop.py +1 -0
- pygsti/modelmembers/operations/fullcptpop.py +2 -2
- pygsti/modelmembers/operations/fulltpop.py +28 -6
- pygsti/modelmembers/operations/fullunitaryop.py +5 -4
- pygsti/modelmembers/operations/lindbladcoefficients.py +93 -78
- pygsti/modelmembers/operations/lindbladerrorgen.py +268 -441
- pygsti/modelmembers/operations/linearop.py +7 -27
- pygsti/modelmembers/operations/opfactory.py +1 -1
- pygsti/modelmembers/operations/repeatedop.py +1 -24
- pygsti/modelmembers/operations/staticstdop.py +1 -1
- pygsti/modelmembers/povms/__init__.py +3 -3
- pygsti/modelmembers/povms/basepovm.py +7 -36
- pygsti/modelmembers/povms/complementeffect.py +4 -9
- pygsti/modelmembers/povms/composedeffect.py +0 -320
- pygsti/modelmembers/povms/computationaleffect.py +1 -1
- pygsti/modelmembers/povms/computationalpovm.py +3 -1
- pygsti/modelmembers/povms/effect.py +3 -5
- pygsti/modelmembers/povms/marginalizedpovm.py +3 -81
- pygsti/modelmembers/povms/tppovm.py +74 -2
- pygsti/modelmembers/states/__init__.py +2 -5
- pygsti/modelmembers/states/composedstate.py +0 -317
- pygsti/modelmembers/states/computationalstate.py +3 -3
- pygsti/modelmembers/states/cptpstate.py +4 -4
- pygsti/modelmembers/states/densestate.py +10 -8
- pygsti/modelmembers/states/fullpurestate.py +0 -24
- pygsti/modelmembers/states/purestate.py +1 -1
- pygsti/modelmembers/states/state.py +5 -6
- pygsti/modelmembers/states/tpstate.py +28 -10
- pygsti/modelmembers/term.py +3 -6
- pygsti/modelmembers/torchable.py +50 -0
- pygsti/modelpacks/_modelpack.py +1 -1
- pygsti/modelpacks/smq1Q_ZN.py +3 -1
- pygsti/modelpacks/smq2Q_XXYYII.py +2 -1
- pygsti/modelpacks/smq2Q_XY.py +3 -3
- pygsti/modelpacks/smq2Q_XYI.py +2 -2
- pygsti/modelpacks/smq2Q_XYICNOT.py +3 -3
- pygsti/modelpacks/smq2Q_XYICPHASE.py +3 -3
- pygsti/modelpacks/smq2Q_XYXX.py +1 -1
- pygsti/modelpacks/smq2Q_XYZICNOT.py +3 -3
- pygsti/modelpacks/smq2Q_XYZZ.py +1 -1
- pygsti/modelpacks/stdtarget.py +0 -121
- pygsti/models/cloudnoisemodel.py +1 -2
- pygsti/models/explicitcalc.py +3 -3
- pygsti/models/explicitmodel.py +3 -13
- pygsti/models/fogistore.py +5 -3
- pygsti/models/localnoisemodel.py +1 -2
- pygsti/models/memberdict.py +0 -12
- pygsti/models/model.py +801 -68
- pygsti/models/modelconstruction.py +4 -4
- pygsti/models/modelnoise.py +2 -2
- pygsti/models/modelparaminterposer.py +1 -1
- pygsti/models/oplessmodel.py +1 -1
- pygsti/models/qutrit.py +15 -14
- pygsti/objectivefns/objectivefns.py +75 -140
- pygsti/objectivefns/wildcardbudget.py +2 -7
- pygsti/optimize/__init__.py +1 -0
- pygsti/optimize/arraysinterface.py +28 -0
- pygsti/optimize/customcg.py +0 -12
- pygsti/optimize/customlm.py +129 -323
- pygsti/optimize/customsolve.py +2 -2
- pygsti/optimize/optimize.py +0 -84
- pygsti/optimize/simplerlm.py +841 -0
- pygsti/optimize/wildcardopt.py +19 -598
- pygsti/protocols/confidenceregionfactory.py +28 -14
- pygsti/protocols/estimate.py +31 -14
- pygsti/protocols/gst.py +238 -142
- pygsti/protocols/modeltest.py +19 -12
- pygsti/protocols/protocol.py +9 -37
- pygsti/protocols/rb.py +450 -79
- pygsti/protocols/treenode.py +8 -2
- pygsti/protocols/vb.py +108 -206
- pygsti/protocols/vbdataframe.py +1 -1
- pygsti/report/factory.py +0 -15
- pygsti/report/fogidiagram.py +1 -17
- pygsti/report/modelfunction.py +12 -3
- pygsti/report/mpl_colormaps.py +1 -1
- pygsti/report/plothelpers.py +11 -3
- pygsti/report/report.py +16 -0
- pygsti/report/reportables.py +41 -37
- pygsti/report/templates/offline/pygsti_dashboard.css +6 -0
- pygsti/report/templates/offline/pygsti_dashboard.js +12 -0
- pygsti/report/workspace.py +2 -14
- pygsti/report/workspaceplots.py +328 -505
- pygsti/tools/basistools.py +9 -36
- pygsti/tools/edesigntools.py +124 -96
- pygsti/tools/fastcalc.cp38-win32.pyd +0 -0
- pygsti/tools/fastcalc.pyx +35 -81
- pygsti/tools/internalgates.py +151 -15
- pygsti/tools/jamiolkowski.py +5 -5
- pygsti/tools/lindbladtools.py +19 -11
- pygsti/tools/listtools.py +0 -114
- pygsti/tools/matrixmod2.py +1 -1
- pygsti/tools/matrixtools.py +173 -339
- pygsti/tools/nameddict.py +1 -1
- pygsti/tools/optools.py +154 -88
- pygsti/tools/pdftools.py +0 -25
- pygsti/tools/rbtheory.py +3 -320
- pygsti/tools/slicetools.py +64 -12
- pyGSTi-0.9.12.dist-info/METADATA +0 -157
- pygsti/algorithms/directx.py +0 -711
- pygsti/evotypes/qibo/__init__.py +0 -33
- pygsti/evotypes/qibo/effectreps.py +0 -78
- pygsti/evotypes/qibo/opreps.py +0 -376
- pygsti/evotypes/qibo/povmreps.py +0 -98
- pygsti/evotypes/qibo/statereps.py +0 -174
- pygsti/extras/rb/__init__.py +0 -13
- pygsti/extras/rb/benchmarker.py +0 -957
- pygsti/extras/rb/dataset.py +0 -378
- pygsti/extras/rb/io.py +0 -814
- pygsti/extras/rb/simulate.py +0 -1020
- pygsti/io/legacyio.py +0 -385
- pygsti/modelmembers/povms/denseeffect.py +0 -142
- {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/LICENSE +0 -0
- {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/top_level.txt +0 -0
pygsti/circuits/circuit.py
CHANGED
@@ -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.
|
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.
|
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.
|
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
|
572
|
-
|
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,
|
576
|
-
|
577
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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.
|
613
|
-
self._times = None # for FUTURE expansion
|
547
|
+
#self._times = None # for FUTURE expansion
|
614
548
|
self.auxinfo = {} # for FUTURE expansion / user metadata
|
615
|
-
|
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
|
-
|
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([
|
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
|
673
|
+
return layertup + comp_lbl_flag + self._compilable_layer_indices_tup
|
716
674
|
else:
|
717
|
-
return
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
+ self.
|
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
|
-
|
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.
|
735
|
-
|
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.
|
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.
|
755
|
-
self.
|
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.
|
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.
|
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.
|
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.
|
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
|
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.
|
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
|
-
|
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
|
-
|
861
|
-
|
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.
|
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.
|
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
|
-
|
976
|
+
|
906
977
|
if isinstance(x, Circuit):
|
907
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
933
|
-
|
934
|
-
def copy(self, editable=
|
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
|
-
|
949
|
-
|
950
|
-
|
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.
|
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.
|
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:
|
993
|
-
|
994
|
-
|
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
|
-
|
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].
|
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
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
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
|
-
|
1102
|
-
|
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.
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
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
|
1315
|
-
self._compilable_layer_indices_tup =
|
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
|
-
|
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.
|
1624
|
+
i = len(self._line_labels)
|
1479
1625
|
else:
|
1480
|
-
i = self.
|
1481
|
-
self._line_labels = self.
|
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.
|
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.
|
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
|
1840
|
+
if self._compilable_layer_indices_tup:
|
1693
1841
|
deleted_indices = set(layers)
|
1694
|
-
new_inds =
|
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 =
|
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.
|
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.
|
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.
|
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
|
-
#
|
1945
|
-
#
|
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
|
-
|
1948
|
-
|
1949
|
-
|
1950
|
-
|
1951
|
-
|
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.
|
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.
|
2127
|
-
if line_lbl in self.
|
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.
|
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.
|
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.
|
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.
|
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.
|
2471
|
-
occurrence=self.
|
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.
|
2475
|
-
occurrence=self.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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 =
|
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(
|
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 =
|
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(
|
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.
|
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
|
-
|
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 =
|
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(
|
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.
|
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
|
3059
|
+
if self._compilable_layer_indices_tup:
|
2924
3060
|
depth = len(self._labels)
|
2925
|
-
self._compilable_layer_indices_tup =
|
2926
|
-
|
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.
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
3311
|
-
return len(lbl.sslbls) if (lbl.sslbls is not None) else len(self.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
3575
|
+
if comp_sslbls is None: comp_sslbls = self._line_labels
|
3459
3576
|
for sslbl in comp_sslbls:
|
3460
|
-
lineIndx = self.
|
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.
|
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.
|
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.
|
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.
|
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.
|
3513
|
-
(max_linelabellen - len(str(self.
|
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.
|
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.
|
3772
|
-
if all([q[0] == 'Q' for q in self.
|
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.
|
3775
|
-
if all([isinstance(q, int) for q in self.
|
3776
|
-
qubit_conversion = {q: q for q in self.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
3937
|
-
if all([q[0] == 'Q' for q in self.
|
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.
|
3940
|
-
if all([isinstance(q, int) for q in self.
|
3941
|
-
qubit_conversion = {q: q for q in self.
|
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.
|
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
|
-
|
3966
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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([
|
4134
|
-
|
4135
|
-
|
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.
|
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.
|
4424
|
-
self.
|
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
|
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?
|