pyGSTi 0.9.12.1__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 (221) hide show
  1. pyGSTi-0.9.13.dist-info/METADATA +185 -0
  2. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/RECORD +207 -217
  3. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/WHEEL +1 -1
  4. pygsti/_version.py +2 -2
  5. pygsti/algorithms/contract.py +1 -1
  6. pygsti/algorithms/core.py +42 -28
  7. pygsti/algorithms/fiducialselection.py +17 -8
  8. pygsti/algorithms/gaugeopt.py +2 -2
  9. pygsti/algorithms/germselection.py +87 -77
  10. pygsti/algorithms/mirroring.py +0 -388
  11. pygsti/algorithms/randomcircuit.py +165 -1333
  12. pygsti/algorithms/rbfit.py +0 -234
  13. pygsti/baseobjs/basis.py +94 -396
  14. pygsti/baseobjs/errorgenbasis.py +0 -132
  15. pygsti/baseobjs/errorgenspace.py +0 -10
  16. pygsti/baseobjs/label.py +52 -168
  17. pygsti/baseobjs/opcalc/fastopcalc.cp38-win_amd64.pyd +0 -0
  18. pygsti/baseobjs/opcalc/fastopcalc.pyx +2 -2
  19. pygsti/baseobjs/polynomial.py +13 -595
  20. pygsti/baseobjs/statespace.py +1 -0
  21. pygsti/circuits/__init__.py +1 -1
  22. pygsti/circuits/circuit.py +682 -505
  23. pygsti/circuits/circuitconstruction.py +0 -4
  24. pygsti/circuits/circuitlist.py +47 -5
  25. pygsti/circuits/circuitparser/__init__.py +8 -8
  26. pygsti/circuits/circuitparser/fastcircuitparser.cp38-win_amd64.pyd +0 -0
  27. pygsti/circuits/circuitstructure.py +3 -3
  28. pygsti/circuits/cloudcircuitconstruction.py +1 -1
  29. pygsti/data/datacomparator.py +2 -7
  30. pygsti/data/dataset.py +46 -44
  31. pygsti/data/hypothesistest.py +0 -7
  32. pygsti/drivers/bootstrap.py +0 -49
  33. pygsti/drivers/longsequence.py +2 -1
  34. pygsti/evotypes/basereps_cython.cp38-win_amd64.pyd +0 -0
  35. pygsti/evotypes/chp/opreps.py +0 -61
  36. pygsti/evotypes/chp/statereps.py +0 -32
  37. pygsti/evotypes/densitymx/effectcreps.cpp +9 -10
  38. pygsti/evotypes/densitymx/effectreps.cp38-win_amd64.pyd +0 -0
  39. pygsti/evotypes/densitymx/effectreps.pyx +1 -1
  40. pygsti/evotypes/densitymx/opreps.cp38-win_amd64.pyd +0 -0
  41. pygsti/evotypes/densitymx/opreps.pyx +2 -2
  42. pygsti/evotypes/densitymx/statereps.cp38-win_amd64.pyd +0 -0
  43. pygsti/evotypes/densitymx/statereps.pyx +1 -1
  44. pygsti/evotypes/densitymx_slow/effectreps.py +7 -23
  45. pygsti/evotypes/densitymx_slow/opreps.py +16 -23
  46. pygsti/evotypes/densitymx_slow/statereps.py +10 -3
  47. pygsti/evotypes/evotype.py +39 -2
  48. pygsti/evotypes/stabilizer/effectreps.cp38-win_amd64.pyd +0 -0
  49. pygsti/evotypes/stabilizer/effectreps.pyx +0 -4
  50. pygsti/evotypes/stabilizer/opreps.cp38-win_amd64.pyd +0 -0
  51. pygsti/evotypes/stabilizer/opreps.pyx +0 -4
  52. pygsti/evotypes/stabilizer/statereps.cp38-win_amd64.pyd +0 -0
  53. pygsti/evotypes/stabilizer/statereps.pyx +1 -5
  54. pygsti/evotypes/stabilizer/termreps.cp38-win_amd64.pyd +0 -0
  55. pygsti/evotypes/stabilizer/termreps.pyx +0 -7
  56. pygsti/evotypes/stabilizer_slow/effectreps.py +0 -22
  57. pygsti/evotypes/stabilizer_slow/opreps.py +0 -4
  58. pygsti/evotypes/stabilizer_slow/statereps.py +0 -4
  59. pygsti/evotypes/statevec/effectreps.cp38-win_amd64.pyd +0 -0
  60. pygsti/evotypes/statevec/effectreps.pyx +1 -1
  61. pygsti/evotypes/statevec/opreps.cp38-win_amd64.pyd +0 -0
  62. pygsti/evotypes/statevec/opreps.pyx +2 -2
  63. pygsti/evotypes/statevec/statereps.cp38-win_amd64.pyd +0 -0
  64. pygsti/evotypes/statevec/statereps.pyx +1 -1
  65. pygsti/evotypes/statevec/termreps.cp38-win_amd64.pyd +0 -0
  66. pygsti/evotypes/statevec/termreps.pyx +0 -7
  67. pygsti/evotypes/statevec_slow/effectreps.py +0 -3
  68. pygsti/evotypes/statevec_slow/opreps.py +0 -5
  69. pygsti/extras/__init__.py +0 -1
  70. pygsti/extras/drift/stabilityanalyzer.py +3 -1
  71. pygsti/extras/interpygate/__init__.py +12 -0
  72. pygsti/extras/interpygate/core.py +0 -36
  73. pygsti/extras/interpygate/process_tomography.py +44 -10
  74. pygsti/extras/rpe/rpeconstruction.py +0 -2
  75. pygsti/forwardsims/__init__.py +1 -0
  76. pygsti/forwardsims/forwardsim.py +14 -55
  77. pygsti/forwardsims/mapforwardsim.py +69 -18
  78. pygsti/forwardsims/mapforwardsim_calc_densitymx.cp38-win_amd64.pyd +0 -0
  79. pygsti/forwardsims/mapforwardsim_calc_densitymx.pyx +65 -66
  80. pygsti/forwardsims/mapforwardsim_calc_generic.py +91 -13
  81. pygsti/forwardsims/matrixforwardsim.py +63 -15
  82. pygsti/forwardsims/termforwardsim.py +8 -110
  83. pygsti/forwardsims/termforwardsim_calc_stabilizer.cp38-win_amd64.pyd +0 -0
  84. pygsti/forwardsims/termforwardsim_calc_statevec.cp38-win_amd64.pyd +0 -0
  85. pygsti/forwardsims/termforwardsim_calc_statevec.pyx +0 -651
  86. pygsti/forwardsims/torchfwdsim.py +265 -0
  87. pygsti/forwardsims/weakforwardsim.py +2 -2
  88. pygsti/io/__init__.py +1 -2
  89. pygsti/io/mongodb.py +0 -2
  90. pygsti/io/stdinput.py +6 -22
  91. pygsti/layouts/copalayout.py +10 -12
  92. pygsti/layouts/distlayout.py +0 -40
  93. pygsti/layouts/maplayout.py +103 -25
  94. pygsti/layouts/matrixlayout.py +99 -60
  95. pygsti/layouts/prefixtable.py +1534 -52
  96. pygsti/layouts/termlayout.py +1 -1
  97. pygsti/modelmembers/instruments/instrument.py +3 -3
  98. pygsti/modelmembers/instruments/tpinstrument.py +2 -2
  99. pygsti/modelmembers/modelmember.py +0 -17
  100. pygsti/modelmembers/operations/__init__.py +2 -4
  101. pygsti/modelmembers/operations/affineshiftop.py +1 -0
  102. pygsti/modelmembers/operations/composederrorgen.py +1 -1
  103. pygsti/modelmembers/operations/composedop.py +1 -24
  104. pygsti/modelmembers/operations/denseop.py +5 -5
  105. pygsti/modelmembers/operations/eigpdenseop.py +2 -2
  106. pygsti/modelmembers/operations/embeddederrorgen.py +1 -1
  107. pygsti/modelmembers/operations/embeddedop.py +0 -1
  108. pygsti/modelmembers/operations/experrorgenop.py +2 -2
  109. pygsti/modelmembers/operations/fullarbitraryop.py +1 -0
  110. pygsti/modelmembers/operations/fullcptpop.py +2 -2
  111. pygsti/modelmembers/operations/fulltpop.py +28 -6
  112. pygsti/modelmembers/operations/fullunitaryop.py +5 -4
  113. pygsti/modelmembers/operations/lindbladcoefficients.py +93 -78
  114. pygsti/modelmembers/operations/lindbladerrorgen.py +268 -441
  115. pygsti/modelmembers/operations/linearop.py +7 -27
  116. pygsti/modelmembers/operations/opfactory.py +1 -1
  117. pygsti/modelmembers/operations/repeatedop.py +1 -24
  118. pygsti/modelmembers/operations/staticstdop.py +1 -1
  119. pygsti/modelmembers/povms/__init__.py +3 -3
  120. pygsti/modelmembers/povms/basepovm.py +7 -36
  121. pygsti/modelmembers/povms/complementeffect.py +4 -9
  122. pygsti/modelmembers/povms/composedeffect.py +0 -320
  123. pygsti/modelmembers/povms/computationaleffect.py +1 -1
  124. pygsti/modelmembers/povms/computationalpovm.py +3 -1
  125. pygsti/modelmembers/povms/effect.py +3 -5
  126. pygsti/modelmembers/povms/marginalizedpovm.py +0 -79
  127. pygsti/modelmembers/povms/tppovm.py +74 -2
  128. pygsti/modelmembers/states/__init__.py +2 -5
  129. pygsti/modelmembers/states/composedstate.py +0 -317
  130. pygsti/modelmembers/states/computationalstate.py +3 -3
  131. pygsti/modelmembers/states/cptpstate.py +4 -4
  132. pygsti/modelmembers/states/densestate.py +6 -4
  133. pygsti/modelmembers/states/fullpurestate.py +0 -24
  134. pygsti/modelmembers/states/purestate.py +1 -1
  135. pygsti/modelmembers/states/state.py +5 -6
  136. pygsti/modelmembers/states/tpstate.py +28 -10
  137. pygsti/modelmembers/term.py +3 -6
  138. pygsti/modelmembers/torchable.py +50 -0
  139. pygsti/modelpacks/_modelpack.py +1 -1
  140. pygsti/modelpacks/smq1Q_ZN.py +3 -1
  141. pygsti/modelpacks/smq2Q_XXYYII.py +2 -1
  142. pygsti/modelpacks/smq2Q_XY.py +3 -3
  143. pygsti/modelpacks/smq2Q_XYI.py +2 -2
  144. pygsti/modelpacks/smq2Q_XYICNOT.py +3 -3
  145. pygsti/modelpacks/smq2Q_XYICPHASE.py +3 -3
  146. pygsti/modelpacks/smq2Q_XYXX.py +1 -1
  147. pygsti/modelpacks/smq2Q_XYZICNOT.py +3 -3
  148. pygsti/modelpacks/smq2Q_XYZZ.py +1 -1
  149. pygsti/modelpacks/stdtarget.py +0 -121
  150. pygsti/models/cloudnoisemodel.py +1 -2
  151. pygsti/models/explicitcalc.py +3 -3
  152. pygsti/models/explicitmodel.py +3 -13
  153. pygsti/models/fogistore.py +5 -3
  154. pygsti/models/localnoisemodel.py +1 -2
  155. pygsti/models/memberdict.py +0 -12
  156. pygsti/models/model.py +800 -65
  157. pygsti/models/modelconstruction.py +4 -4
  158. pygsti/models/modelnoise.py +2 -2
  159. pygsti/models/modelparaminterposer.py +1 -1
  160. pygsti/models/oplessmodel.py +1 -1
  161. pygsti/models/qutrit.py +15 -14
  162. pygsti/objectivefns/objectivefns.py +73 -138
  163. pygsti/objectivefns/wildcardbudget.py +2 -7
  164. pygsti/optimize/__init__.py +1 -0
  165. pygsti/optimize/arraysinterface.py +28 -0
  166. pygsti/optimize/customcg.py +0 -12
  167. pygsti/optimize/customlm.py +129 -323
  168. pygsti/optimize/customsolve.py +2 -2
  169. pygsti/optimize/optimize.py +0 -84
  170. pygsti/optimize/simplerlm.py +841 -0
  171. pygsti/optimize/wildcardopt.py +19 -598
  172. pygsti/protocols/confidenceregionfactory.py +28 -14
  173. pygsti/protocols/estimate.py +31 -14
  174. pygsti/protocols/gst.py +142 -68
  175. pygsti/protocols/modeltest.py +6 -10
  176. pygsti/protocols/protocol.py +9 -37
  177. pygsti/protocols/rb.py +450 -79
  178. pygsti/protocols/treenode.py +8 -2
  179. pygsti/protocols/vb.py +108 -206
  180. pygsti/protocols/vbdataframe.py +1 -1
  181. pygsti/report/factory.py +0 -15
  182. pygsti/report/fogidiagram.py +1 -17
  183. pygsti/report/modelfunction.py +12 -3
  184. pygsti/report/mpl_colormaps.py +1 -1
  185. pygsti/report/plothelpers.py +8 -2
  186. pygsti/report/reportables.py +41 -37
  187. pygsti/report/templates/offline/pygsti_dashboard.css +6 -0
  188. pygsti/report/templates/offline/pygsti_dashboard.js +12 -0
  189. pygsti/report/workspace.py +2 -14
  190. pygsti/report/workspaceplots.py +326 -504
  191. pygsti/tools/basistools.py +9 -36
  192. pygsti/tools/edesigntools.py +124 -96
  193. pygsti/tools/fastcalc.cp38-win_amd64.pyd +0 -0
  194. pygsti/tools/fastcalc.pyx +35 -81
  195. pygsti/tools/internalgates.py +151 -15
  196. pygsti/tools/jamiolkowski.py +5 -5
  197. pygsti/tools/lindbladtools.py +19 -11
  198. pygsti/tools/listtools.py +0 -114
  199. pygsti/tools/matrixmod2.py +1 -1
  200. pygsti/tools/matrixtools.py +173 -339
  201. pygsti/tools/nameddict.py +1 -1
  202. pygsti/tools/optools.py +154 -88
  203. pygsti/tools/pdftools.py +0 -25
  204. pygsti/tools/rbtheory.py +3 -320
  205. pygsti/tools/slicetools.py +64 -12
  206. pyGSTi-0.9.12.1.dist-info/METADATA +0 -155
  207. pygsti/algorithms/directx.py +0 -711
  208. pygsti/evotypes/qibo/__init__.py +0 -33
  209. pygsti/evotypes/qibo/effectreps.py +0 -78
  210. pygsti/evotypes/qibo/opreps.py +0 -376
  211. pygsti/evotypes/qibo/povmreps.py +0 -98
  212. pygsti/evotypes/qibo/statereps.py +0 -174
  213. pygsti/extras/rb/__init__.py +0 -13
  214. pygsti/extras/rb/benchmarker.py +0 -957
  215. pygsti/extras/rb/dataset.py +0 -378
  216. pygsti/extras/rb/io.py +0 -814
  217. pygsti/extras/rb/simulate.py +0 -1020
  218. pygsti/io/legacyio.py +0 -385
  219. pygsti/modelmembers/povms/denseeffect.py +0 -142
  220. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/LICENSE +0 -0
  221. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/top_level.txt +0 -0
@@ -30,36 +30,38 @@ except ImportError:
30
30
  #EXPM_DEFAULT_TOL = 1e-7
31
31
  EXPM_DEFAULT_TOL = 2**-53 # Scipy default
32
32
 
33
+ BLAS_FUNCS = {
34
+ 'herk': {
35
+ 's' : _spl.blas.ssyrk,
36
+ 'd' : _spl.blas.dsyrk,
37
+ 'c' : _spl.blas.cherk,
38
+ 'z': _spl.blas.zherk
39
+ }
40
+ }
33
41
 
34
- def trace(m): # memory leak in numpy causes repeated trace calls to eat up all memory --TODO: Cython this
42
+ def gram_matrix(m, adjoint=False):
35
43
  """
36
- The trace of a matrix, sum_i m[i,i].
44
+ If adjoint=False, then return m.T.conj() @ m, computed in a more efficient way.
37
45
 
38
- A memory leak in some version of numpy can cause repeated calls to numpy's
39
- trace function to eat up all available system memory, and this function
40
- does not have this problem.
41
-
42
- Parameters
43
- ----------
44
- m : numpy array
45
- the matrix (any object that can be double-indexed)
46
+ If adjoint=True, return m @ m.T.conj(), likewise computed in a more efficient way.
47
+ """
48
+ assert isinstance(m, _np.ndarray)
49
+ prefix_char, _, _ = _spl.blas.find_best_blas_type(dtype=m.dtype)
50
+ herk = BLAS_FUNCS["herk"][prefix_char]
46
51
 
47
- Returns
48
- -------
49
- element type of m
50
- The trace of m.
51
- """
52
- return sum([m[i, i] for i in range(m.shape[0])])
53
- # with warnings.catch_warnings():
54
- # warnings.filterwarnings('error')
55
- # try:
56
- # ret =
57
- # except Warning:
58
- # print "BAD trace from:\n"
59
- # for i in range(M.shape[0]):
60
- # print M[i,i]
61
- # raise ValueError("STOP")
62
- # return ret
52
+ if adjoint:
53
+ trans = 0
54
+ elif _np.iscomplexobj(m):
55
+ trans = 2
56
+ else:
57
+ trans = 1
58
+ out = herk(1.0, m, trans=trans)
59
+ i_lower = _np.tril_indices(out.shape[0], -1)
60
+ upper_values = out.T[i_lower]
61
+ out[i_lower] = upper_values.real
62
+ if trans > 0:
63
+ out[i_lower] += upper_values.imag
64
+ return out
63
65
 
64
66
 
65
67
  def is_hermitian(mx, tol=1e-9):
@@ -80,14 +82,13 @@ def is_hermitian(mx, tol=1e-9):
80
82
  True if mx is hermitian, otherwise False.
81
83
  """
82
84
  (m, n) = mx.shape
83
- for i in range(m):
84
- if abs(mx[i, i].imag) > tol: return False
85
- for j in range(i + 1, n):
86
- if abs(mx[i, j] - mx[j, i].conjugate()) > tol: return False
87
- return True
85
+ if m != n:
86
+ return False
87
+ else:
88
+ return _np.all(_np.abs(mx - mx.T.conj()) <= tol)
88
89
 
89
90
 
90
- def is_pos_def(mx, tol=1e-9):
91
+ def is_pos_def(mx, tol=1e-9, attempt_cholesky=False):
91
92
  """
92
93
  Test whether mx is a positive-definite matrix.
93
94
 
@@ -104,7 +105,15 @@ def is_pos_def(mx, tol=1e-9):
104
105
  bool
105
106
  True if mx is positive-semidefinite, otherwise False.
106
107
  """
107
- evals = _np.linalg.eigvals(mx)
108
+ if not is_hermitian(mx, tol):
109
+ return False
110
+ if attempt_cholesky:
111
+ try:
112
+ _ = _spl.cholesky(mx)
113
+ return True # Cholesky succeeded
114
+ except _spl.LinAlgError:
115
+ pass # we fall back on eigenvalue decomposition
116
+ evals = _np.linalg.eigvalsh(mx)
108
117
  return all([ev > -tol for ev in evals])
109
118
 
110
119
 
@@ -125,47 +134,8 @@ def is_valid_density_mx(mx, tol=1e-9):
125
134
  bool
126
135
  True if mx is a valid density matrix, otherwise False.
127
136
  """
128
- return is_hermitian(mx, tol) and is_pos_def(mx, tol) and abs(trace(mx) - 1.0) < tol
129
-
130
-
131
- def frobeniusnorm(ar):
132
- """
133
- Compute the frobenius norm of an array (or matrix),
134
-
135
- sqrt( sum( each_element_of_a^2 ) )
136
-
137
- Parameters
138
- ----------
139
- ar : numpy array
140
- What to compute the frobenius norm of. Note that ar can be any shape
141
- or number of dimenions.
142
-
143
- Returns
144
- -------
145
- float or complex
146
- depending on the element type of ar.
147
- """
148
- return _np.sqrt(_np.sum(ar**2))
149
-
150
-
151
- def frobeniusnorm_squared(ar):
152
- """
153
- Compute the squared frobenius norm of an array (or matrix),
154
-
155
- sum( each_element_of_a^2 ) )
156
-
157
- Parameters
158
- ----------
159
- ar : numpy array
160
- What to compute the squared frobenius norm of. Note that ar can be any
161
- shape or number of dimenions.
162
-
163
- Returns
164
- -------
165
- float or complex
166
- depending on the element type of ar.
167
- """
168
- return _np.sum(ar**2)
137
+ # is_pos_def includes a check that the matrix is Hermitian.
138
+ return abs(_np.trace(mx) - 1.0) < tol and is_pos_def(mx, tol)
169
139
 
170
140
 
171
141
  def nullspace(m, tol=1e-7):
@@ -186,7 +156,7 @@ def nullspace(m, tol=1e-7):
186
156
  """
187
157
  _, s, vh = _np.linalg.svd(m)
188
158
  rank = (s > tol).sum()
189
- return vh[rank:].T.conjugate().copy()
159
+ return vh[rank:].T.conjugate()
190
160
 
191
161
 
192
162
  def nullspace_qr(m, tol=1e-7):
@@ -227,10 +197,7 @@ def nice_nullspace(m, tol=1e-7, orthogonalize=False):
227
197
  Computes the nullspace of a matrix, and tries to return a "nice" basis for it.
228
198
 
229
199
  Columns of the returned value (a basis for the nullspace) each have a maximum
230
- absolute value of 1.0 and are chosen so as to align with the the original
231
- matrix's basis as much as possible (the basis is found by projecting each
232
- original basis vector onto an arbitrariliy-found nullspace and keeping only
233
- a set of linearly independent projections).
200
+ absolute value of 1.0.
234
201
 
235
202
  Parameters
236
203
  ----------
@@ -248,21 +215,22 @@ def nice_nullspace(m, tol=1e-7, orthogonalize=False):
248
215
  An matrix of shape (M,K) whose columns contain nullspace basis vectors.
249
216
  """
250
217
  nullsp = nullspace(m, tol)
251
- nullsp_projector = _np.dot(nullsp, nullsp.conj().T)
252
- keepers = []; current_rank = 0
253
- for i in range(nullsp_projector.shape[1]): # same as mx.shape[1]
254
- rank = _np.linalg.matrix_rank(nullsp_projector[:, 0:i + 1], tol=tol)
255
- if rank > current_rank:
256
- keepers.append(i)
257
- current_rank = rank
258
- ret = _np.take(nullsp_projector, keepers, axis=1)
259
-
260
- if orthogonalize: # and not columns_are_orthogonal(ret):
261
- ret, _ = _np.linalg.qr(ret) # Gram-Schmidt orthogonalization
262
-
218
+ dim_ker = nullsp.shape[1]
219
+ if dim_ker == 0:
220
+ return nullsp # empty 0-by-N array
221
+ _, _, p = _spl.qr(nullsp.T.conj(), mode='raw', pivoting=True)
222
+ ret = nullsp @ (nullsp.T[:, p[:dim_ker]]).conj()
223
+ # ^ That's equivalent to, but faster than:
224
+ # nullsp_projector = nullsp @ nullsp.T.conj()
225
+ # _, _, p = _spl.qr(nullsp_projector mode='raw', pivoting=True)
226
+ # ret = nullsp_projector[:, p[:dim_ker]]
227
+
228
+ if orthogonalize:
229
+ ret, _ = _spl.qr(ret, mode='economic')
263
230
  for j in range(ret.shape[1]): # normalize columns so largest element is +1.0
264
231
  imax = _np.argmax(_np.abs(ret[:, j]))
265
- if abs(ret[imax, j]) > 1e-6: ret[:, j] /= ret[imax, j]
232
+ if abs(ret[imax, j]) > 1e-6:
233
+ ret[:, j] /= ret[imax, j]
266
234
 
267
235
  return ret
268
236
 
@@ -319,14 +287,16 @@ def column_norms(m, ord=None):
319
287
  numpy.ndarray
320
288
  A 1-dimensional array of the column norms (length is number of columns of `m`).
321
289
  """
322
- ord_list = [ord] * m.shape[1] if (ord is None or isinstance(ord, int)) else ord
323
- assert(len(ord_list) == m.shape[1])
324
-
325
290
  if _sps.issparse(m):
326
- #this could be done more efficiently, e.g. by converting to csc and taking column norms directly
291
+ ord_list = ord if isinstance(ord, (list, _np.ndarray)) else [ord] * m.shape[1]
292
+ assert(len(ord_list) == m.shape[1])
327
293
  norms = _np.array([_np.linalg.norm(m[:, j].toarray(), ord=o) for j, o in enumerate(ord_list)])
294
+ elif isinstance(ord, (list, _np.ndarray)):
295
+ assert(len(ord) == m.shape[1])
296
+ norms = _np.array([_np.linalg.norm(m[:, j], ord=o) for j, o in enumerate(ord)])
328
297
  else:
329
- norms = _np.array([_np.linalg.norm(m[:, j], ord=o) for j, o in enumerate(ord_list)])
298
+ norms = _np.linalg.norm(m, axis=0, ord=ord)
299
+
330
300
  return norms
331
301
 
332
302
 
@@ -382,8 +352,9 @@ def columns_are_orthogonal(m, tol=1e-7):
382
352
  -------
383
353
  bool
384
354
  """
385
- if m.size == 0: return True # boundary case
386
- check = _np.dot(m.conj().T, m)
355
+ if m.size == 0:
356
+ return True # boundary case
357
+ check = gram_matrix(m)
387
358
  check[_np.diag_indices_from(check)] = 0.0
388
359
  return bool(_np.linalg.norm(check) / check.size < tol)
389
360
 
@@ -408,9 +379,11 @@ def columns_are_orthonormal(m, tol=1e-7):
408
379
  -------
409
380
  bool
410
381
  """
411
- if m.size == 0: return True # boundary case
412
- check = _np.dot(m.conj().T, m)
413
- return bool(_np.allclose(check, _np.identity(check.shape[0], 'd'), atol=tol))
382
+ if m.size == 0:
383
+ return True # boundary case
384
+ check = gram_matrix(m)
385
+ check[_np.diag_indices_from(check)] -= 1.0
386
+ return bool(_np.linalg.norm(check) / check.size < tol)
414
387
 
415
388
 
416
389
  def independent_columns(m, initial_independent_cols=None, tol=1e-7):
@@ -440,27 +413,30 @@ def independent_columns(m, initial_independent_cols=None, tol=1e-7):
440
413
  list
441
414
  A list of the independent-column indices of `m`.
442
415
  """
443
- indep_cols = []
444
-
445
416
  if not _sps.issparse(m):
446
- running_indep_cols = initial_independent_cols.copy() \
447
- if (initial_independent_cols is not None) else _np.empty((m.shape[0], 0), m.dtype)
448
- num_indep_cols = running_indep_cols.shape[0]
449
-
450
- for j in range(m.shape[1]):
451
- trial = _np.concatenate((running_indep_cols, m[:, j]), axis=1)
452
- if _np.linalg.matrix_rank(trial, tol=tol) == num_indep_cols + 1:
453
- running_indep_cols = trial
454
- indep_cols.append(j)
455
- num_indep_cols += 1
456
-
457
- else: # sparse case
417
+ if initial_independent_cols is None:
418
+ proj_m = m.copy()
419
+ else:
420
+ # We assume initial_independent_cols is full column-rank.
421
+ # This lets us use unpivoted QR instead of pivoted QR or SVD.
422
+ assert initial_independent_cols.shape[0] == m.shape[0]
423
+ q = _spl.qr(initial_independent_cols, mode='econ')[0]
424
+ # proj_m = (I - qq')m
425
+ temp1 = q.T.conj() @ m
426
+ temp2 = q @ temp1
427
+ proj_m = m - temp2
428
+
429
+ rank = _np.linalg.matrix_rank(proj_m, tol=tol)
430
+ pivots = _spl.qr(proj_m, overwrite_a=True, mode='raw', pivoting=True)[2]
431
+ indep_cols = pivots[:rank].tolist()
458
432
 
433
+ else:
434
+ # TODO: re-implement to avoid unreliable calls to ARPACK's svds.
435
+ indep_cols = []
459
436
  from scipy.sparse.linalg import ArpackNoConvergence as _ArpackNoConvergence
460
437
  from scipy.sparse.linalg import ArpackError as _ArpackError
461
438
  running_indep_cols = initial_independent_cols.copy() \
462
439
  if (initial_independent_cols is not None) else _sps.csc_matrix((m.shape[0], 0), dtype=m.dtype)
463
- num_indep_cols = running_indep_cols.shape[0]
464
440
 
465
441
  for j in range(m.shape[1]):
466
442
  trial = _sps.hstack((running_indep_cols, m[:, j]))
@@ -479,15 +455,33 @@ def independent_columns(m, initial_independent_cols=None, tol=1e-7):
479
455
 
480
456
 
481
457
  def pinv_of_matrix_with_orthogonal_columns(m):
482
- """ TODO: docstring """
483
- col_scaling = _np.sum(_np.abs(m)**2, axis=0)
458
+ """
459
+ Return the matrix "pinv_m" so m @ pinvm and pinv_m @ m are orthogonal projectors
460
+ onto subspaces of dimension rank(m).
461
+
462
+ Parameters
463
+ ----------
464
+ m : numpy.ndarray
465
+
466
+ Returns
467
+ ----------
468
+ pinv_m : numpy.ndarray
469
+ """
470
+ col_scaling = _np.linalg.norm(m, axis=0)**2
484
471
  m_with_scaled_cols = m.conj() * col_scaling[None, :]
485
472
  return m_with_scaled_cols.T
486
473
 
487
474
 
488
475
  def matrix_sign(m):
489
476
  """
490
- The "sign" matrix of `m`
477
+ Compute the matrix s = sign(m). The eigenvectors of s are the same as those of m.
478
+ The eigenvalues of s are +/- 1, corresponding to the signs of m's eigenvalues.
479
+
480
+ It's straightforward to compute s when m is a normal operator. If m is not normal,
481
+ then the definition of s can be given in terms of m's Jordan form, and s
482
+ can be computed by (suitably post-processing) the Schur decomposition of m.
483
+
484
+ See https://nhigham.com/2020/12/15/what-is-the-matrix-sign-function/ for background.
491
485
 
492
486
  Parameters
493
487
  ----------
@@ -498,40 +492,45 @@ def matrix_sign(m):
498
492
  -------
499
493
  numpy.ndarray
500
494
  """
501
- #Notes: sign(m) defined s.t. eigvecs of sign(m) are evecs of m
502
- # and evals of sign(m) are +/-1 or 0 based on sign of eigenvalues of m
495
+ N = m.shape[0]
496
+ assert(m.shape == (N, N)), "m must be square!"
503
497
 
504
- #Using the extremely numerically stable (but expensive) Schur method
505
- # see http://www.maths.manchester.ac.uk/~higham/fm/OT104HighamChapter5.pdf
506
- N = m.shape[0]; assert(m.shape == (N, N)), "m must be square!"
507
- T, Z = _spl.schur(m, 'complex') # m = Z T Z^H where Z is unitary and T is upper-triangular
508
- U = _np.zeros(T.shape, 'complex') # will be sign(T), which is easy to compute
509
- # (U is also upper triangular), and then sign(m) = Z U Z^H
498
+ if is_hermitian(m):
499
+ eigvals, eigvecs = _spl.eigh(m)
500
+ sign = (eigvecs * _np.sign(eigvals)[None, :]) @ eigvecs.T.conj()
501
+ return sign
510
502
 
511
- # diagonals are easy
503
+ T, Z = _spl.schur(m, 'complex') # m = Z T Z^H where Z is unitary and T is upper-triangular
504
+ U = _np.zeros(T.shape, 'complex')
512
505
  U[_np.diag_indices_from(U)] = _np.sign(_np.diagonal(T))
506
+ # If T is diagonal, then we're basically done. If T isn't diagonal, then we have work to do.
507
+
508
+ if not _np.all(_np.isclose(T[_np.triu_indices(N, 1)], 0.0)):
509
+ # Use the extremely numerically stable (but expensive) method from
510
+ # N. Higham's book, Functions of Matrices : Theory and Practice, Chapter 5.
511
+
512
+ #Off diagonals: use U^2 = I or TU = UT
513
+ # Note: Tij = Uij = 0 when i > j and i==j easy so just consider i<j case
514
+ # 0 = sum_k Uik Ukj = (i!=j b/c off-diag)
515
+ # FUTURE: speed this up by using np.dot instead of sums below
516
+ for j in range(1, N):
517
+ for i in range(j - 1, -1, -1):
518
+ S = U[i, i] + U[j, j]
519
+ if _np.isclose(S, 0): # then use TU = UT
520
+ if _np.isclose(T[i, i] - T[j, j], 0): # then just set to zero
521
+ U[i, j] = 0.0 # TODO: check correctness of this case
522
+ else:
523
+ U[i, j] = T[i, j] * (U[i, i] - U[j, j]) / (T[i, i] - T[j, j]) + \
524
+ sum([U[i, k] * T[k, j] - T[i, k] * U[k, j] for k in range(i + 1, j)]) \
525
+ / (T[i, i] - T[j, j])
526
+ else: # use U^2 = I
527
+ U[i, j] = - sum([U[i, k] * U[k, j] for k in range(i + 1, j)]) / S
528
+ else:
529
+ pass
513
530
 
514
- #Off diagonals: use U^2 = I or TU = UT
515
- # Note: Tij = Uij = 0 when i > j and i==j easy so just consider i<j case
516
- # 0 = sum_k Uik Ukj = (i!=j b/c off-diag)
517
- # FUTURE: speed this up by using np.dot instead of sums below
518
- for j in range(1, N):
519
- for i in range(j - 1, -1, -1):
520
- S = U[i, i] + U[j, j]
521
- if _np.isclose(S, 0): # then use TU = UT
522
- if _np.isclose(T[i, i] - T[j, j], 0): # then just set to zero
523
- U[i, j] = 0.0 # TODO: check correctness of this case
524
- else:
525
- U[i, j] = T[i, j] * (U[i, i] - U[j, j]) / (T[i, i] - T[j, j]) + \
526
- sum([U[i, k] * T[k, j] - T[i, k] * U[k, j] for k in range(i + 1, j)]) \
527
- / (T[i, i] - T[j, j])
528
- else: # use U^2 = I
529
- U[i, j] = - sum([U[i, k] * U[k, j] for k in range(i + 1, j)]) / S
530
- return _np.dot(Z, _np.dot(U, _np.conjugate(Z.T)))
531
+ sign = Z @ (U @ Z.T.conj())
532
+ return sign
531
533
 
532
- #Quick & dirty - not always stable:
533
- #U,_,Vt = _np.linalg.svd(M)
534
- #return _np.dot(U,Vt)
535
534
 
536
535
 
537
536
  def print_mx(mx, width=9, prec=4, withbrackets=False):
@@ -676,6 +675,7 @@ def unitary_superoperator_matrix_log(m, mx_basis):
676
675
  evals = _np.linalg.eigvals(M_std)
677
676
  assert(_np.allclose(_np.abs(evals), 1.0)) # simple but technically incomplete check for a unitary superop
678
677
  # (e.g. could be anti-unitary: diag(1, -1, -1, -1))
678
+
679
679
  U = _ot.std_process_mx_to_unitary(M_std)
680
680
  H = _spl.logm(U) / -1j # U = exp(-iH)
681
681
  logM_std = _lt.create_elementary_errorgen('H', H) # rho --> -i[H, rho]
@@ -757,7 +757,7 @@ def approximate_matrix_log(m, target_logm, target_weight=10.0, tol=1e-6):
757
757
  logM = flat_logm.reshape(mx_shape)
758
758
  testM = _spl.expm(logM)
759
759
  ret = target_weight * _np.linalg.norm(logM - target_logm)**2 + \
760
- _np.linalg.norm(testM.flatten() - m.flatten(), 1)
760
+ _np.linalg.norm(testM.ravel() - m.ravel(), 1)
761
761
  #print("DEBUG: ",ret)
762
762
  return ret
763
763
 
@@ -774,7 +774,7 @@ def approximate_matrix_log(m, target_logm, target_weight=10.0, tol=1e-6):
774
774
  print_obj_func = None
775
775
 
776
776
  logM = _np.real(real_matrix_log(m, action_if_imaginary="ignore")) # just drop any imaginary part
777
- initial_flat_logM = logM.flatten() # + 0.1*target_logm.flatten()
777
+ initial_flat_logM = logM.ravel() # + 0.1*target_logm.flatten()
778
778
  # Note: adding some of target_logm doesn't seem to help; and hurts in easy cases
779
779
 
780
780
  if _objective(initial_flat_logM) > 1e-16: # otherwise initial logM is fine!
@@ -902,9 +902,6 @@ def real_matrix_log(m, action_if_imaginary="raise", tol=1e-8):
902
902
 
903
903
 
904
904
  ## ------------------------ Erik : Matrix tools that Tim has moved here -----------
905
- from scipy.linalg import sqrtm as _sqrtm
906
- import itertools as _ittls
907
-
908
905
 
909
906
  def column_basis_vector(i, dim):
910
907
  """
@@ -944,27 +941,9 @@ def vec(matrix_in):
944
941
  return [b for a in _np.transpose(matrix_in) for b in a]
945
942
 
946
943
 
947
- def unvec(vector_in):
948
- """
949
- Slices a vector into the columns of a matrix.
950
-
951
- Parameters
952
- ----------
953
- vector_in : numpy.ndarray
954
-
955
- Returns
956
- -------
957
- numpy.ndarray
958
- """
959
- dim = int(_np.sqrt(len(vector_in)))
960
- return _np.transpose(_np.array(list(
961
- zip(*[_ittls.chain(vector_in,
962
- _ittls.repeat(None, dim - 1))] * dim))))
963
-
964
-
965
944
  def norm1(m):
966
945
  """
967
- Returns the 1 norm of a matrix
946
+ Returns the Schatten 1-norm of a matrix
968
947
 
969
948
  Parameters
970
949
  ----------
@@ -975,9 +954,13 @@ def norm1(m):
975
954
  -------
976
955
  numpy.ndarray
977
956
  """
978
- return float(_np.real(_np.trace(_sqrtm(_np.dot(m.conj().T, m)))))
957
+ s = _spl.svdvals(m)
958
+ nrm = _np.sum(s)
959
+ return nrm
979
960
 
980
961
 
962
+ # Riley note: I'd like to rewrite this, but I don't want to mess with reproducibility
963
+ # issues. For now I've just made it a teeny bit more efficient.
981
964
  def random_hermitian(dim):
982
965
  """
983
966
  Generates a random Hermitian matrix
@@ -996,12 +979,13 @@ def random_hermitian(dim):
996
979
  dim = int(dim)
997
980
  a = _np.random.random(size=[dim, dim])
998
981
  b = _np.random.random(size=[dim, dim])
999
- c = a + 1.j * b + (a + 1.j * b).conj().T
982
+ c = a + 1.j * b
983
+ c += c.conj().T
1000
984
  my_norm = norm1(c)
1001
985
  return c / my_norm
1002
986
 
1003
987
 
1004
- def norm1to1(operator, num_samples=10000, mx_basis="gm", return_list=False):
988
+ def norm1to1(operator, num_samples=10000, mx_basis="gm"):
1005
989
  """
1006
990
  The Hermitian 1-to-1 norm of a superoperator represented in the standard basis.
1007
991
 
@@ -1019,23 +1003,20 @@ def norm1to1(operator, num_samples=10000, mx_basis="gm", return_list=False):
1019
1003
  mx_basis : {'std', 'gm', 'pp', 'qt'} or Basis
1020
1004
  The basis of `operator`.
1021
1005
 
1022
- return_list : bool, optional
1023
- Whether the entire list of sampled values is returned or just the maximum.
1024
-
1025
1006
  Returns
1026
1007
  -------
1027
1008
  float or list
1028
1009
  Depends on the value of `return_list`.
1029
1010
  """
1030
1011
  std_operator = change_basis(operator, mx_basis, 'std')
1031
- rand_dim = int(_np.sqrt(float(len(std_operator))))
1032
- vals = [norm1(unvec(_np.dot(std_operator, vec(random_hermitian(rand_dim)))))
1033
- for n in range(num_samples)]
1034
- if return_list:
1035
- return vals
1036
- else:
1037
- return max(vals)
1038
-
1012
+ dim = int(_np.sqrt(len(std_operator)))
1013
+ max_val = 0.0
1014
+ for _ in range(num_samples):
1015
+ invec = random_hermitian(dim).ravel(order='F')
1016
+ outvec = std_operator @ invec
1017
+ val = norm1(outvec.reshape((dim,dim), order='F'))
1018
+ max_val = max(val, max_val)
1019
+ return max_val
1039
1020
 
1040
1021
  ## ------------------------ General utility fns -----------------------------------
1041
1022
 
@@ -1345,9 +1326,9 @@ def _fas(a, inds, rhs, add=False):
1345
1326
  indx_tups = list(_itertools.product(*b))
1346
1327
  inds = tuple(zip(*indx_tups)) # un-zips to one list per dim
1347
1328
  if add:
1348
- a[inds] += rhs.flatten()
1329
+ a[inds] += rhs.ravel()
1349
1330
  else:
1350
- a[inds] = rhs.flatten()
1331
+ a[inds] = rhs.ravel()
1351
1332
 
1352
1333
  #OLD DEBUG: just a reference for building the C-implementation (this is very slow in python!)
1353
1334
  ##Alt: C-able impl
@@ -1443,104 +1424,6 @@ def _findx(a, inds, always_copy=False):
1443
1424
  return a_inds
1444
1425
 
1445
1426
 
1446
- def safe_dot(a, b):
1447
- """
1448
- Performs dot(a,b) correctly when neither, either, or both arguments are sparse matrices.
1449
-
1450
- Parameters
1451
- ----------
1452
- a : numpy.ndarray or scipy.sparse matrix.
1453
- First matrix.
1454
-
1455
- b : numpy.ndarray or scipy.sparse matrix.
1456
- Second matrix.
1457
-
1458
- Returns
1459
- -------
1460
- numpy.ndarray or scipy.sparse matrix
1461
- """
1462
- if _sps.issparse(a):
1463
- return a.dot(b) # sparseMx.dot works for both sparse and dense args
1464
- elif _sps.issparse(b):
1465
- # to return a sparse mx even when a is dense (asymmetric behavior):
1466
- # --> return _sps.csr_matrix(a).dot(b) # numpyMx.dot can't handle sparse argument
1467
- return _np.dot(a, b.toarray())
1468
- else:
1469
- return _np.dot(a, b)
1470
-
1471
-
1472
- def safe_real(a, inplace=False, check=False):
1473
- """
1474
- Get the real-part of `a`, where `a` can be either a dense array or a sparse matrix.
1475
-
1476
- Parameters
1477
- ----------
1478
- a : numpy.ndarray or scipy.sparse matrix.
1479
- Array to take real part of.
1480
-
1481
- inplace : bool, optional
1482
- Whether this operation should be done in-place.
1483
-
1484
- check : bool, optional
1485
- If True, raise a `ValueError` if `a` has a nonzero imaginary part.
1486
-
1487
- Returns
1488
- -------
1489
- numpy.ndarray or scipy.sparse matrix
1490
- """
1491
- if check:
1492
- assert(safe_norm(a, 'imag') < 1e-6), "Check failed: taking real-part of matrix w/nonzero imaginary part"
1493
- if _sps.issparse(a):
1494
- if _sps.isspmatrix_csr(a):
1495
- if inplace:
1496
- ret = _sps.csr_matrix((_np.real(a.data), a.indices, a.indptr), shape=a.shape, dtype='d')
1497
- else: # copy
1498
- ret = _sps.csr_matrix((_np.real(a.data).copy(), a.indices.copy(),
1499
- a.indptr.copy()), shape=a.shape, dtype='d')
1500
- ret.eliminate_zeros()
1501
- return ret
1502
- else:
1503
- raise NotImplementedError("safe_real() doesn't work with %s matrices yet" % str(type(a)))
1504
- else:
1505
- return _np.real(a)
1506
-
1507
-
1508
- def safe_imag(a, inplace=False, check=False):
1509
- """
1510
- Get the imaginary-part of `a`, where `a` can be either a dense array or a sparse matrix.
1511
-
1512
- Parameters
1513
- ----------
1514
- a : numpy.ndarray or scipy.sparse matrix.
1515
- Array to take imaginary part of.
1516
-
1517
- inplace : bool, optional
1518
- Whether this operation should be done in-place.
1519
-
1520
- check : bool, optional
1521
- If True, raise a `ValueError` if `a` has a nonzero real part.
1522
-
1523
- Returns
1524
- -------
1525
- numpy.ndarray or scipy.sparse matrix
1526
- """
1527
- if check:
1528
- assert(safe_norm(a, 'real') < 1e-6), "Check failed: taking imag-part of matrix w/nonzero real part"
1529
- if _sps.issparse(a):
1530
- if _sps.isspmatrix_csr(a):
1531
- if inplace:
1532
- ret = _sps.csr_matrix((_np.imag(a.data), a.indices, a.indptr), shape=a.shape, dtype='d')
1533
- else: # copy
1534
- ret = _sps.csr_matrix((_np.imag(a.data).copy(), a.indices.copy(),
1535
- a.indptr.copy()), shape=a.shape, dtype='d')
1536
- ret.eliminate_zeros()
1537
- return ret
1538
- else:
1539
- raise NotImplementedError("safe_real() doesn't work with %s matrices yet" % str(type(a)))
1540
- else:
1541
- return _np.imag(a)
1542
-
1543
-
1544
1427
  def safe_norm(a, part=None):
1545
1428
  """
1546
1429
  Get the frobenius norm of a matrix or vector, `a`, when it is either a dense array or a sparse matrix.
@@ -1950,23 +1833,6 @@ def _custom_expm_multiply_simple_core(a, b, mu, m_star, s, tol, eta): # t == 1.
1950
1833
  return F
1951
1834
 
1952
1835
 
1953
- #From SciPy source, as a reference - above we assume A is a sparse csr matrix
1954
- # and B is a dense vector
1955
- #def _exact_inf_norm(A):
1956
- # # A compatibility function which should eventually disappear.
1957
- # if scipy.sparse.isspmatrix(A):
1958
- # return max(abs(A).sum(axis=1).flat)
1959
- # else:
1960
- # return np.linalg.norm(A, np.inf)
1961
- #
1962
- #
1963
- #def _exact_1_norm(A):
1964
- # # A compatibility function which should eventually disappear.
1965
- # if scipy.sparse.isspmatrix(A):
1966
- # return max(abs(A).sum(axis=0).flat)
1967
- # else:
1968
- # return np.linalg.norm(A, 1)
1969
-
1970
1836
  def expop_multiply_prep(op, a_1_norm=None, tol=EXPM_DEFAULT_TOL):
1971
1837
  """
1972
1838
  Returns "prepared" meta-info about operation op, which is assumed to be traceless (so no shift is needed).
@@ -2115,7 +1981,7 @@ def to_unitary(scaled_unitary):
2115
1981
  unitary : ndarray
2116
1982
  Such that `scale * unitary == scaled_unitary`.
2117
1983
  """
2118
- scaled_identity = _np.dot(scaled_unitary, _np.conjugate(scaled_unitary.T))
1984
+ scaled_identity = gram_matrix(scaled_unitary, adjoint=True)
2119
1985
  scale = _np.sqrt(scaled_identity[0, 0])
2120
1986
  assert(_np.allclose(scaled_identity / (scale**2), _np.identity(scaled_identity.shape[0], 'd'))), \
2121
1987
  "Given `scaled_unitary` does not appear to be a scaled unitary matrix!"
@@ -2314,30 +2180,6 @@ def project_onto_antikite(mx, kite):
2314
2180
  return mx
2315
2181
 
2316
2182
 
2317
- def remove_dependent_cols(mx, tol=1e-7):
2318
- """
2319
- Removes the linearly dependent columns of a matrix.
2320
-
2321
- Parameters
2322
- ----------
2323
- mx : numpy.ndarray
2324
- The input matrix
2325
-
2326
- Returns
2327
- -------
2328
- A linearly independent subset of the columns of `mx`.
2329
- """
2330
- last_rank = 0; cols_to_remove = []
2331
- for j in range(mx.shape[1]):
2332
- rnk = _np.linalg.matrix_rank(mx[:, 0:j + 1], tol)
2333
- if rnk == last_rank:
2334
- cols_to_remove.append(j)
2335
- else:
2336
- last_rank = rnk
2337
- #print("Removing %d cols" % len(cols_to_remove))
2338
- return _np.delete(mx, cols_to_remove, axis=1)
2339
-
2340
-
2341
2183
  def intersection_space(space1, space2, tol=1e-7, use_nice_nullspace=False):
2342
2184
  """
2343
2185
  TODO: docstring
@@ -2353,16 +2195,8 @@ def union_space(space1, space2, tol=1e-7):
2353
2195
  TODO: docstring
2354
2196
  """
2355
2197
  VW = _np.concatenate((space1, space2), axis=1)
2356
- return remove_dependent_cols(VW, tol)
2357
-
2358
-
2359
- #UNUSED
2360
- #def spectral_radius(x):
2361
- # if hasattr(x, 'ndim') and x.ndim == 2: # then interpret as a numpy array and take norm
2362
- # evals = _np.sort(_np.linalg.eigvals(x))
2363
- # return abs(evals[-1] - evals[0])
2364
- # else:
2365
- # return x
2198
+ indep_cols = independent_columns(VW, None, tol)
2199
+ return VW[:, indep_cols]
2366
2200
 
2367
2201
 
2368
2202
  def jamiolkowski_angle(hamiltonian_mx):