pyGSTi 0.9.12__cp39-cp39-win_amd64.whl → 0.9.13__cp39-cp39-win_amd64.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 +197 -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.cp39-win_amd64.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.cp39-win_amd64.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.cp39-win_amd64.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.cp39-win_amd64.pyd +0 -0
- pygsti/evotypes/densitymx/effectreps.pyx +1 -1
- pygsti/evotypes/densitymx/opreps.cp39-win_amd64.pyd +0 -0
- pygsti/evotypes/densitymx/opreps.pyx +2 -2
- pygsti/evotypes/densitymx/statereps.cp39-win_amd64.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.cp39-win_amd64.pyd +0 -0
- pygsti/evotypes/stabilizer/effectreps.pyx +0 -4
- pygsti/evotypes/stabilizer/opreps.cp39-win_amd64.pyd +0 -0
- pygsti/evotypes/stabilizer/opreps.pyx +0 -4
- pygsti/evotypes/stabilizer/statereps.cp39-win_amd64.pyd +0 -0
- pygsti/evotypes/stabilizer/statereps.pyx +1 -5
- pygsti/evotypes/stabilizer/termreps.cp39-win_amd64.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.cp39-win_amd64.pyd +0 -0
- pygsti/evotypes/statevec/effectreps.pyx +1 -1
- pygsti/evotypes/statevec/opreps.cp39-win_amd64.pyd +0 -0
- pygsti/evotypes/statevec/opreps.pyx +2 -2
- pygsti/evotypes/statevec/statereps.cp39-win_amd64.pyd +0 -0
- pygsti/evotypes/statevec/statereps.pyx +1 -1
- pygsti/evotypes/statevec/termreps.cp39-win_amd64.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.cp39-win_amd64.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.cp39-win_amd64.pyd +0 -0
- pygsti/forwardsims/termforwardsim_calc_statevec.cp39-win_amd64.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.cp39-win_amd64.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/models/model.py
CHANGED
@@ -16,7 +16,6 @@ import itertools as _itertools
|
|
16
16
|
import uuid as _uuid
|
17
17
|
import warnings as _warnings
|
18
18
|
import collections as _collections
|
19
|
-
|
20
19
|
import numpy as _np
|
21
20
|
|
22
21
|
from pygsti.baseobjs import statespace as _statespace
|
@@ -25,15 +24,15 @@ from pygsti.models.layerrules import LayerRules as _LayerRules
|
|
25
24
|
from pygsti.models.modelparaminterposer import LinearInterposer as _LinearInterposer
|
26
25
|
from pygsti.evotypes import Evotype as _Evotype
|
27
26
|
from pygsti.forwardsims import forwardsim as _fwdsim
|
28
|
-
from pygsti.forwardsims import mapforwardsim as _mapfwdsim
|
29
|
-
from pygsti.forwardsims import matrixforwardsim as _matrixfwdsim
|
30
27
|
from pygsti.modelmembers import modelmember as _gm
|
31
28
|
from pygsti.modelmembers import operations as _op
|
29
|
+
from pygsti.modelmembers.povms import POVM as _POVM, POVMEffect as _POVMEffect
|
32
30
|
from pygsti.baseobjs.basis import Basis as _Basis, TensorProdBasis as _TensorProdBasis
|
33
31
|
from pygsti.baseobjs.label import Label as _Label
|
34
32
|
from pygsti.baseobjs.resourceallocation import ResourceAllocation as _ResourceAllocation
|
35
33
|
from pygsti.tools import slicetools as _slct
|
36
34
|
from pygsti.tools import matrixtools as _mt
|
35
|
+
from pygsti.circuits import Circuit as _Circuit, SeparatePOVMCircuit as _SeparatePOVMCircuit
|
37
36
|
|
38
37
|
MEMLIMIT_FOR_NONGAUGE_PARAMS = None
|
39
38
|
|
@@ -178,7 +177,8 @@ class Model(_NicelySerializable):
|
|
178
177
|
if lower_bound == -_np.inf and upper_bound == _np.inf:
|
179
178
|
return # do nothing
|
180
179
|
|
181
|
-
if
|
180
|
+
#Note, this property call will also invoke a param vector rebuild if needed.
|
181
|
+
if self.parameter_bounds is None:
|
182
182
|
self._param_bounds = _default_param_bounds(self.num_params)
|
183
183
|
self._param_bounds[index, :] = (lower_bound, upper_bound)
|
184
184
|
|
@@ -467,9 +467,9 @@ class OpModel(Model):
|
|
467
467
|
"""
|
468
468
|
Creates a new OpModel. Rarely used except from derived classes `__init__` functions.
|
469
469
|
"""
|
470
|
-
self._evotype = _Evotype.cast(evotype)
|
471
470
|
self._set_state_space(state_space, basis)
|
472
471
|
#sets self._state_space, self._basis
|
472
|
+
self._evotype = _Evotype.cast(evotype, state_space=self.state_space)
|
473
473
|
|
474
474
|
super(OpModel, self).__init__(self.state_space) # do this as soon as possible
|
475
475
|
|
@@ -481,6 +481,8 @@ class OpModel(Model):
|
|
481
481
|
self._param_interposer = None
|
482
482
|
self._reinit_opcaches()
|
483
483
|
self.fogi_store = None
|
484
|
+
self._index_mm_map = None
|
485
|
+
self._index_mm_label_map = None
|
484
486
|
|
485
487
|
def __setstate__(self, state_dict):
|
486
488
|
self.__dict__.update(state_dict)
|
@@ -505,7 +507,7 @@ class OpModel(Model):
|
|
505
507
|
except:
|
506
508
|
nqubits = None
|
507
509
|
# TODO: This should probably also take evotype (e.g. 'chp' should probably use a CHPForwardSim, etc)
|
508
|
-
self._sim =
|
510
|
+
self._sim = _fwdsim.ForwardSimulator.cast(simulator, nqubits)
|
509
511
|
self._sim.model = self # ensure the simulator's `model` is set to this object
|
510
512
|
|
511
513
|
@property
|
@@ -605,6 +607,106 @@ class OpModel(Model):
|
|
605
607
|
self._clean_paramvec()
|
606
608
|
return len(self._paramvec)
|
607
609
|
|
610
|
+
@property
|
611
|
+
def parameter_labels(self):
|
612
|
+
"""
|
613
|
+
A list of labels, usually of the form `(op_label, string_description)` describing this model's parameters.
|
614
|
+
"""
|
615
|
+
self._clean_paramvec()
|
616
|
+
return self._ops_paramlbls_to_model_paramlbls(self._paramlbls)
|
617
|
+
|
618
|
+
def set_parameter_label(self, index, label):
|
619
|
+
"""
|
620
|
+
Set the label of a single model parameter.
|
621
|
+
|
622
|
+
Parameters
|
623
|
+
----------
|
624
|
+
index : int
|
625
|
+
The index of the paramter whose label should be set.
|
626
|
+
|
627
|
+
label : object
|
628
|
+
An object that serves to label this parameter. Often a string.
|
629
|
+
|
630
|
+
Returns
|
631
|
+
-------
|
632
|
+
None
|
633
|
+
"""
|
634
|
+
self._clean_paramvec()
|
635
|
+
self._paramlbls[index] = label
|
636
|
+
|
637
|
+
@property
|
638
|
+
def parameter_bounds(self):
|
639
|
+
""" Upper and lower bounds on the values of each parameter, utilized by optimization routines """
|
640
|
+
self._clean_paramvec()
|
641
|
+
return self._param_bounds
|
642
|
+
|
643
|
+
@property
|
644
|
+
def num_modeltest_params(self):
|
645
|
+
"""
|
646
|
+
The parameter count to use when testing this model against data.
|
647
|
+
|
648
|
+
Often times, this is the same as :meth:`num_params`, but there are times
|
649
|
+
when it can convenient or necessary to use a parameter count different than
|
650
|
+
the actual number of parameters in this model.
|
651
|
+
|
652
|
+
Returns
|
653
|
+
-------
|
654
|
+
int
|
655
|
+
the number of model parameters.
|
656
|
+
"""
|
657
|
+
self._clean_paramvec()
|
658
|
+
return Model.num_modeltest_params.fget(self)
|
659
|
+
|
660
|
+
@property
|
661
|
+
def parameter_labels(self):
|
662
|
+
"""
|
663
|
+
A list of labels, usually of the form `(op_label, string_description)` describing this model's parameters.
|
664
|
+
"""
|
665
|
+
self._clean_paramvec()
|
666
|
+
return self._ops_paramlbls_to_model_paramlbls(self._paramlbls)
|
667
|
+
|
668
|
+
def set_parameter_label(self, index, label):
|
669
|
+
"""
|
670
|
+
Set the label of a single model parameter.
|
671
|
+
|
672
|
+
Parameters
|
673
|
+
----------
|
674
|
+
index : int
|
675
|
+
The index of the paramter whose label should be set.
|
676
|
+
|
677
|
+
label : object
|
678
|
+
An object that serves to label this parameter. Often a string.
|
679
|
+
|
680
|
+
Returns
|
681
|
+
-------
|
682
|
+
None
|
683
|
+
"""
|
684
|
+
self._clean_paramvec()
|
685
|
+
self._paramlbls[index] = label
|
686
|
+
|
687
|
+
@property
|
688
|
+
def parameter_bounds(self):
|
689
|
+
""" Upper and lower bounds on the values of each parameter, utilized by optimization routines """
|
690
|
+
self._clean_paramvec()
|
691
|
+
return self._param_bounds
|
692
|
+
|
693
|
+
@property
|
694
|
+
def num_modeltest_params(self):
|
695
|
+
"""
|
696
|
+
The parameter count to use when testing this model against data.
|
697
|
+
|
698
|
+
Often times, this is the same as :meth:`num_params`, but there are times
|
699
|
+
when it can convenient or necessary to use a parameter count different than
|
700
|
+
the actual number of parameters in this model.
|
701
|
+
|
702
|
+
Returns
|
703
|
+
-------
|
704
|
+
int
|
705
|
+
the number of model parameters.
|
706
|
+
"""
|
707
|
+
self._clean_paramvec()
|
708
|
+
return Model.num_modeltest_params.fget(self)
|
709
|
+
|
608
710
|
def _iter_parameterized_objs(self):
|
609
711
|
raise NotImplementedError("Derived Model classes should implement _iter_parameterized_objs")
|
610
712
|
#return # default is to have no parameterized objects
|
@@ -648,10 +750,6 @@ class OpModel(Model):
|
|
648
750
|
# flag as a result of this call. `False` is the safe option, as
|
649
751
|
# this call potentially changes this operation's parameters.
|
650
752
|
|
651
|
-
#print("Cleaning Paramvec (dirty=%s, rebuild=%s)" % (self.dirty, self._need_to_rebuild))
|
652
|
-
#import inspect, pprint
|
653
|
-
#pprint.pprint([(x.filename,x.lineno,x.function) for x in inspect.stack()[0:7]])
|
654
|
-
|
655
753
|
if self._need_to_rebuild:
|
656
754
|
self._rebuild_paramvec()
|
657
755
|
self._need_to_rebuild = False
|
@@ -665,17 +763,27 @@ class OpModel(Model):
|
|
665
763
|
# we're confident this code always works.
|
666
764
|
def clean_single_obj(obj, lbl): # sync an object's to_vector result w/_paramvec
|
667
765
|
if obj.dirty:
|
668
|
-
|
766
|
+
try:
|
767
|
+
w = obj.to_vector()
|
768
|
+
except RuntimeError as e:
|
769
|
+
chk_message = 'ComplementPOVMEffect.to_vector() should never be called'
|
770
|
+
# ^ Defined in complementeffect.py::ComplementPOVMEffect::to_vector().
|
771
|
+
if chk_message in str(e):
|
772
|
+
return # there's nothing to do in this call to clean_single_obj().
|
773
|
+
else:
|
774
|
+
raise e # we don't know what went wrong.
|
669
775
|
chk_norm = _np.linalg.norm(ops_paramvec[obj.gpindices] - w)
|
670
776
|
#print(lbl, " is dirty! vec = ", w, " chk_norm = ",chk_norm)
|
671
777
|
if (not _np.isfinite(chk_norm)) or chk_norm > TOL:
|
672
778
|
ops_paramvec[obj.gpindices] = w
|
673
779
|
obj.dirty = False
|
780
|
+
return
|
674
781
|
|
675
782
|
def clean_obj(obj, lbl): # recursive so works with objects that have sub-members
|
676
783
|
for i, subm in enumerate(obj.submembers()):
|
677
784
|
clean_obj(subm, _Label(lbl.name + ":%d" % i, lbl.sslbls))
|
678
785
|
clean_single_obj(obj, lbl)
|
786
|
+
return
|
679
787
|
|
680
788
|
for lbl, obj in self._iter_parameterized_objs():
|
681
789
|
clean_obj(obj, lbl)
|
@@ -889,6 +997,13 @@ class OpModel(Model):
|
|
889
997
|
w = self._model_paramvec_to_ops_paramvec(self._paramvec)
|
890
998
|
Np = len(w) # NOT self.num_params since the latter calls us!
|
891
999
|
wl = self._paramlbls
|
1000
|
+
|
1001
|
+
if self._param_bounds is not None:
|
1002
|
+
msg = 'Internal Model attributes are being rebuilt. This is likely because a modelmember has been '\
|
1003
|
+
+ 'either added or removed. If you have manually set parameter bounds values at the Model level '\
|
1004
|
+
+ '(not the model member level), for example using the `set_parameter_bounds` method, these values '\
|
1005
|
+
+ 'will be overwritten by the parameter bounds found in each of the modelmembers.'
|
1006
|
+
_warnings.warn(msg)
|
892
1007
|
wb = self._param_bounds if (self._param_bounds is not None) else _default_param_bounds(Np)
|
893
1008
|
#NOTE: interposer doesn't quite work with parameter bounds yet, as we need to convert "model"
|
894
1009
|
# bounds to "ops" bounds like we do the parameter vector. Need something like:
|
@@ -1006,7 +1121,6 @@ class OpModel(Model):
|
|
1006
1121
|
Np = len(w) # reset Np from possible new params (NOT self.num_params since the latter calls us!)
|
1007
1122
|
indices_to_remove = sorted(set(range(Np)) - used_gpindices)
|
1008
1123
|
if debug: print("Indices to remove = ", indices_to_remove, " of ", Np)
|
1009
|
-
|
1010
1124
|
if len(indices_to_remove) > 0:
|
1011
1125
|
#if debug: print("DEBUG: Removing %d params:" % len(indices_to_remove), indices_to_remove)
|
1012
1126
|
w = _np.delete(w, indices_to_remove)
|
@@ -1029,9 +1143,13 @@ class OpModel(Model):
|
|
1029
1143
|
obj.set_gpindices(new_inds, self, memo)
|
1030
1144
|
|
1031
1145
|
self._paramvec = self._ops_paramvec_to_model_paramvec(w)
|
1032
|
-
self._paramlbls =
|
1146
|
+
self._paramlbls = wl
|
1033
1147
|
self._param_bounds = wb if _param_bounds_are_nontrivial(wb) else None
|
1034
1148
|
if debug: print("DEBUG: Done rebuild: %d op params" % len(w))
|
1149
|
+
|
1150
|
+
#rebuild the model index to model member map if needed.
|
1151
|
+
self._build_index_mm_map()
|
1152
|
+
|
1035
1153
|
|
1036
1154
|
def _init_virtual_obj(self, obj):
|
1037
1155
|
"""
|
@@ -1058,6 +1176,34 @@ class OpModel(Model):
|
|
1058
1176
|
for _, o in self._iter_parameterized_objs():
|
1059
1177
|
cnt += o._obj_refcount(obj)
|
1060
1178
|
return cnt
|
1179
|
+
|
1180
|
+
def _build_index_mm_map(self):
|
1181
|
+
"""
|
1182
|
+
Build a map between indices into a model's parameter vector and the corresponding children.
|
1183
|
+
The map is a list whose indices are indexes into the model's parameter vector and whose values are
|
1184
|
+
lists (because there can be more than one with parameter collection) of references to the
|
1185
|
+
corresponding child model members who's gpindices correspond it.
|
1186
|
+
"""
|
1187
|
+
|
1188
|
+
#Mapping between the model index and the corresponding model members will be more complicated
|
1189
|
+
#when there is a parameter interposer, so table implementing this for that case.
|
1190
|
+
if self.param_interposer is not None:
|
1191
|
+
self._index_mm_map = None
|
1192
|
+
self._index_mm_label_map = None
|
1193
|
+
else:
|
1194
|
+
index_mm_map = [[] for _ in range(len(self._paramvec))]
|
1195
|
+
index_mm_label_map = [[] for _ in range(len(self._paramvec))]
|
1196
|
+
|
1197
|
+
for lbl, obj in self._iter_parameterized_objs():
|
1198
|
+
#if the gpindices are a slice then convert to a list of indices.
|
1199
|
+
gpindices = _slct.indices(obj.gpindices) if isinstance(obj.gpindices, slice) else obj.gpindices
|
1200
|
+
for gpidx in gpindices:
|
1201
|
+
index_mm_map[gpidx].append(obj)
|
1202
|
+
index_mm_label_map[gpidx].append(lbl)
|
1203
|
+
self._index_mm_map = index_mm_map
|
1204
|
+
self._index_mm_label_map = index_mm_label_map
|
1205
|
+
#Note to future selves. If we add a flag indicating the presence of collected parameters
|
1206
|
+
#then we can improve the performance of this by using a simpler structure when no collected
|
1061
1207
|
|
1062
1208
|
def to_vector(self):
|
1063
1209
|
"""
|
@@ -1106,6 +1252,105 @@ class OpModel(Model):
|
|
1106
1252
|
|
1107
1253
|
if OpModel._pcheck: self._check_paramvec()
|
1108
1254
|
|
1255
|
+
def set_parameter_value(self, index, val, close=False):
|
1256
|
+
"""
|
1257
|
+
This method allows for updating the value of a single model parameter at the
|
1258
|
+
specified parameter index.
|
1259
|
+
|
1260
|
+
Parameters
|
1261
|
+
----------
|
1262
|
+
index : int or tuple
|
1263
|
+
Index of the parameter value in the model's parameter vector to update.
|
1264
|
+
If a tuple this instead indexes by the corresponding parameter label.
|
1265
|
+
|
1266
|
+
val : float
|
1267
|
+
Updated parameter value.
|
1268
|
+
|
1269
|
+
close : bool, optional
|
1270
|
+
Set to `True` if val is close to the current parameter vector.
|
1271
|
+
This can make some operations more efficient.
|
1272
|
+
|
1273
|
+
Returns
|
1274
|
+
-------
|
1275
|
+
None
|
1276
|
+
"""
|
1277
|
+
|
1278
|
+
self.set_parameter_values([index], [val], close)
|
1279
|
+
|
1280
|
+
|
1281
|
+
|
1282
|
+
def set_parameter_values(self, indices, values, close=False):
|
1283
|
+
"""
|
1284
|
+
This method allows for updating the values of multiple model parameter at the
|
1285
|
+
specified parameter indices.
|
1286
|
+
|
1287
|
+
Parameters
|
1288
|
+
----------
|
1289
|
+
indices : list of ints or tuples
|
1290
|
+
Indices of the parameter values in the model's parameter vector to update.
|
1291
|
+
If tuples this instead indexes by the corresponding parameter label.
|
1292
|
+
Mixing integer indices and parameter label tuples is not supported.
|
1293
|
+
Note: In the event that the parameter labels vector for this model contains
|
1294
|
+
duplicates the update may only apply to the first instance.
|
1295
|
+
|
1296
|
+
values : list or tuple of floats
|
1297
|
+
Updated parameter values.
|
1298
|
+
|
1299
|
+
close : bool, optional
|
1300
|
+
Set to `True` if values are close to the current parameter vector.
|
1301
|
+
This can make some operations more efficient.
|
1302
|
+
|
1303
|
+
Returns
|
1304
|
+
-------
|
1305
|
+
None
|
1306
|
+
"""
|
1307
|
+
|
1308
|
+
if isinstance(indices[0], tuple):
|
1309
|
+
#parse the strings into integer indices.
|
1310
|
+
param_labels_list = self.parameter_labels.tolist()
|
1311
|
+
indices = [param_labels_list.index(lbl) for lbl in indices]
|
1312
|
+
|
1313
|
+
for idx, val in zip(indices, values):
|
1314
|
+
self._paramvec[idx] = val
|
1315
|
+
|
1316
|
+
if self._param_interposer is not None or self._index_mm_map is None:
|
1317
|
+
#fall back to standard from_vector call.
|
1318
|
+
self.from_vector(self._paramvec)
|
1319
|
+
else:
|
1320
|
+
#get all of the model members which need to be be updated and loop through them to update their
|
1321
|
+
#parameters.
|
1322
|
+
unique_mms = {lbl:val for idx in indices for lbl, val in zip(self._index_mm_label_map[idx], self._index_mm_map[idx])}
|
1323
|
+
for obj in unique_mms.values():
|
1324
|
+
obj.from_vector(self._paramvec[obj.gpindices].copy(), close, dirty_value=False)
|
1325
|
+
|
1326
|
+
#go through the model members which have been updated and identify whether any of them have children
|
1327
|
+
#which may be present in the _opcaches which have already been updated by the parents. I think the
|
1328
|
+
#conditions under which this should be safe are: a) the layer rules are ExplicitLayerRules,
|
1329
|
+
#b) The parent is a POVM (it should be safe to assume that POVMs update their children,
|
1330
|
+
#and c) the effect is a child of that POVM.
|
1331
|
+
|
1332
|
+
if isinstance(self._layer_rules, _ExplicitLayerRules):
|
1333
|
+
updated_children = []
|
1334
|
+
for obj in unique_mms.values():
|
1335
|
+
if isinstance(obj, _POVM):
|
1336
|
+
updated_children.extend(obj.values())
|
1337
|
+
else:
|
1338
|
+
updated_children = None
|
1339
|
+
|
1340
|
+
# Call from_vector on elements of the cache
|
1341
|
+
if self._call_fromvector_on_cache:
|
1342
|
+
#print(f'{self._opcaches=}')
|
1343
|
+
for opcache in self._opcaches.values():
|
1344
|
+
for obj in opcache.values():
|
1345
|
+
opcache_elem_gpindices = _slct.indices(obj.gpindices) if isinstance(obj.gpindices, slice) else obj.gpindices
|
1346
|
+
if any([idx in opcache_elem_gpindices for idx in indices]):
|
1347
|
+
#check whether we have already updated this object.
|
1348
|
+
if updated_children is not None and any([child is obj for child in updated_children]):
|
1349
|
+
continue
|
1350
|
+
obj.from_vector(self._paramvec[opcache_elem_gpindices], close, dirty_value=False)
|
1351
|
+
|
1352
|
+
if OpModel._pcheck: self._check_paramvec()
|
1353
|
+
|
1109
1354
|
@property
|
1110
1355
|
def param_interposer(self):
|
1111
1356
|
return self._param_interposer
|
@@ -1131,6 +1376,8 @@ class OpModel(Model):
|
|
1131
1376
|
return self.param_interposer.ops_paramlbls_to_model_paramlbls(w) \
|
1132
1377
|
if (self.param_interposer is not None) else w
|
1133
1378
|
|
1379
|
+
#------Model-Specific Circuit Operations------------#
|
1380
|
+
|
1134
1381
|
def circuit_outcomes(self, circuit):
|
1135
1382
|
"""
|
1136
1383
|
Get all the possible outcome labels produced by simulating this circuit.
|
@@ -1142,10 +1389,45 @@ class OpModel(Model):
|
|
1142
1389
|
|
1143
1390
|
Returns
|
1144
1391
|
-------
|
1145
|
-
tuple
|
1392
|
+
tuple corresponding to the possible outcomes for circuit.
|
1146
1393
|
"""
|
1147
|
-
outcomes =
|
1394
|
+
outcomes = self.expand_instruments_and_separate_povm(circuit) # dict w/keys=sep-povm-circuits, vals=outcomes
|
1148
1395
|
return tuple(_itertools.chain(*outcomes.values())) # concatenate outputs from all sep-povm-circuits
|
1396
|
+
|
1397
|
+
def bulk_circuit_outcomes(self, circuits, split_circuits=None, completed_circuits=None):
|
1398
|
+
"""
|
1399
|
+
Get all the possible outcome labels produced by simulating each of the circuits
|
1400
|
+
in this list of circuits.
|
1401
|
+
|
1402
|
+
Parameters
|
1403
|
+
----------
|
1404
|
+
circuits : list of Circuits
|
1405
|
+
list of Circuits to get outcomes of.
|
1406
|
+
|
1407
|
+
split_circuits : list of tuples, optional (default None)
|
1408
|
+
If specified, this is a list of tuples for each circuit corresponding to the splitting of
|
1409
|
+
the circuit into the prep label, spam-free circuit, and povm label. This is the same format
|
1410
|
+
produced by the :meth:split_circuit(s) method, and so this option can allow for accelerating this
|
1411
|
+
method when that has previously been run. When using this kwarg only one of this or
|
1412
|
+
the `complete_circuits` kwargs should be used.
|
1413
|
+
|
1414
|
+
completed_circuits : list of Circuits, optional (default None)
|
1415
|
+
If specified, this is a list of compeleted circuits with prep and povm labels included.
|
1416
|
+
This is the format produced by the :meth:complete_circuit(s) method, and this can
|
1417
|
+
be used to accelerate this method call when that has been previously run. Should not
|
1418
|
+
be used in conjunction with `split_circuits`.
|
1419
|
+
|
1420
|
+
Returns
|
1421
|
+
-------
|
1422
|
+
list of tuples corresponding to the possible outcomes for each circuit.
|
1423
|
+
"""
|
1424
|
+
|
1425
|
+
# list of dict w/keys=sep-povm-circuits, vals=outcomes
|
1426
|
+
outcomes_list = self.bulk_expand_instruments_and_separate_povm(circuits,
|
1427
|
+
split_circuits=split_circuits,
|
1428
|
+
completed_circuits=completed_circuits)
|
1429
|
+
|
1430
|
+
return [tuple(_itertools.chain(*outcomes.values())) for outcomes in outcomes_list] # concatenate outputs from all sep-povm-circuits
|
1149
1431
|
|
1150
1432
|
def split_circuit(self, circuit, erroron=('prep', 'povm'), split_prep=True, split_povm=True):
|
1151
1433
|
"""
|
@@ -1181,39 +1463,146 @@ class OpModel(Model):
|
|
1181
1463
|
|
1182
1464
|
Returns
|
1183
1465
|
-------
|
1184
|
-
prep_label :
|
1466
|
+
prep_label : Label or None
|
1185
1467
|
ops_only_circuit : Circuit
|
1186
|
-
povm_label :
|
1187
|
-
"""
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1468
|
+
povm_label : Label or None
|
1469
|
+
"""
|
1470
|
+
|
1471
|
+
split_circuit = self.split_circuits([circuit], erroron, split_prep, split_povm)
|
1472
|
+
return split_circuit[0]
|
1473
|
+
|
1474
|
+
|
1475
|
+
def split_circuits(self, circuits, erroron=('prep', 'povm'), split_prep=True, split_povm=True):
|
1476
|
+
"""
|
1477
|
+
Splits a circuit into prep_layer + op_layers + povm_layer components.
|
1478
|
+
|
1479
|
+
If `circuit` does not contain a prep label or a
|
1480
|
+
povm label a default label is returned if one exists.
|
1481
|
+
|
1482
|
+
Parameters
|
1483
|
+
----------
|
1484
|
+
circuit : list of Circuit
|
1485
|
+
A list of circuits, possibly beginning with a state preparation
|
1486
|
+
label and ending with a povm label.
|
1487
|
+
|
1488
|
+
erroron : tuple of {'prep','povm'}
|
1489
|
+
A ValueError is raised if a preparation or povm label cannot be
|
1490
|
+
resolved when 'prep' or 'povm' is included in 'erroron'. Otherwise
|
1491
|
+
`None` is returned in place of unresolvable labels. An exception
|
1492
|
+
is when this model has no preps or povms, in which case `None`
|
1493
|
+
is always returned and errors are never raised, since in this
|
1494
|
+
case one usually doesn't expect to use the Model to compute
|
1495
|
+
probabilities (e.g. in germ selection).
|
1496
|
+
|
1497
|
+
split_prep : bool, optional
|
1498
|
+
Whether to split off the state prep and return it as `prep_label`. If
|
1499
|
+
`False`, then the returned preparation label is always `None`, and is
|
1500
|
+
not removed from `ops_only_circuit`.
|
1501
|
+
|
1502
|
+
split_povm : bool, optional
|
1503
|
+
Whether to split off the POVM and return it as `povm_label`. If
|
1504
|
+
`False`, then the returned POVM label is always `None`, and is
|
1505
|
+
not removed from `ops_only_circuit`.
|
1506
|
+
|
1507
|
+
Returns
|
1508
|
+
-------
|
1509
|
+
list of tuples containing
|
1510
|
+
prep_label : Label or None
|
1511
|
+
ops_only_circuit : Circuit
|
1512
|
+
povm_label : Label or None
|
1513
|
+
"""
|
1514
|
+
|
1515
|
+
#precompute unique default povm labels.
|
1516
|
+
unique_sslbls = set([ckt._line_labels for ckt in circuits])
|
1517
|
+
default_povm_labels = {sslbls:self._default_primitive_povm_layer_lbl(sslbls) for sslbls in unique_sslbls}
|
1518
|
+
|
1519
|
+
if split_prep and split_povm: #can avoid some duplicated effort in this case.
|
1520
|
+
#get the tuple of prep and povm labels to avoid having to access through dict
|
1521
|
+
#many times.
|
1522
|
+
primitive_prep_labels_tup = self.primitive_prep_labels
|
1523
|
+
primitive_povm_labels_tup = self.primitive_povm_labels
|
1524
|
+
primitive_prep_labels_set = set(primitive_prep_labels_tup)
|
1525
|
+
primitive_povm_labels_set = set(primitive_povm_labels_tup)
|
1526
|
+
|
1527
|
+
split_circuits = []
|
1528
|
+
for ckt in circuits:
|
1529
|
+
if len(ckt) > 0 and ckt[0] in primitive_prep_labels_set:
|
1530
|
+
prep_lbl = ckt[0]
|
1531
|
+
circuit = ckt[1:]
|
1532
|
+
elif len(primitive_prep_labels_tup)==1:
|
1533
|
+
prep_lbl = primitive_prep_labels_tup[0]
|
1534
|
+
circuit = None
|
1535
|
+
else:
|
1536
|
+
if 'prep' in erroron and self._has_primitive_preps():
|
1537
|
+
msg = f"Cannot resolve state prep in {ckt}. There are likely multiple preps in this model."
|
1538
|
+
raise ValueError(msg)
|
1539
|
+
else:
|
1540
|
+
prep_lbl = None
|
1541
|
+
circuit = None
|
1542
|
+
|
1543
|
+
if len(ckt) > 0 and ckt[-1] in primitive_povm_labels_set:
|
1544
|
+
povm_lbl = ckt[-1]
|
1545
|
+
circuit = circuit[:-1] if circuit is not None else ckt[:-1]
|
1546
|
+
elif default_povm_labels[ckt._line_labels] is not None:
|
1547
|
+
povm_lbl = default_povm_labels[ckt._line_labels]
|
1548
|
+
else:
|
1549
|
+
if 'povm' in erroron and self._has_primitive_povms():
|
1550
|
+
msg = f"Cannot resolve POVM in {ckt}."
|
1551
|
+
raise ValueError(msg)
|
1552
|
+
else:
|
1553
|
+
povm_lbl = None
|
1554
|
+
split_circuits.append((prep_lbl, circuit, povm_lbl))
|
1555
|
+
|
1556
|
+
elif split_prep:
|
1557
|
+
#get the tuple of prep labels to avoid having to access through dict
|
1558
|
+
#many times.
|
1559
|
+
primitive_prep_labels_tup = self.primitive_prep_labels
|
1560
|
+
primitive_prep_labels_set = set(primitive_prep_labels_tup)
|
1561
|
+
|
1562
|
+
split_circuits = []
|
1563
|
+
for ckt in circuits:
|
1564
|
+
if len(ckt) > 0 and ckt[0] in primitive_prep_labels_set:
|
1565
|
+
prep_lbl = ckt[0]
|
1566
|
+
circuit = ckt[1:]
|
1567
|
+
elif primitive_prep_labels_tup:
|
1568
|
+
prep_lbl = primitive_prep_labels_tup[0]
|
1569
|
+
circuit = ckt
|
1570
|
+
else:
|
1571
|
+
if 'prep' in erroron and self._has_primitive_preps():
|
1572
|
+
raise ValueError("Cannot resolve state prep in %s" % circuit)
|
1573
|
+
else:
|
1574
|
+
prep_lbl = None
|
1575
|
+
circuit = ckt
|
1576
|
+
split_circuits.append((prep_lbl, circuit, None))
|
1577
|
+
|
1578
|
+
elif split_povm:
|
1579
|
+
#get the tuple of povm labels to avoid having to access through dict
|
1580
|
+
#many times.
|
1581
|
+
primitive_povm_labels_tup = self.primitive_povm_labels
|
1582
|
+
primitive_povm_labels_set = set(primitive_povm_labels_tup)
|
1583
|
+
|
1584
|
+
split_circuits = []
|
1585
|
+
for ckt in circuits:
|
1586
|
+
if len(ckt) > 0 and ckt[-1] in primitive_povm_labels_set:
|
1587
|
+
povm_lbl = ckt[-1]
|
1588
|
+
circuit = ckt[:-1]
|
1589
|
+
elif default_povm_labels[ckt._line_labels] is not None:
|
1590
|
+
povm_lbl = default_povm_labels[ckt._line_labels]
|
1591
|
+
circuit = ckt
|
1592
|
+
else:
|
1593
|
+
if 'povm' in erroron and self._has_primitive_povms():
|
1594
|
+
raise ValueError("Cannot resolve POVM in %s" % str(circuit))
|
1595
|
+
else:
|
1596
|
+
povm_lbl = None
|
1597
|
+
circuit = ckt
|
1598
|
+
split_circuits.append((None, circuit, povm_lbl))
|
1599
|
+
|
1211
1600
|
else:
|
1212
|
-
|
1601
|
+
split_circuits = [(None, ckt, None) for ckt in circuits]
|
1213
1602
|
|
1214
|
-
return
|
1603
|
+
return split_circuits
|
1215
1604
|
|
1216
|
-
def complete_circuit(self, circuit):
|
1605
|
+
def complete_circuit(self, circuit, prep_lbl_to_prepend=None, povm_lbl_to_append=None):
|
1217
1606
|
"""
|
1218
1607
|
Adds any implied preparation or measurement layers to `circuit`
|
1219
1608
|
|
@@ -1224,39 +1613,380 @@ class OpModel(Model):
|
|
1224
1613
|
----------
|
1225
1614
|
circuit : Circuit
|
1226
1615
|
Circuit to act on.
|
1616
|
+
|
1617
|
+
prep_lbl_to_prepend : Label, optional (default None)
|
1618
|
+
Optional user specified prep label to prepend. If not
|
1619
|
+
specified will use the default value as given by
|
1620
|
+
:meth:_default_primitive_prep_layer_lbl. If the circuit
|
1621
|
+
already has a prep label this argument will be ignored.
|
1622
|
+
|
1623
|
+
povm_lbl_to_append : Label, optional (default None)
|
1624
|
+
Optional user specified prep label to prepend. If not
|
1625
|
+
specified will use the default value as given by
|
1626
|
+
:meth:_default_primitive_prep_layer_lbl. If the circuit
|
1627
|
+
already has a prep label this argument will be ignored.
|
1628
|
+
Returns
|
1629
|
+
-------
|
1630
|
+
Circuit
|
1631
|
+
Possibly the same object as `circuit`, if no additions are needed.
|
1632
|
+
"""
|
1633
|
+
comp_circuit = self.complete_circuits([circuit], prep_lbl_to_prepend, povm_lbl_to_append, False)
|
1634
|
+
return comp_circuit[0]
|
1635
|
+
|
1636
|
+
def expand_instruments_and_separate_povm(self, circuit, observed_outcomes=None):
|
1637
|
+
"""
|
1638
|
+
Creates a dictionary of :class:`SeparatePOVMCircuit` objects from expanding the instruments of this circuit.
|
1639
|
+
|
1640
|
+
Each key of the returned dictionary replaces the instruments in this circuit with a selection
|
1641
|
+
of their members. (The size of the resulting dictionary is the product of the sizes of
|
1642
|
+
each instrument appearing in this circuit when `observed_outcomes is None`). Keys are stored
|
1643
|
+
as :class:`SeparatePOVMCircuit` objects so it's easy to keep track of which POVM outcomes (effects)
|
1644
|
+
correspond to observed data. This function is, for the most part, used internally to process
|
1645
|
+
a circuit before computing its outcome probabilities.
|
1646
|
+
|
1647
|
+
Parameters
|
1648
|
+
----------
|
1649
|
+
circuit : Circuit
|
1650
|
+
The circuit to expand, using necessary details regarding the expansion from this model, including:
|
1651
|
+
|
1652
|
+
- default SPAM layers
|
1653
|
+
- definitions of instrument-containing layers
|
1654
|
+
- expansions of individual instruments and POVMs
|
1655
|
+
|
1656
|
+
observed_outcomes : iterable, optional (default None)
|
1657
|
+
If specified an iterable over the subset of outcomes empirically observed for this circuit.
|
1227
1658
|
|
1228
1659
|
Returns
|
1229
1660
|
-------
|
1661
|
+
OrderedDict
|
1662
|
+
A dict whose keys are :class:`SeparatePOVMCircuit` objects and whose
|
1663
|
+
values are tuples of the outcome labels corresponding to this circuit,
|
1664
|
+
one per POVM effect held in the key.
|
1665
|
+
"""
|
1666
|
+
expanded_circuit_outcomes = self.bulk_expand_instruments_and_separate_povm([circuit], [observed_outcomes])
|
1667
|
+
return expanded_circuit_outcomes[0]
|
1668
|
+
|
1669
|
+
def bulk_expand_instruments_and_separate_povm(self, circuits, observed_outcomes_list=None, split_circuits = None,
|
1670
|
+
completed_circuits = None):
|
1671
|
+
"""
|
1672
|
+
Creates a list of dictionaries mapping from :class:`SeparatePOVMCircuit`
|
1673
|
+
objects from expanding the instruments of this circuit.
|
1674
|
+
|
1675
|
+
Each key of the returned dictionary replaces the instruments in this circuit with a selection
|
1676
|
+
of their members. (The size of the resulting dictionary is the product of the sizes of
|
1677
|
+
each instrument appearing in this circuit when `observed_outcomes is None`). Keys are stored
|
1678
|
+
as :class:`SeparatePOVMCircuit` objects so it's easy to keep track of which POVM outcomes (effects)
|
1679
|
+
correspond to observed data. This function is, for the most part, used internally to process
|
1680
|
+
a circuit before computing its outcome probabilities.
|
1681
|
+
|
1682
|
+
This function works similarly to expand_instruments_and_separate_povm, except it operates on
|
1683
|
+
an entire list of circuits at once, and provides additional kwargs to accelerate computation.
|
1684
|
+
|
1685
|
+
Parameters
|
1686
|
+
----------
|
1687
|
+
circuit : Circuit
|
1688
|
+
The circuit to expand, using necessary details regarding the expansion from this model, including:
|
1689
|
+
|
1690
|
+
- default SPAM layers
|
1691
|
+
- definitions of instrument-containing layers
|
1692
|
+
- expansions of individual instruments and POVMs
|
1693
|
+
|
1694
|
+
observed_outcomes_list : list of iterables, optional (default None)
|
1695
|
+
If specified a list of iterables over the subset of outcomes empirically observed for each circuit.
|
1696
|
+
|
1697
|
+
split_circuits : list of tuples, optional (default None)
|
1698
|
+
If specified, this is a list of tuples for each circuit corresponding to the splitting of
|
1699
|
+
the circuit into the prep label, spam-free circuit, and povm label. This is the same format
|
1700
|
+
produced by the :meth:split_circuit(s) method, and so this option can allow for accelerating this
|
1701
|
+
method when that has previously been run. When using this kwarg only one of this or
|
1702
|
+
the `complete_circuits` kwargs should be used.
|
1703
|
+
|
1704
|
+
completed_circuits : list of Circuits, optional (default None)
|
1705
|
+
If specified, this is a list of compeleted circuits with prep and povm labels included.
|
1706
|
+
This is the format produced by the :meth:complete_circuit(s) method, and this can
|
1707
|
+
be used to accelerate this method call when that has been previously run. Should not
|
1708
|
+
be used in conjunction with `split_circuits`.
|
1709
|
+
|
1710
|
+
Returns
|
1711
|
+
-------
|
1712
|
+
list of OrderedDicts
|
1713
|
+
A list of dictionaries whose keys are :class:`SeparatePOVMCircuit` objects and whose
|
1714
|
+
values are tuples of the outcome labels corresponding to each circuit,
|
1715
|
+
one per POVM effect held in the key.
|
1716
|
+
"""
|
1717
|
+
|
1718
|
+
assert(not (completed_circuits is not None and split_circuits is not None)), "Inclusion of non-trivial values"\
|
1719
|
+
+" for both `complete_circuits` and `split_circuits` is not supported. Please use only one of these two arguments."
|
1720
|
+
|
1721
|
+
if split_circuits is not None:
|
1722
|
+
povm_lbls = [split_ckt[2] for split_ckt in split_circuits]
|
1723
|
+
circuits_without_povm = [(split_ckt[0],) + split_ckt[1] for split_ckt in split_circuits]
|
1724
|
+
elif completed_circuits is not None:
|
1725
|
+
povm_lbls = [comp_ckt[-1] for comp_ckt in completed_circuits]
|
1726
|
+
circuits_without_povm = [comp_ckt[:-1] for comp_ckt in completed_circuits]
|
1727
|
+
else:
|
1728
|
+
completed_circuits = self.complete_circuits(circuits)
|
1729
|
+
povm_lbls = [comp_ckt[-1] for comp_ckt in completed_circuits]
|
1730
|
+
circuits_without_povm = [comp_ckt[:-1] for comp_ckt in completed_circuits]
|
1731
|
+
|
1732
|
+
if observed_outcomes_list is None:
|
1733
|
+
observed_outcomes_list = [None]*len(circuits)
|
1734
|
+
|
1735
|
+
|
1736
|
+
expanded_circuit_outcomes_list = [_collections.OrderedDict() for _ in range(len(circuits))]
|
1737
|
+
|
1738
|
+
def create_tree(lst):
|
1739
|
+
subs = _collections.OrderedDict()
|
1740
|
+
for el in lst:
|
1741
|
+
if len(el) > 0:
|
1742
|
+
if el[0] not in subs: subs[el[0]] = []
|
1743
|
+
subs[el[0]].append(el[1:])
|
1744
|
+
return _collections.OrderedDict([(k, create_tree(sub_lst)) for k, sub_lst in subs.items()])
|
1745
|
+
|
1746
|
+
def add_expanded_circuit_outcomes(circuit, running_outcomes, ootree, start):
|
1747
|
+
"""
|
1748
|
+
"""
|
1749
|
+
cir = circuit if start == 0 else circuit[start:] # for performance, avoid uneeded slicing
|
1750
|
+
for k, layer_label in enumerate(cir, start=start):
|
1751
|
+
components = layer_label.components
|
1752
|
+
#instrument_inds = _np.nonzero([model._is_primitive_instrument_layer_lbl(component)
|
1753
|
+
# for component in components])[0] # SLOWER than statement below
|
1754
|
+
instrument_inds = _np.array([i for i, component in enumerate(components)
|
1755
|
+
if self._is_primitive_instrument_layer_lbl(component)])
|
1756
|
+
if instrument_inds.size > 0:
|
1757
|
+
# This layer contains at least one instrument => recurse with instrument(s) replaced with
|
1758
|
+
# all combinations of their members.
|
1759
|
+
component_lookup = {i: comp for i, comp in enumerate(components)}
|
1760
|
+
instrument_members = [self._member_labels_for_instrument(components[i])
|
1761
|
+
for i in instrument_inds] # also components of outcome labels
|
1762
|
+
for selected_instrmt_members in _itertools.product(*instrument_members):
|
1763
|
+
expanded_layer_lbl = component_lookup.copy()
|
1764
|
+
expanded_layer_lbl.update({i: components[i] + "_" + sel
|
1765
|
+
for i, sel in zip(instrument_inds, selected_instrmt_members)})
|
1766
|
+
expanded_layer_lbl = _Label([expanded_layer_lbl[i] for i in range(len(components))])
|
1767
|
+
|
1768
|
+
if ootree is not None:
|
1769
|
+
new_ootree = ootree
|
1770
|
+
for sel in selected_instrmt_members:
|
1771
|
+
new_ootree = new_ootree.get(sel, {})
|
1772
|
+
if len(new_ootree) == 0: continue # no observed outcomes along this outcome-tree path
|
1773
|
+
else:
|
1774
|
+
new_ootree = None
|
1775
|
+
|
1776
|
+
add_expanded_circuit_outcomes(circuit[0:k] + _Circuit((expanded_layer_lbl,)) + circuit[k + 1:],
|
1777
|
+
running_outcomes + selected_instrmt_members, new_ootree, k + 1)
|
1778
|
+
break
|
1779
|
+
|
1780
|
+
else: # no more instruments to process: `cir` contains no instruments => add an expanded circuit
|
1781
|
+
assert(circuit not in expanded_circuit_outcomes) # shouldn't be possible to generate duplicates...
|
1782
|
+
elabels = self._effect_labels_for_povm(povm_lbl) if (observed_outcomes is None) \
|
1783
|
+
else tuple(ootree.keys())
|
1784
|
+
outcomes = tuple((running_outcomes + (elabel,) for elabel in elabels))
|
1785
|
+
expanded_circuit_outcomes[_SeparatePOVMCircuit(circuit, povm_lbl, elabels)] = outcomes
|
1786
|
+
|
1787
|
+
has_instruments = self._has_instruments()
|
1788
|
+
unique_povm_labels = set(povm_lbls)
|
1789
|
+
effect_label_dict = {povm_lbl: self._effect_labels_for_povm(povm_lbl) for povm_lbl in unique_povm_labels}
|
1790
|
+
|
1791
|
+
for povm_lbl, circuit_without_povm, expanded_circuit_outcomes, observed_outcomes in zip(povm_lbls, circuits_without_povm,
|
1792
|
+
expanded_circuit_outcomes_list,
|
1793
|
+
observed_outcomes_list):
|
1794
|
+
ootree = create_tree(observed_outcomes) if observed_outcomes is not None else None # tree of observed outcomes
|
1795
|
+
# e.g. [('0','00'), ('0','01'), ('1','10')] ==> {'0': {'00': {}, '01': {}}, '1': {'10': {}}}
|
1796
|
+
|
1797
|
+
if has_instruments:
|
1798
|
+
add_expanded_circuit_outcomes(circuit_without_povm, (), ootree, start=0)
|
1799
|
+
else:
|
1800
|
+
# It may be helpful to cache the set of elabels for a POVM (maybe within the model?) because
|
1801
|
+
# currently the call to _effect_labels_for_povm may be a bottleneck. It's needed, even when we have
|
1802
|
+
# observed outcomes, because there may be some observed outcomes that aren't modeled (e.g. leakage states)
|
1803
|
+
if observed_outcomes is None:
|
1804
|
+
elabels = effect_label_dict[povm_lbl]
|
1805
|
+
else:
|
1806
|
+
possible_lbls = set(effect_label_dict[povm_lbl])
|
1807
|
+
elabels = tuple([oo for oo in ootree.keys() if oo in possible_lbls])
|
1808
|
+
outcomes = tuple(((elabel,) for elabel in elabels))
|
1809
|
+
expanded_circuit_outcomes[_SeparatePOVMCircuit(circuit_without_povm, povm_lbl, elabels)] = outcomes
|
1810
|
+
|
1811
|
+
return expanded_circuit_outcomes_list
|
1812
|
+
|
1813
|
+
def complete_circuits(self, circuits, prep_lbl_to_prepend=None, povm_lbl_to_append=None, return_split = False):
|
1814
|
+
"""
|
1815
|
+
Adds any implied preparation or measurement layers to list of circuits.
|
1816
|
+
|
1817
|
+
Converts `circuit` into a "complete circuit", where the first (0-th)
|
1818
|
+
layer is a state preparation and the final layer is a measurement (POVM) layer.
|
1819
|
+
|
1820
|
+
Parameters
|
1821
|
+
----------
|
1822
|
+
circuits : list of Circuit
|
1823
|
+
List of Circuit objects to act on.
|
1824
|
+
|
1825
|
+
prep_lbl_to_prepend : Label, optional (default None)
|
1826
|
+
Optional user specified prep label to prepend. If not
|
1827
|
+
specified will use the default value as given by
|
1828
|
+
:meth:_default_primitive_prep_layer_lbl. If the circuit
|
1829
|
+
already has a prep label this argument will be ignored.
|
1830
|
+
|
1831
|
+
povm_lbl_to_append : Label, optional (default None)
|
1832
|
+
Optional user specified prep label to prepend. If not
|
1833
|
+
specified will use the default value as given by
|
1834
|
+
:meth:_default_primitive_prep_layer_lbl. If the circuit
|
1835
|
+
already has a prep label this argument will be ignored.
|
1836
|
+
|
1837
|
+
return_split : bool, optional (default False)
|
1838
|
+
If True we additionally return a list of tuples of the form:
|
1839
|
+
(prep_label, no_spam_circuit, povm_label)
|
1840
|
+
for each circuit. This is of the same format returned by
|
1841
|
+
:meth:split_circuits when using the kwarg combination:
|
1842
|
+
erroron=('prep', 'povm'), split_prep=True, split_povm=True
|
1843
|
+
Returns
|
1844
|
+
-------
|
1230
1845
|
Circuit
|
1231
1846
|
Possibly the same object as `circuit`, if no additions are needed.
|
1232
1847
|
"""
|
1233
|
-
prep_lbl_to_prepend = None
|
1234
|
-
povm_lbl_to_append = None
|
1235
1848
|
|
1236
|
-
if
|
1849
|
+
if prep_lbl_to_prepend is None:
|
1237
1850
|
prep_lbl_to_prepend = self._default_primitive_prep_layer_lbl()
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
if
|
1257
|
-
|
1851
|
+
prep_lbl_tup_to_prepend = (prep_lbl_to_prepend,)
|
1852
|
+
else:
|
1853
|
+
prep_lbl_tup_to_prepend = (prep_lbl_to_prepend,)
|
1854
|
+
|
1855
|
+
#get the tuple of povm labels to avoid having to access through dict
|
1856
|
+
#many times.
|
1857
|
+
primitive_prep_labels = set(self.primitive_prep_labels)
|
1858
|
+
primitive_povm_labels = set(self.primitive_povm_labels)
|
1859
|
+
|
1860
|
+
#precompute unique default povm labels.
|
1861
|
+
unique_sslbls = set([ckt._line_labels for ckt in circuits])
|
1862
|
+
default_povm_labels = {sslbls:(self._default_primitive_povm_layer_lbl(sslbls),) for sslbls in unique_sslbls}
|
1863
|
+
|
1864
|
+
comp_circuits = []
|
1865
|
+
if return_split:
|
1866
|
+
split_circuits = []
|
1867
|
+
|
1868
|
+
for ckt in circuits:
|
1869
|
+
if len(ckt) == 0 or not ckt[0] in primitive_prep_labels:
|
1870
|
+
if prep_lbl_to_prepend is None:
|
1871
|
+
raise ValueError(f"Missing state prep in {ckt.str} and there's no default!")
|
1872
|
+
else:
|
1873
|
+
current_prep_lbl_to_prepend = prep_lbl_tup_to_prepend
|
1874
|
+
else:
|
1875
|
+
current_prep_lbl_to_prepend = ()
|
1258
1876
|
|
1259
|
-
|
1877
|
+
if len(ckt) == 0 or (not ckt[-1] in primitive_povm_labels and not ckt[-1].name in primitive_povm_labels):
|
1878
|
+
current_povm_lbl_to_append = (povm_lbl_to_append,) if povm_lbl_to_append is not None else default_povm_labels[ckt._line_labels]
|
1879
|
+
if current_povm_lbl_to_append[0] is None: #if still None we have no default and raise an error.
|
1880
|
+
raise ValueError(f"Missing POVM in {ckt.str} and there's no default!")
|
1881
|
+
else:
|
1882
|
+
current_povm_lbl_to_append = ()
|
1883
|
+
|
1884
|
+
if return_split:
|
1885
|
+
#we will almost always be in this case for standard usage, so hit this quickly.
|
1886
|
+
if current_prep_lbl_to_prepend and current_povm_lbl_to_append:
|
1887
|
+
split_circuits.append((current_prep_lbl_to_prepend[0], ckt, current_povm_lbl_to_append[0]))
|
1888
|
+
elif current_prep_lbl_to_prepend and not current_povm_lbl_to_append:
|
1889
|
+
#for some reason this slice [:-1] returns the empty circuit when
|
1890
|
+
#ckt is length 1, so this looks to be alright from an IndexError perspective.
|
1891
|
+
split_circuits.append((current_prep_lbl_to_prepend[0], ckt[:-1], ckt[-1]))
|
1892
|
+
elif not current_prep_lbl_to_prepend and current_povm_lbl_to_append:
|
1893
|
+
#for some reason this slice [1:] returns the empty circuit when
|
1894
|
+
#ckt is length 1, so this looks to be alright from an IndexError perspective.
|
1895
|
+
split_circuits.append((ckt[0], ckt[1:], current_povm_lbl_to_append[0]))
|
1896
|
+
else:
|
1897
|
+
split_circuits.append((ckt[0], ckt[1:-1], ckt[-1]))
|
1898
|
+
comp_circuits.append(ckt.sandwich(current_prep_lbl_to_prepend, current_povm_lbl_to_append))
|
1899
|
+
|
1900
|
+
if return_split:
|
1901
|
+
return comp_circuits, split_circuits
|
1902
|
+
else:
|
1903
|
+
return comp_circuits
|
1904
|
+
|
1905
|
+
def circuit_parameter_dependence(self, circuits, return_param_circ_map = False):
|
1906
|
+
"""
|
1907
|
+
Calculate the which model parameters each of the input circuits depends upon.
|
1908
|
+
Return this result in the the form of a dictionary whose keys are circuits,
|
1909
|
+
and whose values are lists of parameters upon which that circuit depends.
|
1910
|
+
Optionally a reverse mapping from model parameters to the input circuits
|
1911
|
+
which depend on that parameter.
|
1912
|
+
|
1913
|
+
Note: This methods does not work with models using parameter interposers presently.
|
1914
|
+
|
1915
|
+
Parameters
|
1916
|
+
----------
|
1917
|
+
circuits : list of Circuits
|
1918
|
+
List of circuits to determine parameter dependence for.
|
1919
|
+
|
1920
|
+
return_param_circ_map : bool, optional (default False)
|
1921
|
+
A flag indicating whether to return a reverse mapping from parameters
|
1922
|
+
to circuits depending on those parameters.
|
1923
|
+
|
1924
|
+
Returns
|
1925
|
+
-------
|
1926
|
+
circuit_parameter_map : dict
|
1927
|
+
Dictionary with keys given by Circuits and values giving the list of
|
1928
|
+
model parameter indices upon which that circuit depends.
|
1929
|
+
|
1930
|
+
param_to_circuit_map : dict, optional
|
1931
|
+
Dictionary with keys given by model parameter indices, and values
|
1932
|
+
giving the list of input circuits dependent upon that parameter.
|
1933
|
+
"""
|
1934
|
+
|
1935
|
+
if self.param_interposer is not None:
|
1936
|
+
msg = 'Circuit parameter dependence evaluation is not currently implemented for models with parameter interposers.'
|
1937
|
+
raise NotImplementedError(msg)
|
1938
|
+
#start by completing the model:
|
1939
|
+
#Here we want to do this for all of the different primitive prep and
|
1940
|
+
#measurement layers present.
|
1941
|
+
circuit_parameter_map = {}
|
1942
|
+
|
1943
|
+
completed_circuits_by_prep_povm = []
|
1944
|
+
prep_povm_pairs = list(_itertools.product(self.primitive_prep_labels, self.primitive_povm_labels))
|
1945
|
+
for prep_lbl, povm_lbl in prep_povm_pairs:
|
1946
|
+
completed_circuits_by_prep_povm.append(self.complete_circuits(circuits, prep_lbl_to_prepend=prep_lbl, povm_lbl_to_append=povm_lbl))
|
1947
|
+
|
1948
|
+
#we should now have in completed_circuits_by_prep_povm a list of completed circuits
|
1949
|
+
#for each prep, povm pair. Unique layers by circuit will then be the union of these
|
1950
|
+
#accross each of the sublists.
|
1951
|
+
|
1952
|
+
unique_layers_by_circuit = []
|
1953
|
+
for circuits_by_prep_povm in zip(*completed_circuits_by_prep_povm):
|
1954
|
+
#Take the complete set of circuits and get the unique layers which appear accross all of them
|
1955
|
+
#then use this to pre-compute circuit_layer_operators and gpindices.
|
1956
|
+
unique_layers_by_circuit.append(set(sum([ckt.layertup for ckt in circuits_by_prep_povm], ())))
|
1957
|
+
|
1958
|
+
#then aggregate these:
|
1959
|
+
unique_layers = set()
|
1960
|
+
unique_layers = unique_layers.union(*unique_layers_by_circuit)
|
1961
|
+
|
1962
|
+
#Now pre-compute the gpindices for all of these unique layers
|
1963
|
+
unique_layers_gpindices_dict = {layer:_slct.indices(self.circuit_layer_operator(layer).gpindices) for layer in unique_layers}
|
1964
|
+
|
1965
|
+
#loop through the circuit layers and get the circuit layer operators.
|
1966
|
+
#from each of the circuit layer operators we'll get their gpindices.
|
1967
|
+
|
1968
|
+
for circuit, ckt_layer_set in zip(circuits, unique_layers_by_circuit):
|
1969
|
+
seen_gpindices = []
|
1970
|
+
for layer in ckt_layer_set:
|
1971
|
+
gpindices_for_layer = unique_layers_gpindices_dict[layer]
|
1972
|
+
seen_gpindices.extend(gpindices_for_layer)
|
1973
|
+
|
1974
|
+
seen_gpindices = sorted(set(seen_gpindices))
|
1975
|
+
|
1976
|
+
circuit_parameter_map[circuit] = seen_gpindices
|
1977
|
+
|
1978
|
+
#We can also optionally compute the reverse map, from parameters to circuits which touch that parameter.
|
1979
|
+
#it would be more efficient to do this in parallel with the other maps construction, so refactor this later.
|
1980
|
+
if return_param_circ_map:
|
1981
|
+
param_to_circuit_map = [[] for _ in range(self.num_params)]
|
1982
|
+
#keys in circuit_parameter_map should be in the same order as in circuits.
|
1983
|
+
for param_list in circuit_parameter_map.values():
|
1984
|
+
for param_idx in param_list:
|
1985
|
+
param_to_circuit_map[param_idx].append(circuit)
|
1986
|
+
|
1987
|
+
return circuit_parameter_map, param_to_circuit_map
|
1988
|
+
else:
|
1989
|
+
return circuit_parameter_map
|
1260
1990
|
|
1261
1991
|
# ---- Operation container interface ----
|
1262
1992
|
# These functions allow oracle access to whether a label of a given type
|
@@ -2259,3 +2989,6 @@ def _default_param_bounds(num_params):
|
|
2259
2989
|
def _param_bounds_are_nontrivial(param_bounds):
|
2260
2990
|
"""Checks whether a parameter-bounds array holds any actual bounds, or if all are just +-inf """
|
2261
2991
|
return _np.any(param_bounds[:, 0] != -_np.inf) or _np.any(param_bounds[:, 1] != _np.inf)
|
2992
|
+
|
2993
|
+
#stick this on the bottom to resolve a circular import issue:
|
2994
|
+
from pygsti.models.explicitmodel import ExplicitLayerRules as _ExplicitLayerRules
|