pyGSTi 0.9.12__cp38-cp38-win_amd64.whl → 0.9.13__cp38-cp38-win_amd64.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (225) hide show
  1. pyGSTi-0.9.13.dist-info/METADATA +185 -0
  2. {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/RECORD +211 -220
  3. {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/WHEEL +1 -1
  4. pygsti/_version.py +2 -2
  5. pygsti/algorithms/contract.py +1 -1
  6. pygsti/algorithms/core.py +62 -35
  7. pygsti/algorithms/fiducialpairreduction.py +95 -110
  8. pygsti/algorithms/fiducialselection.py +17 -8
  9. pygsti/algorithms/gaugeopt.py +2 -2
  10. pygsti/algorithms/germselection.py +87 -77
  11. pygsti/algorithms/mirroring.py +0 -388
  12. pygsti/algorithms/randomcircuit.py +165 -1333
  13. pygsti/algorithms/rbfit.py +0 -234
  14. pygsti/baseobjs/basis.py +94 -396
  15. pygsti/baseobjs/errorgenbasis.py +0 -132
  16. pygsti/baseobjs/errorgenspace.py +0 -10
  17. pygsti/baseobjs/label.py +52 -168
  18. pygsti/baseobjs/opcalc/fastopcalc.cp38-win_amd64.pyd +0 -0
  19. pygsti/baseobjs/opcalc/fastopcalc.pyx +2 -2
  20. pygsti/baseobjs/polynomial.py +13 -595
  21. pygsti/baseobjs/protectedarray.py +72 -132
  22. pygsti/baseobjs/statespace.py +1 -0
  23. pygsti/circuits/__init__.py +1 -1
  24. pygsti/circuits/circuit.py +753 -504
  25. pygsti/circuits/circuitconstruction.py +0 -4
  26. pygsti/circuits/circuitlist.py +47 -5
  27. pygsti/circuits/circuitparser/__init__.py +8 -8
  28. pygsti/circuits/circuitparser/fastcircuitparser.cp38-win_amd64.pyd +0 -0
  29. pygsti/circuits/circuitstructure.py +3 -3
  30. pygsti/circuits/cloudcircuitconstruction.py +27 -14
  31. pygsti/data/datacomparator.py +4 -9
  32. pygsti/data/dataset.py +51 -46
  33. pygsti/data/hypothesistest.py +0 -7
  34. pygsti/drivers/bootstrap.py +0 -49
  35. pygsti/drivers/longsequence.py +46 -10
  36. pygsti/evotypes/basereps_cython.cp38-win_amd64.pyd +0 -0
  37. pygsti/evotypes/chp/opreps.py +0 -61
  38. pygsti/evotypes/chp/statereps.py +0 -32
  39. pygsti/evotypes/densitymx/effectcreps.cpp +9 -10
  40. pygsti/evotypes/densitymx/effectreps.cp38-win_amd64.pyd +0 -0
  41. pygsti/evotypes/densitymx/effectreps.pyx +1 -1
  42. pygsti/evotypes/densitymx/opreps.cp38-win_amd64.pyd +0 -0
  43. pygsti/evotypes/densitymx/opreps.pyx +2 -2
  44. pygsti/evotypes/densitymx/statereps.cp38-win_amd64.pyd +0 -0
  45. pygsti/evotypes/densitymx/statereps.pyx +1 -1
  46. pygsti/evotypes/densitymx_slow/effectreps.py +7 -23
  47. pygsti/evotypes/densitymx_slow/opreps.py +16 -23
  48. pygsti/evotypes/densitymx_slow/statereps.py +10 -3
  49. pygsti/evotypes/evotype.py +39 -2
  50. pygsti/evotypes/stabilizer/effectreps.cp38-win_amd64.pyd +0 -0
  51. pygsti/evotypes/stabilizer/effectreps.pyx +0 -4
  52. pygsti/evotypes/stabilizer/opreps.cp38-win_amd64.pyd +0 -0
  53. pygsti/evotypes/stabilizer/opreps.pyx +0 -4
  54. pygsti/evotypes/stabilizer/statereps.cp38-win_amd64.pyd +0 -0
  55. pygsti/evotypes/stabilizer/statereps.pyx +1 -5
  56. pygsti/evotypes/stabilizer/termreps.cp38-win_amd64.pyd +0 -0
  57. pygsti/evotypes/stabilizer/termreps.pyx +0 -7
  58. pygsti/evotypes/stabilizer_slow/effectreps.py +0 -22
  59. pygsti/evotypes/stabilizer_slow/opreps.py +0 -4
  60. pygsti/evotypes/stabilizer_slow/statereps.py +0 -4
  61. pygsti/evotypes/statevec/effectreps.cp38-win_amd64.pyd +0 -0
  62. pygsti/evotypes/statevec/effectreps.pyx +1 -1
  63. pygsti/evotypes/statevec/opreps.cp38-win_amd64.pyd +0 -0
  64. pygsti/evotypes/statevec/opreps.pyx +2 -2
  65. pygsti/evotypes/statevec/statereps.cp38-win_amd64.pyd +0 -0
  66. pygsti/evotypes/statevec/statereps.pyx +1 -1
  67. pygsti/evotypes/statevec/termreps.cp38-win_amd64.pyd +0 -0
  68. pygsti/evotypes/statevec/termreps.pyx +0 -7
  69. pygsti/evotypes/statevec_slow/effectreps.py +0 -3
  70. pygsti/evotypes/statevec_slow/opreps.py +0 -5
  71. pygsti/extras/__init__.py +0 -1
  72. pygsti/extras/drift/signal.py +1 -1
  73. pygsti/extras/drift/stabilityanalyzer.py +3 -1
  74. pygsti/extras/interpygate/__init__.py +12 -0
  75. pygsti/extras/interpygate/core.py +0 -36
  76. pygsti/extras/interpygate/process_tomography.py +44 -10
  77. pygsti/extras/rpe/rpeconstruction.py +0 -2
  78. pygsti/forwardsims/__init__.py +1 -0
  79. pygsti/forwardsims/forwardsim.py +50 -93
  80. pygsti/forwardsims/mapforwardsim.py +78 -20
  81. pygsti/forwardsims/mapforwardsim_calc_densitymx.cp38-win_amd64.pyd +0 -0
  82. pygsti/forwardsims/mapforwardsim_calc_densitymx.pyx +65 -66
  83. pygsti/forwardsims/mapforwardsim_calc_generic.py +91 -13
  84. pygsti/forwardsims/matrixforwardsim.py +72 -17
  85. pygsti/forwardsims/termforwardsim.py +9 -111
  86. pygsti/forwardsims/termforwardsim_calc_stabilizer.cp38-win_amd64.pyd +0 -0
  87. pygsti/forwardsims/termforwardsim_calc_statevec.cp38-win_amd64.pyd +0 -0
  88. pygsti/forwardsims/termforwardsim_calc_statevec.pyx +0 -651
  89. pygsti/forwardsims/torchfwdsim.py +265 -0
  90. pygsti/forwardsims/weakforwardsim.py +2 -2
  91. pygsti/io/__init__.py +1 -2
  92. pygsti/io/mongodb.py +0 -2
  93. pygsti/io/stdinput.py +6 -22
  94. pygsti/layouts/copalayout.py +10 -12
  95. pygsti/layouts/distlayout.py +0 -40
  96. pygsti/layouts/maplayout.py +103 -25
  97. pygsti/layouts/matrixlayout.py +99 -60
  98. pygsti/layouts/prefixtable.py +1534 -52
  99. pygsti/layouts/termlayout.py +1 -1
  100. pygsti/modelmembers/instruments/instrument.py +3 -3
  101. pygsti/modelmembers/instruments/tpinstrument.py +2 -2
  102. pygsti/modelmembers/modelmember.py +0 -17
  103. pygsti/modelmembers/operations/__init__.py +3 -4
  104. pygsti/modelmembers/operations/affineshiftop.py +206 -0
  105. pygsti/modelmembers/operations/composederrorgen.py +1 -1
  106. pygsti/modelmembers/operations/composedop.py +1 -24
  107. pygsti/modelmembers/operations/denseop.py +5 -5
  108. pygsti/modelmembers/operations/eigpdenseop.py +2 -2
  109. pygsti/modelmembers/operations/embeddederrorgen.py +1 -1
  110. pygsti/modelmembers/operations/embeddedop.py +0 -1
  111. pygsti/modelmembers/operations/experrorgenop.py +5 -2
  112. pygsti/modelmembers/operations/fullarbitraryop.py +1 -0
  113. pygsti/modelmembers/operations/fullcptpop.py +2 -2
  114. pygsti/modelmembers/operations/fulltpop.py +28 -6
  115. pygsti/modelmembers/operations/fullunitaryop.py +5 -4
  116. pygsti/modelmembers/operations/lindbladcoefficients.py +93 -78
  117. pygsti/modelmembers/operations/lindbladerrorgen.py +268 -441
  118. pygsti/modelmembers/operations/linearop.py +7 -27
  119. pygsti/modelmembers/operations/opfactory.py +1 -1
  120. pygsti/modelmembers/operations/repeatedop.py +1 -24
  121. pygsti/modelmembers/operations/staticstdop.py +1 -1
  122. pygsti/modelmembers/povms/__init__.py +3 -3
  123. pygsti/modelmembers/povms/basepovm.py +7 -36
  124. pygsti/modelmembers/povms/complementeffect.py +4 -9
  125. pygsti/modelmembers/povms/composedeffect.py +0 -320
  126. pygsti/modelmembers/povms/computationaleffect.py +1 -1
  127. pygsti/modelmembers/povms/computationalpovm.py +3 -1
  128. pygsti/modelmembers/povms/effect.py +3 -5
  129. pygsti/modelmembers/povms/marginalizedpovm.py +3 -81
  130. pygsti/modelmembers/povms/tppovm.py +74 -2
  131. pygsti/modelmembers/states/__init__.py +2 -5
  132. pygsti/modelmembers/states/composedstate.py +0 -317
  133. pygsti/modelmembers/states/computationalstate.py +3 -3
  134. pygsti/modelmembers/states/cptpstate.py +4 -4
  135. pygsti/modelmembers/states/densestate.py +10 -8
  136. pygsti/modelmembers/states/fullpurestate.py +0 -24
  137. pygsti/modelmembers/states/purestate.py +1 -1
  138. pygsti/modelmembers/states/state.py +5 -6
  139. pygsti/modelmembers/states/tpstate.py +28 -10
  140. pygsti/modelmembers/term.py +3 -6
  141. pygsti/modelmembers/torchable.py +50 -0
  142. pygsti/modelpacks/_modelpack.py +1 -1
  143. pygsti/modelpacks/smq1Q_ZN.py +3 -1
  144. pygsti/modelpacks/smq2Q_XXYYII.py +2 -1
  145. pygsti/modelpacks/smq2Q_XY.py +3 -3
  146. pygsti/modelpacks/smq2Q_XYI.py +2 -2
  147. pygsti/modelpacks/smq2Q_XYICNOT.py +3 -3
  148. pygsti/modelpacks/smq2Q_XYICPHASE.py +3 -3
  149. pygsti/modelpacks/smq2Q_XYXX.py +1 -1
  150. pygsti/modelpacks/smq2Q_XYZICNOT.py +3 -3
  151. pygsti/modelpacks/smq2Q_XYZZ.py +1 -1
  152. pygsti/modelpacks/stdtarget.py +0 -121
  153. pygsti/models/cloudnoisemodel.py +1 -2
  154. pygsti/models/explicitcalc.py +3 -3
  155. pygsti/models/explicitmodel.py +3 -13
  156. pygsti/models/fogistore.py +5 -3
  157. pygsti/models/localnoisemodel.py +1 -2
  158. pygsti/models/memberdict.py +0 -12
  159. pygsti/models/model.py +801 -68
  160. pygsti/models/modelconstruction.py +4 -4
  161. pygsti/models/modelnoise.py +2 -2
  162. pygsti/models/modelparaminterposer.py +1 -1
  163. pygsti/models/oplessmodel.py +1 -1
  164. pygsti/models/qutrit.py +15 -14
  165. pygsti/objectivefns/objectivefns.py +75 -140
  166. pygsti/objectivefns/wildcardbudget.py +2 -7
  167. pygsti/optimize/__init__.py +1 -0
  168. pygsti/optimize/arraysinterface.py +28 -0
  169. pygsti/optimize/customcg.py +0 -12
  170. pygsti/optimize/customlm.py +129 -323
  171. pygsti/optimize/customsolve.py +2 -2
  172. pygsti/optimize/optimize.py +0 -84
  173. pygsti/optimize/simplerlm.py +841 -0
  174. pygsti/optimize/wildcardopt.py +19 -598
  175. pygsti/protocols/confidenceregionfactory.py +28 -14
  176. pygsti/protocols/estimate.py +31 -14
  177. pygsti/protocols/gst.py +238 -142
  178. pygsti/protocols/modeltest.py +19 -12
  179. pygsti/protocols/protocol.py +9 -37
  180. pygsti/protocols/rb.py +450 -79
  181. pygsti/protocols/treenode.py +8 -2
  182. pygsti/protocols/vb.py +108 -206
  183. pygsti/protocols/vbdataframe.py +1 -1
  184. pygsti/report/factory.py +0 -15
  185. pygsti/report/fogidiagram.py +1 -17
  186. pygsti/report/modelfunction.py +12 -3
  187. pygsti/report/mpl_colormaps.py +1 -1
  188. pygsti/report/plothelpers.py +11 -3
  189. pygsti/report/report.py +16 -0
  190. pygsti/report/reportables.py +41 -37
  191. pygsti/report/templates/offline/pygsti_dashboard.css +6 -0
  192. pygsti/report/templates/offline/pygsti_dashboard.js +12 -0
  193. pygsti/report/workspace.py +2 -14
  194. pygsti/report/workspaceplots.py +328 -505
  195. pygsti/tools/basistools.py +9 -36
  196. pygsti/tools/edesigntools.py +124 -96
  197. pygsti/tools/fastcalc.cp38-win_amd64.pyd +0 -0
  198. pygsti/tools/fastcalc.pyx +35 -81
  199. pygsti/tools/internalgates.py +151 -15
  200. pygsti/tools/jamiolkowski.py +5 -5
  201. pygsti/tools/lindbladtools.py +19 -11
  202. pygsti/tools/listtools.py +0 -114
  203. pygsti/tools/matrixmod2.py +1 -1
  204. pygsti/tools/matrixtools.py +173 -339
  205. pygsti/tools/nameddict.py +1 -1
  206. pygsti/tools/optools.py +154 -88
  207. pygsti/tools/pdftools.py +0 -25
  208. pygsti/tools/rbtheory.py +3 -320
  209. pygsti/tools/slicetools.py +64 -12
  210. pyGSTi-0.9.12.dist-info/METADATA +0 -157
  211. pygsti/algorithms/directx.py +0 -711
  212. pygsti/evotypes/qibo/__init__.py +0 -33
  213. pygsti/evotypes/qibo/effectreps.py +0 -78
  214. pygsti/evotypes/qibo/opreps.py +0 -376
  215. pygsti/evotypes/qibo/povmreps.py +0 -98
  216. pygsti/evotypes/qibo/statereps.py +0 -174
  217. pygsti/extras/rb/__init__.py +0 -13
  218. pygsti/extras/rb/benchmarker.py +0 -957
  219. pygsti/extras/rb/dataset.py +0 -378
  220. pygsti/extras/rb/io.py +0 -814
  221. pygsti/extras/rb/simulate.py +0 -1020
  222. pygsti/io/legacyio.py +0 -385
  223. pygsti/modelmembers/povms/denseeffect.py +0 -142
  224. {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/LICENSE +0 -0
  225. {pyGSTi-0.9.12.dist-info → pyGSTi-0.9.13.dist-info}/top_level.txt +0 -0
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 self._param_bounds is None:
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 = simulator = _fwdsim.ForwardSimulator.cast(simulator, nqubits)
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
- w = obj.to_vector()
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 = self._ops_paramlbls_to_model_paramlbls(wl)
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 = circuit.expand_instruments_and_separate_povm(self) # dict w/keys=sep-povm-circuits, vals=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 : str or None
1466
+ prep_label : Label or None
1185
1467
  ops_only_circuit : Circuit
1186
- povm_label : str or None
1187
- """
1188
- if split_prep:
1189
- if len(circuit) > 0 and self._is_primitive_prep_layer_lbl(circuit[0]):
1190
- prep_lbl = circuit[0]
1191
- circuit = circuit[1:]
1192
- elif self._default_primitive_prep_layer_lbl() is not None:
1193
- prep_lbl = self._default_primitive_prep_layer_lbl()
1194
- else:
1195
- if 'prep' in erroron and self._has_primitive_preps():
1196
- raise ValueError("Cannot resolve state prep in %s" % circuit)
1197
- else: prep_lbl = None
1198
- else:
1199
- prep_lbl = None
1200
-
1201
- if split_povm:
1202
- if len(circuit) > 0 and self._is_primitive_povm_layer_lbl(circuit[-1]):
1203
- povm_lbl = circuit[-1]
1204
- circuit = circuit[:-1]
1205
- elif self._default_primitive_povm_layer_lbl(circuit.line_labels) is not None:
1206
- povm_lbl = self._default_primitive_povm_layer_lbl(circuit.line_labels)
1207
- else:
1208
- if 'povm' in erroron and self._has_primitive_povms():
1209
- raise ValueError("Cannot resolve POVM in %s" % str(circuit))
1210
- else: povm_lbl = None
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
- povm_lbl = None
1601
+ split_circuits = [(None, ckt, None) for ckt in circuits]
1213
1602
 
1214
- return prep_lbl, circuit, povm_lbl
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 len(circuit) == 0 or not self._is_primitive_prep_layer_lbl(circuit[0]):
1849
+ if prep_lbl_to_prepend is None:
1237
1850
  prep_lbl_to_prepend = self._default_primitive_prep_layer_lbl()
1238
- if prep_lbl_to_prepend is None:
1239
- #raise ValueError(f"Missing state prep in {circuit.str} and there's no default!")
1240
- raise ValueError("Missing state prep in %s and there's no default!" % circuit.str)
1241
-
1242
- if len(circuit) == 0 or not self._is_primitive_povm_layer_lbl(circuit[-1]):
1243
- sslbls = circuit.line_labels if circuit.line_labels != ("*",) else None
1244
- povm_lbl_to_append = self._default_primitive_povm_layer_lbl(sslbls)
1245
-
1246
- if povm_lbl_to_append is None:
1247
- #raise ValueError(f"Missing POVM in {circuit.str} and there's no default!")
1248
- raise ValueError("Missing POVM in %s and there's no default!" % circuit.str)
1249
-
1250
- if prep_lbl_to_prepend or povm_lbl_to_append:
1251
- #SLOW way:
1252
- #circuit = circuit.copy(editable=True)
1253
- #if prep_lbl_to_prepend: circuit.insert_layer_inplace(prep_lbl_to_prepend, 0)
1254
- #if povm_lbl_to_append: circuit.insert_layer_inplace(povm_lbl_to_append, len(circuit))
1255
- #circuit.done_editing()
1256
- if prep_lbl_to_prepend: circuit = (prep_lbl_to_prepend,) + circuit
1257
- if povm_lbl_to_append: circuit = circuit + (povm_lbl_to_append,)
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
- return circuit
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